Jelajahi Sumber

Update Nuke to v9 and fix ValidateApiDiff (#19529)

Julien Lebosquain 1 bulan lalu
induk
melakukan
5bd5249b61

+ 95 - 102
.nuke/build.schema.json

@@ -1,48 +1,78 @@
 {
   "$schema": "http://json-schema.org/draft-04/schema#",
-  "title": "Build Schema",
-  "$ref": "#/definitions/build",
   "definitions": {
-    "build": {
-      "type": "object",
+    "Host": {
+      "type": "string",
+      "enum": [
+        "AppVeyor",
+        "AzurePipelines",
+        "Bamboo",
+        "Bitbucket",
+        "Bitrise",
+        "GitHubActions",
+        "GitLab",
+        "Jenkins",
+        "Rider",
+        "SpaceAutomation",
+        "TeamCity",
+        "Terminal",
+        "TravisCI",
+        "VisualStudio",
+        "VSCode"
+      ]
+    },
+    "ExecutableTarget": {
+      "type": "string",
+      "enum": [
+        "BuildToNuGetCache",
+        "CiAzureLinux",
+        "CiAzureOSX",
+        "CiAzureWindows",
+        "Clean",
+        "Compile",
+        "CompileHtmlPreviewer",
+        "CompileNative",
+        "CreateIntermediateNugetPackages",
+        "CreateNugetPackages",
+        "DownloadApiBaselinePackages",
+        "GenerateCppHeaders",
+        "OutputApiDiff",
+        "OutputVersion",
+        "Package",
+        "RunCoreLibsTests",
+        "RunHtmlPreviewerTests",
+        "RunLeakTests",
+        "RunRenderTests",
+        "RunTests",
+        "RunToolsTests",
+        "ValidateApiDiff",
+        "VerifyXamlCompilation",
+        "ZipFiles"
+      ]
+    },
+    "Verbosity": {
+      "type": "string",
+      "description": "",
+      "enum": [
+        "Verbose",
+        "Normal",
+        "Minimal",
+        "Quiet"
+      ]
+    },
+    "NukeBuild": {
       "properties": {
-        "configuration": {
-          "type": "string"
-        },
         "Continue": {
           "type": "boolean",
           "description": "Indicates to continue a previously failed build attempt"
         },
-        "force-api-baseline": {
-          "type": "string"
-        },
-        "force-nuget-version": {
-          "type": "string"
-        },
         "Help": {
           "type": "boolean",
           "description": "Shows the help text for this build assembly"
         },
         "Host": {
-          "type": "string",
           "description": "Host for execution. Default is 'automatic'",
-          "enum": [
-            "AppVeyor",
-            "AzurePipelines",
-            "Bamboo",
-            "Bitbucket",
-            "Bitrise",
-            "GitHubActions",
-            "GitLab",
-            "Jenkins",
-            "Rider",
-            "SpaceAutomation",
-            "TeamCity",
-            "Terminal",
-            "TravisCI",
-            "VisualStudio",
-            "VSCode"
-          ]
+          "$ref": "#/definitions/Host"
         },
         "NoLogo": {
           "type": "boolean",
@@ -71,91 +101,54 @@
           "type": "array",
           "description": "List of targets to be skipped. Empty list skips all dependencies",
           "items": {
-            "type": "string",
-            "enum": [
-              "BuildToNuGetCache",
-              "CiAzureLinux",
-              "CiAzureOSX",
-              "CiAzureWindows",
-              "Clean",
-              "Compile",
-              "CompileHtmlPreviewer",
-              "CompileNative",
-              "CreateIntermediateNugetPackages",
-              "CreateNugetPackages",
-              "DownloadApiBaselinePackages",
-              "GenerateCppHeaders",
-              "OutputApiDiff",
-              "OutputVersion",
-              "Package",
-              "RunCoreLibsTests",
-              "RunHtmlPreviewerTests",
-              "RunLeakTests",
-              "RunRenderTests",
-              "RunTests",
-              "RunToolsTests",
-              "ValidateApiDiff",
-              "VerifyXamlCompilation",
-              "ZipFiles"
-            ]
+            "$ref": "#/definitions/ExecutableTarget"
           }
         },
-        "skip-previewer": {
-          "type": "boolean"
-        },
-        "skip-tests": {
-          "type": "boolean"
-        },
         "Target": {
           "type": "array",
           "description": "List of targets to be invoked. Default is '{default_target}'",
           "items": {
-            "type": "string",
-            "enum": [
-              "BuildToNuGetCache",
-              "CiAzureLinux",
-              "CiAzureOSX",
-              "CiAzureWindows",
-              "Clean",
-              "Compile",
-              "CompileHtmlPreviewer",
-              "CompileNative",
-              "CreateIntermediateNugetPackages",
-              "CreateNugetPackages",
-              "DownloadApiBaselinePackages",
-              "GenerateCppHeaders",
-              "OutputApiDiff",
-              "OutputVersion",
-              "Package",
-              "RunCoreLibsTests",
-              "RunHtmlPreviewerTests",
-              "RunLeakTests",
-              "RunRenderTests",
-              "RunTests",
-              "RunToolsTests",
-              "ValidateApiDiff",
-              "VerifyXamlCompilation",
-              "ZipFiles"
-            ]
+            "$ref": "#/definitions/ExecutableTarget"
           }
         },
-        "update-api-suppression": {
-          "type": "boolean"
-        },
         "Verbosity": {
-          "type": "string",
           "description": "Logging verbosity during build execution. Default is 'Normal'",
-          "enum": [
-            "Minimal",
-            "Normal",
-            "Quiet",
-            "Verbose"
+          "$ref": "#/definitions/Verbosity"
+        }
+      }
+    }
+  },
+  "allOf": [
+    {
+      "properties": {
+        "configuration": {
+          "type": "string"
+        },
+        "force-api-baseline": {
+          "type": "string"
+        },
+        "force-nuget-version": {
+          "type": "string"
+        },
+        "skip-previewer": {
+          "type": "boolean"
+        },
+        "skip-tests": {
+          "type": "boolean"
+        },
+        "update-api-suppression": {
+          "type": [
+            "boolean",
+            "null"
           ]
         },
         "version-output-dir": {
           "type": "string"
         }
       }
+    },
+    {
+      "$ref": "#/definitions/NukeBuild"
     }
-  }
-}
+  ]
+}

+ 57 - 12
nukebuild/ApiDiffHelper.cs

@@ -7,6 +7,7 @@ using System.IO;
 using System.IO.Compression;
 using System.Linq;
 using System.Security.Cryptography;
+using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
 using NuGet.Common;
@@ -26,9 +27,13 @@ public static class ApiDiffHelper
     const string MainPackageName = "Avalonia";
     const string FolderLib = "lib";
 
+    private static readonly Regex s_suppressionPathRegex =
+        new("<(Left|Right)>(.*?)</(Left|Right)>", RegexOptions.Compiled);
+
     public static void ValidatePackage(
         Tool apiCompatTool,
         PackageDiffInfo packageDiff,
+        AbsolutePath rootAssembliesFolderPath,
         AbsolutePath suppressionFilesFolderPath,
         bool updateSuppressionFile)
     {
@@ -36,28 +41,68 @@ public static class ApiDiffHelper
 
         Directory.CreateDirectory(suppressionFilesFolderPath);
 
-        var suppressionArgs = "";
-
-        var suppressionFile = suppressionFilesFolderPath / (packageDiff.PackageId + ".nupkg.xml");
-        if (suppressionFile.FileExists())
-            suppressionArgs += $""" --suppression-file="{suppressionFile}" --permit-unnecessary-suppressions """;
-
-        if (updateSuppressionFile)
-            suppressionArgs += $""" --suppression-output-file="{suppressionFile}" --generate-suppression-file --preserve-unnecessary-suppressions """;
-
+        var suppressionFilePath = suppressionFilesFolderPath / (packageDiff.PackageId + ".nupkg.xml");
+        var replaceDirectorySeparators = Path.DirectorySeparatorChar == '\\';
         var allErrors = new List<string>();
 
         foreach (var framework in packageDiff.Frameworks)
         {
-            var args = $""" -l="{framework.BaselineFolderPath}" -r="{framework.CurrentFolderPath}" {suppressionArgs}""";
+            var relativeBaselinePath = rootAssembliesFolderPath.GetRelativePathTo(framework.BaselineFolderPath);
+            var relativeCurrentPath = rootAssembliesFolderPath.GetRelativePathTo(framework.CurrentFolderPath);
+            var args = "";
+
+            if (suppressionFilePath.FileExists())
+            {
+                args += $""" --suppression-file="{suppressionFilePath}" --permit-unnecessary-suppressions """;
+
+                if (replaceDirectorySeparators)
+                    ReplaceDirectorySeparators(suppressionFilePath, '/', '\\');
+            }
+
+            if (updateSuppressionFile)
+                args += $""" --suppression-output-file="{suppressionFilePath}" --generate-suppression-file --preserve-unnecessary-suppressions """;
+
+            args += $""" -l="{relativeBaselinePath}" -r="{relativeCurrentPath}" """;
+
+            var localErrors = GetErrors(apiCompatTool($"{args:nq}", rootAssembliesFolderPath, exitHandler: _ => { }));
+
+            if (replaceDirectorySeparators)
+                ReplaceDirectorySeparators(suppressionFilePath, '\\', '/');
 
-            var localErrors = GetErrors(apiCompatTool(args));
             allErrors.AddRange(localErrors);
         }
 
         ThrowOnErrors(allErrors, packageDiff.PackageId, "ValidateApiDiff");
     }
 
+    /// <summary>
+    /// The ApiCompat tool treats paths with '/' and '\' separators as different files.
+    /// Before running the tool, adjust the existing separators (using a dirty regex) to match the current platform.
+    /// After running the tool, change all separators back to '/'.
+    /// </summary>
+    static void ReplaceDirectorySeparators(AbsolutePath suppressionFilePath, char oldSeparator, char newSeparator)
+    {
+        if (!File.Exists(suppressionFilePath))
+            return;
+
+        var lines = File.ReadAllLines(suppressionFilePath);
+
+        for (var i = 0; i < lines.Length; i++)
+        {
+            var original = lines[i];
+
+            var replacement = s_suppressionPathRegex.Replace(original, match =>
+            {
+                var path = match.Groups[2].Value.Replace(oldSeparator, newSeparator);
+                return $"<{match.Groups[1].Value}>{path}</{match.Groups[3].Value}>";
+            });
+
+            lines[i] = replacement;
+        }
+
+        File.WriteAllLines(suppressionFilePath, lines);
+    }
+
     public static void GenerateMarkdownDiff(
         Tool apiDiffTool,
         PackageDiffInfo packageDiff,
@@ -87,7 +132,7 @@ public static class ApiDiffHelper
                     var frameworkOutputFolderPath = packageOutputFolderPath / framework.Framework.GetShortFolderName();
                     var args = $""" -b="{framework.BaselineFolderPath}" -bfn="{baselineDisplay}" -a="{framework.CurrentFolderPath}" -afn="{currentDisplay}" -o="{frameworkOutputFolderPath}" -eattrs="{excludedAttributesFilePath}" """;
 
-                    var localErrors = GetErrors(apiDiffTool(args));
+                    var localErrors = GetErrors(apiDiffTool($"{args:nq}"));
 
                     if (localErrors.Length > 0)
                     {

+ 29 - 21
nukebuild/Build.cs

@@ -11,12 +11,8 @@ using Nuke.Common.Tooling;
 using Nuke.Common.Tools.DotNet;
 using Nuke.Common.Tools.Npm;
 using static Nuke.Common.EnvironmentInfo;
-using static Nuke.Common.IO.FileSystemTasks;
 using static Nuke.Common.IO.PathConstruction;
-using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
 using static Nuke.Common.Tools.DotNet.DotNetTasks;
-using static Nuke.Common.Tools.Xunit.XunitTasks;
-using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
 using static Serilog.Log;
 using MicroCom.CodeGenerator;
 using NuGet.Configuration;
@@ -40,13 +36,13 @@ partial class Build : NukeBuild
     ApiDiffHelper.GlobalDiffInfo? GlobalDiff { get; set; }
 #nullable restore
 
-    [PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net8.0")]
+    [NuGetPackage("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net8.0")]
     Tool ApiCompatTool;
     
-    [PackageExecutable("Microsoft.DotNet.ApiDiff.Tool", "Microsoft.DotNet.ApiDiff.Tool.dll", Framework = "net8.0")]
+    [NuGetPackage("Microsoft.DotNet.ApiDiff.Tool", "Microsoft.DotNet.ApiDiff.Tool.dll", Framework = "net8.0")]
     Tool ApiDiffTool;
 
-    [PackageExecutable("dotnet-ilrepack", "ILRepackTool.dll", Framework = "net8.0")]
+    [NuGetPackage("dotnet-ilrepack", "ILRepackTool.dll", Framework = "net8.0")]
     Tool IlRepackTool;
     
     protected override void OnBuildInitialized()
@@ -96,7 +92,7 @@ partial class Build : NukeBuild
             c.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64"));
         c.AddProperty("PackageVersion", Parameters.Version)
             .SetConfiguration(Parameters.Configuration)
-            .SetVerbosity(DotNetVerbosity.Minimal);
+            .SetVerbosity(DotNetVerbosity.minimal);
         if (Parameters.IsPackingToLocalCache)
             c
                 .AddProperty("ForcePackAvaloniaNative", "True")
@@ -116,12 +112,23 @@ partial class Build : NukeBuild
 
     Target Clean => _ => _.Executes(() =>
     {
-        Parameters.BuildDirs.ForEach(DeleteDirectory);
-        EnsureCleanDirectory(Parameters.ArtifactsDir);
-        EnsureCleanDirectory(Parameters.NugetIntermediateRoot);
-        EnsureCleanDirectory(Parameters.NugetRoot);
-        EnsureCleanDirectory(Parameters.ZipRoot);
-        EnsureCleanDirectory(Parameters.TestResultsRoot);
+        foreach (var buildDir in Parameters.BuildDirs)
+        {
+            Information("Deleting {Directory}", buildDir);
+            buildDir.DeleteDirectory();
+        }
+
+        CleanDirectory(Parameters.ArtifactsDir);
+        CleanDirectory(Parameters.NugetIntermediateRoot);
+        CleanDirectory(Parameters.NugetRoot);
+        CleanDirectory(Parameters.ZipRoot);
+        CleanDirectory(Parameters.TestResultsRoot);
+
+        void CleanDirectory(AbsolutePath path)
+        {
+            Information("Cleaning {Path}", path);
+            path.CreateOrCleanDirectory();
+        }
     });
 
     Target CompileHtmlPreviewer => _ => _
@@ -133,7 +140,7 @@ partial class Build : NukeBuild
 
             NpmTasks.NpmInstall(c => c
                 .SetProcessWorkingDirectory(webappDir)
-                .SetProcessArgumentConfigurator(a => a.Add("--silent")));
+                .SetProcessAdditionalArguments("--silent"));
             NpmTasks.NpmRun(c => c
                 .SetProcessWorkingDirectory(webappDir)
                 .SetCommand("dist"));
@@ -226,7 +233,7 @@ partial class Build : NukeBuild
                 .SetFramework(tfm)
                 .EnableNoBuild()
                 .EnableNoRestore()
-                .When(Parameters.PublishTestResults, _ => _
+                .When(_ => Parameters.PublishTestResults, _ => _
                     .SetLoggers("trx")
                     .SetResultsDirectory(Parameters.TestResultsRoot)));
         }
@@ -241,7 +248,7 @@ partial class Build : NukeBuild
 
             NpmTasks.NpmInstall(c => c
                 .SetProcessWorkingDirectory(webappTestDir)
-                .SetProcessArgumentConfigurator(a => a.Add("--silent")));
+                .SetProcessAdditionalArguments("--silent"));
             NpmTasks.NpmRun(c => c
                 .SetProcessWorkingDirectory(webappTestDir)
                 .SetCommand("test"));
@@ -318,7 +325,7 @@ partial class Build : NukeBuild
                                                        Parameters.Version + ".nupkg",
                                                        IlRepackTool);
             var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
-            EnsureCleanDirectory(Parameters.NugetRoot);
+            Parameters.NugetRoot.CreateOrCleanDirectory();
             if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
                 new NumergeNukeLogger()))
                 throw new Exception("Package merge failed");
@@ -350,6 +357,7 @@ partial class Build : NukeBuild
                 packageDiff => ApiDiffHelper.ValidatePackage(
                     ApiCompatTool,
                     packageDiff,
+                    Parameters.ArtifactsDir / "api-diff" / "assemblies",
                     Parameters.ApiValidationSuppressionFiles,
                     Parameters.UpdateApiValidationSuppression));
         });
@@ -451,7 +459,7 @@ partial class Build : NukeBuild
             var artifactsDirectory = buildTestsDirectory / "artifacts";
             var nugetCacheDirectory = artifactsDirectory / "nuget-cache";
 
-            DeleteDirectory(artifactsDirectory);
+            artifactsDirectory.DeleteDirectory();
             BuildTestsAndVerify("Debug");
             BuildTestsAndVerify("Release");
 
@@ -465,7 +473,7 @@ partial class Build : NukeBuild
                     .SetProperty("NuGetPackageRoot", nugetCacheDirectory)
                     .SetPackageDirectory(nugetCacheDirectory)
                     .SetProjectFile(buildTestsDirectory / "BuildTests.sln")
-                    .SetProcessArgumentConfigurator(arguments => arguments.Add("--nodeReuse:false")));
+                    .SetProcessAdditionalArguments("--nodeReuse:false"));
 
                 // Standard compilation - should have compiled XAML
                 VerifyBuildTestAssembly("bin", "BuildTests");
@@ -494,7 +502,7 @@ partial class Build : NukeBuild
                         .SetPackageDirectory(nugetCacheDirectory)
                         .SetNoBuild(noBuild)
                         .SetProject(buildTestsDirectory / projectName / (projectName + ".csproj"))
-                        .SetProcessArgumentConfigurator(arguments => arguments.Add("--nodeReuse:false")));
+                        .SetProcessAdditionalArguments("--nodeReuse:false"));
 
                 void VerifyBuildTestAssembly(string folder, string projectName)
                     => XamlCompilationVerifier.VerifyAssemblyCompiledXaml(

+ 5 - 6
nukebuild/BuildParameters.cs

@@ -9,7 +9,6 @@ using System.Xml.Linq;
 using Nuke.Common;
 using Nuke.Common.CI.AzurePipelines;
 using Nuke.Common.IO;
-using static Nuke.Common.IO.PathConstruction;
 
 public partial class Build
 {
@@ -68,7 +67,7 @@ public partial class Build
         public AbsolutePath ZipRoot { get; }
         public AbsolutePath TestResultsRoot { get; }
         public string DirSuffix { get; }
-        public List<string> BuildDirs { get; }
+        public List<AbsolutePath> BuildDirs { get; }
         public string FileZipSuffix { get; }
         public AbsolutePath ZipCoreArtifacts { get; }
         public AbsolutePath ZipNuGetArtifacts { get; }
@@ -147,10 +146,10 @@ public partial class Build
             NugetIntermediateRoot = RootDirectory / "build-intermediate" / "nuget";
             ZipRoot = ArtifactsDir / "zip";
             TestResultsRoot = ArtifactsDir / "test-results";
-            BuildDirs = GlobDirectories(RootDirectory, "**/bin")
-                .Concat(GlobDirectories(RootDirectory, "**/obj"))
-                .Where(dir => !dir.Contains("nukebuild"))
-                .Concat(GlobDirectories(RootDirectory, "**/node_modules"))
+            BuildDirs = RootDirectory.GlobDirectories("**/bin")
+                .Concat(RootDirectory.GlobDirectories("**/obj"))
+                .Where(dir => !((string)dir).Contains("nukebuild"))
+                .Concat(RootDirectory.GlobDirectories("**/node_modules"))
                 .ToList();
             DirSuffix = Configuration;
             FileZipSuffix = Version + ".zip";

+ 1 - 1
nukebuild/BuildTasksPatcher.cs

@@ -85,7 +85,7 @@ public class BuildTasksPatcher
                         var cecilMdbAsm = GetAssemblyPath(typeof(Mono.Cecil.Mdb.MdbReaderProvider));
 
                         ilRepackTool.Invoke(
-                            $"/internalize /out:\"{output}\" \"{temp}\" \"{cecilAsm}\" \"{cecilRocksAsm}\" \"{cecilPdbAsm}\" \"{cecilMdbAsm}\"",
+                            $"/internalize /out:\"{output:nq}\" \"{temp:nq}\" \"{cecilAsm:nq}\" \"{cecilRocksAsm:nq}\" \"{cecilPdbAsm:nq}\" \"{cecilMdbAsm:nq}\"",
                             tempDir);
 
                         // 'hurr-durr assembly with the same name is already loaded' prevention

+ 2 - 8
nukebuild/_build.csproj

@@ -15,18 +15,12 @@
 
   <Import Project="..\build\JetBrains.dotMemoryUnit.props" />
   <ItemGroup>
-    <PackageReference Include="Nuke.Common" Version="6.2.1" />
-    <PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
+    <PackageReference Include="Nuke.Common" Version="9.0.4" />
     <PackageReference Include="MicroCom.CodeGenerator" Version="0.11.0" />
     <!-- Keep in sync with Avalonia.Build.Tasks -->
     <PackageReference Include="Mono.Cecil" Version="0.11.5" />
-    <PackageReference Include="Microsoft.Build.Framework" Version="17.3.2" PrivateAssets="All" />
+    <PackageReference Include="Microsoft.Build.Framework" Version="17.14.8" PrivateAssets="All" />
     <PackageReference Include="NuGet.Protocol" Version="6.14.0" />
-    <PackageReference Include="xunit.runner.console" Version="2.4.2">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-    </PackageReference>
-
     <PackageDownload Include="Microsoft.DotNet.ApiCompat.Tool" Version="[10.0.100-preview.7.25380.108]" />
     <PackageDownload Include="Microsoft.DotNet.ApiDiff.Tool" Version="[10.0.100-rc.1.25414.111]" />
     <PackageDownload Include="dotnet-ilrepack" Version="[1.0.0]" />