Browse Source

Handle added packages in API diff (#20428)

pull/20437/head
Julien Lebosquain 4 weeks ago
committed by GitHub
parent
commit
d034e5f4e8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 113
      nukebuild/ApiDiffHelper.cs
  2. 5
      nukebuild/Build.cs

113
nukebuild/ApiDiffHelper.cs

@ -304,44 +304,95 @@ public static class ApiDiffHelper
{
using var packageReader = new PackageArchiveReader(currentArchive);
packageId = packageReader.NuspecReader.GetId();
baselineFolderPath = outputFolderPath / "baseline" / packageId;
Directory.CreateDirectory(baselineFolderPath);
currentFolderPath = outputFolderPath / "current" / packageId;
Directory.CreateDirectory(currentFolderPath);
currentFolderNames = ExtractDiffableAssembliesFromPackage(currentArchive, currentFolderPath);
}
// Download baseline package
memoryStream.Position = 0L;
memoryStream.SetLength(0L);
await DownloadBaselinePackageAsync(memoryStream, downloadContext, packageId, baselineVersion);
memoryStream.Position = 0L;
var packageExists = await downloadContext.FindPackageByIdResource.DoesPackageExistAsync(
packageId,
baselineVersion,
downloadContext.CacheContext,
NullLogger.Instance,
CancellationToken.None);
// Extract baseline package
using (var baselineArchive = new ZipArchive(memoryStream, ZipArchiveMode.Read, leaveOpen: true))
if (packageExists)
{
baselineFolderPath = outputFolderPath / "baseline" / packageId;
// Download baseline package
memoryStream.Position = 0L;
memoryStream.SetLength(0L);
await DownloadBaselinePackageAsync(memoryStream, downloadContext, packageId, baselineVersion);
memoryStream.Position = 0L;
// Extract baseline package
using var baselineArchive = new ZipArchive(memoryStream, ZipArchiveMode.Read, leaveOpen: true);
baselineFolderNames = ExtractDiffableAssembliesFromPackage(baselineArchive, baselineFolderPath);
}
else
{
Information("Baseline package {Id} {Version} does not exist. Assuming new package.", packageId, baselineVersion);
baselineFolderNames = [];
}
if (currentFolderNames.Count == 0 && baselineFolderNames.Count == 0)
continue;
var frameworkDiffs = new List<FrameworkDiffInfo>();
// Match frameworks
foreach (var (framework, currentFolderName) in currentFolderNames)
{
// Ignore new frameworks that didn't exist in the baseline package. Empty folders make the ApiDiff tool crash.
if (!baselineFolderNames.TryGetValue(framework, out var baselineFolderName))
continue;
baselineFolderName = currentFolderName;
frameworkDiffs.Add(new FrameworkDiffInfo(
var frameworkDiff = new FrameworkDiffInfo(
framework,
baselineFolderPath / FolderLib / baselineFolderName,
currentFolderPath / FolderLib / currentFolderName));
currentFolderPath / FolderLib / currentFolderName);
EnsureAssemblies(frameworkDiff);
frameworkDiffs.Add(frameworkDiff);
}
packageDiffs.Add(new PackageDiffInfo(packageId, [..frameworkDiffs]));
}
return new GlobalDiffInfo(baselineVersion, currentVersion, packageDiffs.DrainToImmutable());
// Ensure that both sides of a framework diff have matching assemblies.
// For any missing, generate an empty assembly to diff against.
// (The API diff tool supports added and removed assemblies in theory but actually throws if one side doesn't have any.)
static void EnsureAssemblies(FrameworkDiffInfo frameworkDiff)
{
Directory.CreateDirectory(frameworkDiff.BaselineFolderPath);
Directory.CreateDirectory(frameworkDiff.CurrentFolderPath);
var baselineFileNames = GetFileNames(frameworkDiff.BaselineFolderPath);
var currentFileNames = GetFileNames(frameworkDiff.CurrentFolderPath);
GenerateMissingAssemblies(currentFileNames.Except(baselineFileNames), frameworkDiff.BaselineFolderPath);
GenerateMissingAssemblies(baselineFileNames.Except(currentFileNames), frameworkDiff.CurrentFolderPath);
static string[] GetFileNames(string folderPath)
=> Directory.EnumerateFiles(folderPath, "*.dll").Select(Path.GetFileName)!.ToArray<string>();
void GenerateMissingAssemblies(IEnumerable<string> missingFileNames, string folderPath)
{
foreach (var missingFileName in missingFileNames)
{
GenerateEmptyAssembly(
Path.GetFileNameWithoutExtension(missingFileName),
frameworkDiff.Framework.GetShortFolderName(),
Path.Join(folderPath, missingFileName));
}
}
}
}
static async Task<NuGetDownloadContext> CreateNuGetDownloadContextAsync()
@ -453,6 +504,44 @@ public static class ApiDiffHelper
value;
}
static void GenerateEmptyAssembly(string name, string framework, string outputFilePath)
{
var projectContents =
$"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>{framework}</TargetFramework>
<Configuration>Release</Configuration>
<DebugType>None</DebugType>
</PropertyGroup>
</Project>
""";
var tempDirPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var projectFilePath = Path.Join(tempDirPath, $"{name}.csproj");
Directory.CreateDirectory(tempDirPath);
try
{
File.WriteAllText(projectFilePath, projectContents);
using var process = ProcessTasks.StartProcess(
"dotnet",
$"build \"{projectFilePath}\" --output \"{tempDirPath}\"",
tempDirPath);
process.AssertZeroExitCode();
File.Copy(Path.Join(tempDirPath, $"{name}.dll"), outputFilePath);
}
finally
{
if (Directory.Exists(tempDirPath))
Directory.Delete(tempDirPath, true);
}
}
public sealed class GlobalDiffInfo(
NuGetVersion baselineVersion,
NuGetVersion currentVersion,

5
nukebuild/Build.cs

@ -336,11 +336,14 @@ partial class Build : NukeBuild
.DependsOn(CreateNugetPackages)
.Executes(async () =>
{
var apiDiffPath = Parameters.ArtifactsDir / "api-diff";
apiDiffPath.DeleteDirectory();
GlobalDiff = await ApiDiffHelper.DownloadAndExtractPackagesAsync(
Directory.EnumerateFiles(Parameters.NugetRoot, "*.nupkg").Select(path => (AbsolutePath)path),
NuGetVersion.Parse(Parameters.Version),
Parameters.IsReleaseBranch,
Parameters.ArtifactsDir / "api-diff" / "assemblies",
apiDiffPath / "assemblies",
Parameters.ForceApiValidationBaseline is { } forcedBaseline ? NuGetVersion.Parse(forcedBaseline) : null);
});

Loading…
Cancel
Save