6 changed files with 170 additions and 5 deletions
@ -0,0 +1,123 @@ |
|||||
|
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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue