| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- 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 System.Threading.Tasks;
- using Nuke.Common.Tooling;
- public static class ApiDiffValidation
- {
- private static readonly HttpClient s_httpClient = new();
- public static async Task ValidatePackage(
- Tool apiCompatTool, string packagePath, string 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!);
- }
- await using (var baselineStream = await 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)!);
- await using (var baselineDllFile = File.Create(baselineDllRealPath))
- {
- await baselineDll.entry.Open().CopyToAsync(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)!);
- await using (var targetDllFile = File.Create(targetDllRealPath))
- {
- await targetDll.entry.Open().CopyToAsync(targetDllFile);
- }
- left.Add(baselineDllPath);
- right.Add(targetDllPath);
- }
- if (left.Any())
- {
- var args = $""" -l={string.Join(',', left)} -r="{string.Join(',', right)}" """;
- if (File.Exists(suppressionFile))
- {
- args += $""" --suppression-file="{suppressionFile}" """;
- }
- if (updateSuppressionFile)
- {
- args += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file=true """;
- }
- var result = apiCompatTool(args, tempFolder)
- .Where(t => t.Type == OutputType.Err).ToArray();
- if (result.Any())
- {
- throw new AggregateException(
- $"ApiDiffValidation task has failed for \"{Path.GetFileName(packagePath)}\" package",
- result.Select(r => new Exception(r.Text)));
- }
- }
- }
- }
- 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 async Task<Stream> DownloadBaselinePackage(string packagePath, string 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 response = await s_httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get,
- $"https://www.nuget.org/api/v2/package/{packageId}/{baselineVersion}"), HttpCompletionOption.ResponseHeadersRead);
- response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync();
- var memoryStream = new MemoryStream();
- await stream.CopyToAsync(memoryStream);
- memoryStream.Seek(0, SeekOrigin.Begin);
- return memoryStream;
- }
- catch (Exception ex)
- {
- throw new InvalidOperationException($"Downloading baseline package for {packagePath} failed.\r" + ex.Message, ex);
- }
- }
- }
|