A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

123 lines
5.0 KiB

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using Nuke.Common.Tooling;
public static class ApiDiffValidation
{
public static void ValidatePackage(
Tool apiCompatTool, string packagePath, Version baselineVersion,
string suppressionFilesFolder, bool updateSuppressionFile)
{
if (baselineVersion is null)
{
throw new InvalidOperationException(
"Build \"api-baseline\" parameter must be set when running Nuke CreatePackages");
}
if (!Directory.Exists(suppressionFilesFolder))
{
Directory.CreateDirectory(suppressionFilesFolder!);
}
using (var baselineStream = DownloadBaselinePackage(packagePath, baselineVersion))
using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read))
using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read))
using (Helpers.UseTempDir(out var tempFolder))
{
var targetDlls = GetDlls(target);
var baselineDlls = GetDlls(baseline);
var left = new List<string>();
var right = new List<string>();
var suppressionFile = Path.Combine(suppressionFilesFolder, Path.GetFileName(packagePath) + ".xml");
foreach (var baselineDll in baselineDlls)
{
var baselineDllPath = Path.Combine("baseline", baselineDll.target, baselineDll.entry.Name);
var baselineDllRealPath = Path.Combine(tempFolder, baselineDllPath);
Directory.CreateDirectory(Path.GetDirectoryName(baselineDllRealPath)!);
using (var baselineDllFile = File.Create(baselineDllRealPath))
{
baselineDll.entry.Open().CopyTo(baselineDllFile);
}
var targetDll = targetDlls.FirstOrDefault(e =>
e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name);
if (targetDll.entry is null)
{
throw new InvalidOperationException($"Some assemblies are missing in the new package: {baselineDll.entry.Name} for {baselineDll.target}");
}
var targetDllPath = Path.Combine("target", targetDll.target, targetDll.entry.Name);
var targetDllRealPath = Path.Combine(tempFolder, targetDllPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetDllRealPath)!);
using (var targetDllFile = File.Create(targetDllRealPath))
{
targetDll.entry.Open().CopyTo(targetDllFile);
}
left.Add(baselineDllPath);
right.Add(targetDllPath);
}
var args = $""" -l={string.Join(',', left)} -r="{string.Join(',', right)}" """;
updateSuppressionFile = true;
if (File.Exists(suppressionFile))
{
args += $""" --suppression-file="{suppressionFile}" """;
}
if (updateSuppressionFile)
{
args += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file=true """;
}
apiCompatTool(args, tempFolder);
}
}
private static IReadOnlyCollection<(string target, ZipArchiveEntry entry)> GetDlls(ZipArchive archive)
{
return archive.Entries
.Where(e => Path.GetExtension(e.FullName) == ".dll")
.Select(e => (
entry: e,
isRef: e.FullName.Contains("ref/"),
target: Path.GetDirectoryName(e.FullName)!.Split('/').Last())
)
.GroupBy(e => (e.target, e.entry.Name))
.Select(g => g.MaxBy(e => e.isRef))
.Select(e => (e.target, e.entry))
.ToArray();
}
static Stream DownloadBaselinePackage(string packagePath, Version baselineVersion)
{
Build.Information("Downloading {0} baseline package for version {1}", Path.GetFileName(packagePath), baselineVersion);
try
{
var packageId = Regex.Replace(
Path.GetFileNameWithoutExtension(packagePath),
"""(\.\d+\.\d+\.\d+)$""", "");
using var httpClient = new HttpClient();
using var response = httpClient.Send(new HttpRequestMessage(HttpMethod.Get,
$"https://www.nuget.org/api/v2/package/{packageId}/{baselineVersion}"));
using var stream = response.Content.ReadAsStream();
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Downloading baseline package for {packagePath} failed.\r" + ex.Message, ex);
}
}
}