ApiDiffValidation.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Linq;
  6. using System.Net.Http;
  7. using System.Text.RegularExpressions;
  8. using Nuke.Common.Tooling;
  9. public static class ApiDiffValidation
  10. {
  11. public static void ValidatePackage(
  12. Tool apiCompatTool, string packagePath, Version baselineVersion,
  13. string suppressionFilesFolder, bool updateSuppressionFile)
  14. {
  15. if (baselineVersion is null)
  16. {
  17. throw new InvalidOperationException(
  18. "Build \"api-baseline\" parameter must be set when running Nuke CreatePackages");
  19. }
  20. if (!Directory.Exists(suppressionFilesFolder))
  21. {
  22. Directory.CreateDirectory(suppressionFilesFolder!);
  23. }
  24. using (var baselineStream = DownloadBaselinePackage(packagePath, baselineVersion))
  25. using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read))
  26. using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read))
  27. using (Helpers.UseTempDir(out var tempFolder))
  28. {
  29. var targetDlls = GetDlls(target);
  30. var baselineDlls = GetDlls(baseline);
  31. var left = new List<string>();
  32. var right = new List<string>();
  33. var suppressionFile = Path.Combine(suppressionFilesFolder, Path.GetFileName(packagePath) + ".xml");
  34. foreach (var baselineDll in baselineDlls)
  35. {
  36. var baselineDllPath = Path.Combine("baseline", baselineDll.target, baselineDll.entry.Name);
  37. var baselineDllRealPath = Path.Combine(tempFolder, baselineDllPath);
  38. Directory.CreateDirectory(Path.GetDirectoryName(baselineDllRealPath)!);
  39. using (var baselineDllFile = File.Create(baselineDllRealPath))
  40. {
  41. baselineDll.entry.Open().CopyTo(baselineDllFile);
  42. }
  43. var targetDll = targetDlls.FirstOrDefault(e =>
  44. e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name);
  45. if (targetDll.entry is null)
  46. {
  47. throw new InvalidOperationException($"Some assemblies are missing in the new package: {baselineDll.entry.Name} for {baselineDll.target}");
  48. }
  49. var targetDllPath = Path.Combine("target", targetDll.target, targetDll.entry.Name);
  50. var targetDllRealPath = Path.Combine(tempFolder, targetDllPath);
  51. Directory.CreateDirectory(Path.GetDirectoryName(targetDllRealPath)!);
  52. using (var targetDllFile = File.Create(targetDllRealPath))
  53. {
  54. targetDll.entry.Open().CopyTo(targetDllFile);
  55. }
  56. left.Add(baselineDllPath);
  57. right.Add(targetDllPath);
  58. }
  59. var args = $""" -l={string.Join(',', left)} -r="{string.Join(',', right)}" """;
  60. updateSuppressionFile = true;
  61. if (File.Exists(suppressionFile))
  62. {
  63. args += $""" --suppression-file="{suppressionFile}" """;
  64. }
  65. if (updateSuppressionFile)
  66. {
  67. args += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file=true """;
  68. }
  69. apiCompatTool(args, tempFolder);
  70. }
  71. }
  72. private static IReadOnlyCollection<(string target, ZipArchiveEntry entry)> GetDlls(ZipArchive archive)
  73. {
  74. return archive.Entries
  75. .Where(e => Path.GetExtension(e.FullName) == ".dll")
  76. .Select(e => (
  77. entry: e,
  78. isRef: e.FullName.Contains("ref/"),
  79. target: Path.GetDirectoryName(e.FullName)!.Split('/').Last())
  80. )
  81. .GroupBy(e => (e.target, e.entry.Name))
  82. .Select(g => g.MaxBy(e => e.isRef))
  83. .Select(e => (e.target, e.entry))
  84. .ToArray();
  85. }
  86. static Stream DownloadBaselinePackage(string packagePath, Version baselineVersion)
  87. {
  88. Build.Information("Downloading {0} baseline package for version {1}", Path.GetFileName(packagePath), baselineVersion);
  89. try
  90. {
  91. var packageId = Regex.Replace(
  92. Path.GetFileNameWithoutExtension(packagePath),
  93. """(\.\d+\.\d+\.\d+)$""", "");
  94. using var httpClient = new HttpClient();
  95. using var response = httpClient.Send(new HttpRequestMessage(HttpMethod.Get,
  96. $"https://www.nuget.org/api/v2/package/{packageId}/{baselineVersion}"));
  97. using var stream = response.Content.ReadAsStream();
  98. var memoryStream = new MemoryStream();
  99. stream.CopyTo(memoryStream);
  100. memoryStream.Seek(0, SeekOrigin.Begin);
  101. return memoryStream;
  102. }
  103. catch (Exception ex)
  104. {
  105. throw new InvalidOperationException($"Downloading baseline package for {packagePath} failed.\r" + ex.Message, ex);
  106. }
  107. }
  108. }