diff --git a/src/Layout/redist/targets/GenerateBundledVersions.targets b/src/Layout/redist/targets/GenerateBundledVersions.targets
index 127438a4e6b0..d53a41b4ee31 100644
--- a/src/Layout/redist/targets/GenerateBundledVersions.targets
+++ b/src/Layout/redist/targets/GenerateBundledVersions.targets
@@ -582,6 +582,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IsTrimmable="%(BundledTargetFramework.IsTrimmable)"
+
+ ExcludedRuntimeIdentifiers="%(BundledTargetFramework.AppHostExcludedRuntimeIdentifiers)"
+
+ Crossgen2PortableRuntimeIdentifiers="%(BundledTargetFramework.Crossgen2PortableRids)"
+
+ ILCompilerRuntimePackNamePattern="%(BundledTargetFramework.ILCompilerRuntimePackNamePattern)"
+
+ ILCompilerPortableRuntimeIdentifiers="%(BundledTargetFramework.ILCompilerPortableRids)"
+
+ RuntimePackExcludedRuntimeIdentifiers="%(BundledTargetFramework.AspNetCoreRuntimePackExcludedRuntimeIdentifiers)"
+
+
+
'', '
')
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+@(BundledTargetFramework->'
- '
- HasMetadata('Crossgen2Rids')->'
- HasMetadata('ILCompilerRids')->'
- HasMetadata('NativeAOTRids')->'
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+', '')
+@(BundledTargetFramework->'
-
+', '')
+@(BundledTargetFramework->HasMetadata('WebAssemblySdkPackVersion')->'
-
+', '')
+@(BundledTargetFramework->HasMetadata('AspNetCorePackVersion')->'
- HasMetadata('MonoRids')->'
- '
- '
- '
- '
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+', '')
+ /// Verifies the shape of the Known* items in Microsoft.NETCoreSdk.BundledVersions.props, which is generated
+ /// by batching over the BundledTargetFramework catalog in
+ /// src/Layout/redist/targets/GenerateBundledVersions.targets. These assertions guard against a target
+ /// framework accidentally gaining or losing an item (or a piece of metadata) when that catalog is edited.
+ ///
+ [TestClass]
+ public class BundledVersionsPropsTests : SdkTest
+ {
+ private static ILookup GetItemsByTargetFramework(string itemType)
+ {
+ var bundledVersionsPropsPath = Path.Combine(
+ SdkTestContext.Current.ToolsetUnderTest.SdkFolderUnderTest,
+ "Microsoft.NETCoreSdk.BundledVersions.props");
+
+ File.Exists(bundledVersionsPropsPath).Should().BeTrue($"'{bundledVersionsPropsPath}' should exist");
+
+ return XDocument.Load(bundledVersionsPropsPath)
+ .Root
+ .Elements()
+ .SelectMany(itemGroup => itemGroup.Elements())
+ .Where(item => item.Name.LocalName.Equals(itemType, StringComparison.Ordinal))
+ .ToLookup(item => (string)item.Attribute("TargetFramework"));
+ }
+
+ private static IEnumerable TargetFrameworks =>
+ GetItemsByTargetFramework("KnownFrameworkReference")
+ .Where(group => group.Any(item => (string)item.Attribute("Include") == "Microsoft.NETCore.App"))
+ .Select(group => group.Key);
+
+ private static Version VersionOf(string targetFramework) => NuGetFramework.Parse(targetFramework).Version;
+
+ [TestMethod]
+ public void ThereIsAKnownFrameworkReferenceForEveryTargetFramework()
+ {
+ var knownFrameworkReferences = GetItemsByTargetFramework("KnownFrameworkReference");
+
+ // Every shipped target framework should be in the catalog. The target framework of the SDK being
+ // built isn't listed here so that this test doesn't need to be updated for each new release.
+ TargetFrameworks.Should().Contain(new[] { "netcoreapp3.0", "netcoreapp3.1", "net5.0", "net6.0", "net7.0", "net8.0", "net9.0", "net10.0" });
+
+ foreach (var targetFramework in TargetFrameworks)
+ {
+ var includes = knownFrameworkReferences[targetFramework].Select(item => (string)item.Attribute("Include"));
+
+ includes.Should().BeEquivalentTo(new[]
+ {
+ "Microsoft.NETCore.App",
+ "Microsoft.WindowsDesktop.App",
+ "Microsoft.WindowsDesktop.App.WPF",
+ "Microsoft.WindowsDesktop.App.WindowsForms",
+ "Microsoft.AspNetCore.App"
+ }, $"{targetFramework} should have a KnownFrameworkReference for each shared framework");
+
+ foreach (var knownFrameworkReference in knownFrameworkReferences[targetFramework])
+ {
+ foreach (var requiredMetadata in new[]
+ {
+ "RuntimeFrameworkName",
+ "DefaultRuntimeFrameworkVersion",
+ "LatestRuntimeFrameworkVersion",
+ "TargetingPackName",
+ "TargetingPackVersion",
+ "RuntimePackNamePatterns",
+ "RuntimePackRuntimeIdentifiers"
+ })
+ {
+ ((string)knownFrameworkReference.Attribute(requiredMetadata)).Should().NotBeNullOrEmpty(
+ $"{knownFrameworkReference.Attribute("Include")} for {targetFramework} should specify {requiredMetadata}");
+ }
+ }
+ }
+ }
+
+ [TestMethod]
+ public void ToolPacksAreGeneratedForTheExpectedTargetFrameworks()
+ {
+ var appHostPacks = GetItemsByTargetFramework("KnownAppHostPack");
+ var ilLinkPacks = GetItemsByTargetFramework("KnownILLinkPack");
+ var crossgen2Packs = GetItemsByTargetFramework("KnownCrossgen2Pack");
+ var ilCompilerPacks = GetItemsByTargetFramework("KnownILCompilerPack");
+ var webAssemblySdkPacks = GetItemsByTargetFramework("KnownWebAssemblySdkPack");
+ var aspNetCorePacks = GetItemsByTargetFramework("KnownAspNetCorePack");
+ var runtimePacks = GetItemsByTargetFramework("KnownRuntimePack");
+
+ foreach (var targetFramework in TargetFrameworks)
+ {
+ var version = VersionOf(targetFramework);
+
+ appHostPacks[targetFramework].Should().HaveCount(1, $"{targetFramework} should have a KnownAppHostPack");
+ ilLinkPacks[targetFramework].Should().HaveCount(1, $"{targetFramework} should have a KnownILLinkPack");
+
+ // Crossgen2 was introduced in .NET 5, and NativeAOT (ILCompiler) in .NET 7.
+ crossgen2Packs[targetFramework].Should().HaveCount(version.Major >= 5 ? 1 : 0, $"KnownCrossgen2Pack for {targetFramework}");
+ ilCompilerPacks[targetFramework].Should().HaveCount(version.Major >= 7 ? 1 : 0, $"KnownILCompilerPack for {targetFramework}");
+
+ // The WebAssembly SDK pack applies to .NET 6 and up, the ASP.NET Core assets pack to .NET 10 and up.
+ webAssemblySdkPacks[targetFramework].Should().HaveCount(version.Major >= 6 ? 1 : 0, $"KnownWebAssemblySdkPack for {targetFramework}");
+ aspNetCorePacks[targetFramework].Should().HaveCount(version.Major >= 10 ? 1 : 0, $"KnownAspNetCorePack for {targetFramework}");
+
+ // The Mono runtime pack applies to .NET 6 and up, the NativeAOT runtime pack to .NET 8 and up.
+ var runtimePackLabels = runtimePacks[targetFramework].Select(item => (string)item.Attribute("RuntimePackLabels"));
+ var expectedRuntimePackLabels = new List();
+ if (version.Major >= 8)
+ {
+ expectedRuntimePackLabels.Add("NativeAOT");
+ }
+ if (version.Major >= 6)
+ {
+ expectedRuntimePackLabels.Add("Mono");
+ }
+
+ runtimePackLabels.Should().BeEquivalentTo(expectedRuntimePackLabels, $"KnownRuntimePack items for {targetFramework}");
+ }
+ }
+
+ [TestMethod]
+ public void PortableRuntimeIdentifiersAreOnlySetForRecentTargetFrameworks()
+ {
+ foreach (var (itemType, portableMetadata, runtimeIdentifiersMetadata) in new[]
+ {
+ ("KnownCrossgen2Pack", "Crossgen2PortableRuntimeIdentifiers", "Crossgen2RuntimeIdentifiers"),
+ ("KnownILCompilerPack", "ILCompilerPortableRuntimeIdentifiers", "ILCompilerRuntimeIdentifiers")
+ })
+ {
+ var packs = GetItemsByTargetFramework(itemType);
+
+ foreach (var targetFramework in TargetFrameworks)
+ {
+ foreach (var pack in packs[targetFramework])
+ {
+ ((string)pack.Attribute(runtimeIdentifiersMetadata)).Should().NotBeNullOrEmpty(
+ $"{itemType} for {targetFramework} should specify {runtimeIdentifiersMetadata}");
+
+ // The portable runtime identifier list was added in .NET 10.
+ var hasPortableRuntimeIdentifiers = !string.IsNullOrEmpty((string)pack.Attribute(portableMetadata));
+ hasPortableRuntimeIdentifiers.Should().Be(VersionOf(targetFramework).Major >= 10,
+ $"{itemType} for {targetFramework} {portableMetadata}");
+ }
+ }
+ }
+ }
+ }
+}