diff --git a/eng/Versions.props b/eng/Versions.props index d261d3d3d9..947a6569b4 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,6 +19,7 @@ 20230414.1 2.14.1 1.10.2 + 13.0.3 1.0.52 0.54.0 diff --git a/src/externalPackages/projects/azure-activedirectory-identitymodel-extensions-for-dotnet.proj b/src/externalPackages/projects/azure-activedirectory-identitymodel-extensions-for-dotnet.proj index 97d6471fa2..26c7ec5db2 100644 --- a/src/externalPackages/projects/azure-activedirectory-identitymodel-extensions-for-dotnet.proj +++ b/src/externalPackages/projects/azure-activedirectory-identitymodel-extensions-for-dotnet.proj @@ -7,7 +7,7 @@ 50722 - + Microsoft.IdentityModel.Tokens diff --git a/src/externalPackages/projects/newtonsoft-json.proj b/src/externalPackages/projects/newtonsoft-json.proj index 1702bbf0bd..b3c2fb334d 100644 --- a/src/externalPackages/projects/newtonsoft-json.proj +++ b/src/externalPackages/projects/newtonsoft-json.proj @@ -14,6 +14,12 @@ $(DotNetToolArgs) /p:PublicSign=true $(DotNetToolArgs) /p:TreatWarningsAsErrors=false $(DotNetToolArgs) /p:AdditionalConstants=SIGNED + + 27908 + + Newtonsoft.Json + $(DotNetToolArgs) /p:FileVersion=$(NewtonsoftJsonReleaseVersion).$(FileVersionRevision) $(DotNetTool) pack $(NewtonsoftJsonProjectPath) /bl:$(ArtifactsLogRepoDir)build.binlog $(DotNetToolArgs) diff --git a/tests/SbrpTests/ExternalPackageTests.cs b/tests/SbrpTests/ExternalPackageTests.cs index ea24fb02fa..a491d205a8 100644 --- a/tests/SbrpTests/ExternalPackageTests.cs +++ b/tests/SbrpTests/ExternalPackageTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; +using NuGet.Packaging; using SbrpUtilities; using Xunit; using Xunit.Abstractions; @@ -87,17 +88,18 @@ public void SourceRevisionIdMatchesSubmoduleCommit() /// /// Validates that the FileVersionRevision property in external package .proj files /// matches the actual FileVersion revision from the Microsoft-shipped NuGet package. - /// The test downloads the package specified by FileVersionValidationPackage using the - /// NuGet protocol API (honoring NuGet.config sources), extracts a DLL, reads its - /// FileVersion, and compares the revision component. + /// The test discovers the package ID from the component's build output directory + /// (artifacts/obj/{component}/), downloads the same package from NuGet, extracts a DLL, + /// reads its FileVersion, and compares the revision component. /// See https://github.com/dotnet/source-build/issues/5509 /// - [Fact] + [SkippableFact] public async Task FileVersionRevisionMatchesPublishedPackage() { string repoRoot = PathUtilities.GetRepoRoot().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string projectsDir = Path.Combine(repoRoot, "src", "externalPackages", "projects"); string versionsPropsPath = Path.Combine(repoRoot, "eng", "Versions.props"); + string artifactsObjDir = Path.Combine(repoRoot, "artifacts", "obj"); string[] projFiles = Directory.GetFiles(projectsDir, "*.proj"); Assert.True(projFiles.Length > 0, $"No .proj files found in {projectsDir}"); @@ -107,7 +109,7 @@ public async Task FileVersionRevisionMatchesPublishedPackage() foreach (string projFile in projFiles) { - string packageName = Path.GetFileNameWithoutExtension(projFile); + string componentName = Path.GetFileNameWithoutExtension(projFile); XDocument doc = XDocument.Load(projFile); string? fileVersionRevision = doc.Descendants("FileVersionRevision").FirstOrDefault()?.Value; @@ -116,47 +118,134 @@ public async Task FileVersionRevisionMatchesPublishedPackage() continue; } - string? validationPackage = doc.Descendants("FileVersionValidationPackage").FirstOrDefault()?.Value; - if (string.IsNullOrEmpty(validationPackage)) + // Find matching release version in eng/Versions.props + string? releaseVersion = CommonUtilities.FindReleaseVersion(versionsPropsPath, componentName); + + if (string.IsNullOrEmpty(releaseVersion)) { - errors.Add($"{packageName}.proj: Has FileVersionRevision but no FileVersionValidationPackage property."); + errors.Add($"{componentName}.proj: No matching release version property found in eng/Versions.props."); checkedCount++; continue; } - // Find matching release version in eng/Versions.props - string? releaseVersion = CommonUtilities.FindReleaseVersion(versionsPropsPath, packageName); + // Find a .nupkg produced by this specific component + string componentObjDir = Path.Combine(artifactsObjDir, componentName); + if (!Directory.Exists(componentObjDir)) + { + Output.WriteLine($"Skipping {componentName}: build output directory not found ({componentObjDir})."); + continue; + } - if (string.IsNullOrEmpty(releaseVersion)) + string[] componentNupkgs = Directory.GetFiles(componentObjDir, "*.nupkg", SearchOption.AllDirectories); + + // Find the first package that contains a DLL so we can read its FileVersion + string? packageId = null; + foreach (string nupkg in componentNupkgs) { - errors.Add($"{packageName}.proj: No matching release version property found in eng/Versions.props."); - checkedCount++; + using PackageArchiveReader reader = new(nupkg); + bool hasDll = reader.GetLibItems() + .SelectMany(group => group.Items) + .Any(item => item.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)); + if (hasDll) + { + packageId = reader.NuspecReader.GetId(); + break; + } + } + + if (packageId is null) + { + Output.WriteLine($"Skipping {componentName}: no .nupkg with a DLL found in build output."); continue; } - // Download the package and read the FileVersion revision + // Download the published package and read the FileVersion revision var (revision, fileVersion) = await CommonUtilities.GetFileVersionRevisionAsync( - repoRoot, validationPackage, releaseVersion); + repoRoot, packageId, releaseVersion); if (revision is null) { - errors.Add($"{packageName}.proj: Unable to download {validationPackage} {releaseVersion} to validate FileVersionRevision."); + errors.Add($"{componentName}.proj: Unable to download {packageId} {releaseVersion} to validate FileVersionRevision."); checkedCount++; continue; } if (!int.TryParse(fileVersionRevision, out int expectedRevision) || expectedRevision != revision.Value) { - errors.Add($"{packageName}.proj: FileVersionRevision '{fileVersionRevision}' does not match " + - $"actual revision '{revision}' from {validationPackage} {releaseVersion} " + + errors.Add($"{componentName}.proj: FileVersionRevision '{fileVersionRevision}' does not match " + + $"actual revision '{revision}' from {packageId} {releaseVersion} " + $"(FileVersion: {fileVersion})."); } checkedCount++; } - Assert.True(checkedCount > 0, "No external packages with FileVersionRevision were found to validate."); + Skip.If(checkedCount == 0, "No components with FileVersionRevision had build output to validate."); Assert.True(errors.Count == 0, $"FileVersionRevision validation failed:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}"); } + + /// + /// Validates that the release version configured in eng/Versions.props for each external + /// component matches the version of at least one NuGet package produced by that component. + /// The test scans each component's build output directory (artifacts/obj/{component}/) for + /// .nupkg files and verifies at least one has the expected version. + /// This catches cases where a submodule is updated but the release version is not. + /// + [SkippableFact] + public void ReleaseVersionMatchesPackageOutput() + { + string repoRoot = PathUtilities.GetRepoRoot().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string projectsDir = Path.Combine(repoRoot, "src", "externalPackages", "projects"); + string versionsPropsPath = Path.Combine(repoRoot, "eng", "Versions.props"); + string artifactsObjDir = Path.Combine(repoRoot, "artifacts", "obj"); + + string[] projFiles = Directory.GetFiles(projectsDir, "*.proj"); + Assert.True(projFiles.Length > 0, $"No .proj files found in {projectsDir}"); + + List errors = new(); + int checkedCount = 0; + + foreach (string projFile in projFiles) + { + string componentName = Path.GetFileNameWithoutExtension(projFile); + string? releaseVersion = CommonUtilities.FindReleaseVersion(versionsPropsPath, componentName); + + if (string.IsNullOrEmpty(releaseVersion)) + { + continue; + } + + // Scan the component's own build output directory for .nupkg files + string componentObjDir = Path.Combine(artifactsObjDir, componentName); + if (!Directory.Exists(componentObjDir)) + { + Output.WriteLine($"Skipping {componentName}: build output directory not found ({componentObjDir})."); + continue; + } + + string[] componentNupkgs = Directory.GetFiles(componentObjDir, "*.nupkg", SearchOption.AllDirectories); + if (componentNupkgs.Length == 0) + { + Output.WriteLine($"Skipping {componentName}: no .nupkg files found in build output."); + continue; + } + + string versionSuffix = $".{releaseVersion}.nupkg"; + bool foundMatch = componentNupkgs.Any(f => + Path.GetFileName(f).EndsWith(versionSuffix, StringComparison.OrdinalIgnoreCase)); + + if (!foundMatch) + { + string foundPackages = string.Join(", ", componentNupkgs.Select(Path.GetFileName)); + errors.Add($"{componentName}: Expected package version {releaseVersion} but found: {foundPackages}"); + } + + checkedCount++; + } + + Skip.If(checkedCount == 0, "No components with release versions had build output to validate."); + Assert.True(errors.Count == 0, + $"Release version validation failed:{Environment.NewLine}{string.Join(Environment.NewLine, errors)}"); + } }