Build.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Linq;
  6. using System.Runtime.InteropServices;
  7. using System.Threading.Tasks;
  8. using System.Xml.Linq;
  9. using Nuke.Common;
  10. using Nuke.Common.Tooling;
  11. using Nuke.Common.Tools.DotNet;
  12. using Nuke.Common.Tools.Npm;
  13. using static Nuke.Common.EnvironmentInfo;
  14. using static Nuke.Common.IO.FileSystemTasks;
  15. using static Nuke.Common.IO.PathConstruction;
  16. using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
  17. using static Nuke.Common.Tools.DotNet.DotNetTasks;
  18. using static Nuke.Common.Tools.Xunit.XunitTasks;
  19. using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
  20. using static Serilog.Log;
  21. using MicroCom.CodeGenerator;
  22. using NuGet.Configuration;
  23. using Nuke.Common.CI.AzurePipelines;
  24. using Nuke.Common.IO;
  25. /*
  26. Before editing this file, install support plugin for your IDE,
  27. running and debugging a particular target (optionally without deps) would be way easier
  28. ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support
  29. VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support
  30. */
  31. partial class Build : NukeBuild
  32. {
  33. BuildParameters Parameters { get; set; }
  34. [PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net6.0")]
  35. Tool ApiCompatTool;
  36. [PackageExecutable("Microsoft.DotNet.GenAPI.Tool", "Microsoft.DotNet.GenAPI.Tool.dll", Framework = "net8.0")]
  37. Tool ApiGenTool;
  38. [PackageExecutable("dotnet-ilrepack", "ILRepackTool.dll", Framework = "net8.0")]
  39. Tool IlRepackTool;
  40. protected override void OnBuildInitialized()
  41. {
  42. Parameters = new BuildParameters(this, ScheduledTargets.Contains(BuildToNuGetCache));
  43. Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.",
  44. Parameters.Version,
  45. Parameters.Configuration,
  46. typeof(NukeBuild).Assembly.GetName().Version.ToString());
  47. if (Parameters.IsLocalBuild)
  48. {
  49. Information("Repository Name: " + Parameters.RepositoryName);
  50. Information("Repository Branch: " + Parameters.RepositoryBranch);
  51. }
  52. Information("Configuration: " + Parameters.Configuration);
  53. Information("IsLocalBuild: " + Parameters.IsLocalBuild);
  54. Information("IsRunningOnUnix: " + Parameters.IsRunningOnUnix);
  55. Information("IsRunningOnWindows: " + Parameters.IsRunningOnWindows);
  56. Information("IsRunningOnAzure:" + Parameters.IsRunningOnAzure);
  57. Information("IsPullRequest: " + Parameters.IsPullRequest);
  58. Information("IsMainRepo: " + Parameters.IsMainRepo);
  59. Information("IsMasterBranch: " + Parameters.IsMasterBranch);
  60. Information("IsReleaseBranch: " + Parameters.IsReleaseBranch);
  61. Information("IsReleasable: " + Parameters.IsReleasable);
  62. Information("IsMyGetRelease: " + Parameters.IsMyGetRelease);
  63. Information("IsNuGetRelease: " + Parameters.IsNuGetRelease);
  64. void ExecWait(string preamble, string command, string args)
  65. {
  66. Console.WriteLine(preamble);
  67. Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit();
  68. }
  69. ExecWait("dotnet version:", "dotnet", "--info");
  70. ExecWait("dotnet workloads:", "dotnet", "workload list");
  71. Information("Processor count: " + Environment.ProcessorCount);
  72. Information("Available RAM: " + GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 0x100000 + "MB");
  73. if (Host is AzurePipelines azurePipelines)
  74. azurePipelines.UpdateBuildNumber(Parameters.Version);
  75. }
  76. DotNetConfigHelper ApplySettingCore(DotNetConfigHelper c)
  77. {
  78. if (Parameters.IsRunningOnAzure)
  79. c.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_11_X64"));
  80. c.AddProperty("PackageVersion", Parameters.Version)
  81. .SetConfiguration(Parameters.Configuration)
  82. .SetVerbosity(DotNetVerbosity.Minimal);
  83. if (Parameters.IsPackingToLocalCache)
  84. c
  85. .AddProperty("ForcePackAvaloniaNative", "True")
  86. .AddProperty("SkipObscurePlatforms", "True")
  87. .AddProperty("SkipBuildingSamples", "True")
  88. .AddProperty("SkipBuildingTests", "True");
  89. return c;
  90. }
  91. DotNetBuildSettings ApplySetting(DotNetBuildSettings c, Configure<DotNetBuildSettings> configurator = null) =>
  92. ApplySettingCore(c).Build.Apply(configurator);
  93. DotNetPackSettings ApplySetting(DotNetPackSettings c, Configure<DotNetPackSettings> configurator = null) =>
  94. ApplySettingCore(c).Pack.Apply(configurator);
  95. DotNetTestSettings ApplySetting(DotNetTestSettings c, Configure<DotNetTestSettings> configurator = null) =>
  96. ApplySettingCore(c).Test.Apply(configurator);
  97. Target Clean => _ => _.Executes(() =>
  98. {
  99. Parameters.BuildDirs.ForEach(DeleteDirectory);
  100. EnsureCleanDirectory(Parameters.ArtifactsDir);
  101. EnsureCleanDirectory(Parameters.NugetIntermediateRoot);
  102. EnsureCleanDirectory(Parameters.NugetRoot);
  103. EnsureCleanDirectory(Parameters.ZipRoot);
  104. EnsureCleanDirectory(Parameters.TestResultsRoot);
  105. });
  106. Target CompileHtmlPreviewer => _ => _
  107. .DependsOn(Clean)
  108. .OnlyWhenStatic(() => !Parameters.SkipPreviewer)
  109. .Executes(() =>
  110. {
  111. var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp";
  112. NpmTasks.NpmInstall(c => c
  113. .SetProcessWorkingDirectory(webappDir)
  114. .SetProcessArgumentConfigurator(a => a.Add("--silent")));
  115. NpmTasks.NpmRun(c => c
  116. .SetProcessWorkingDirectory(webappDir)
  117. .SetCommand("dist"));
  118. });
  119. Target CompileNative => _ => _
  120. .DependsOn(Clean)
  121. .DependsOn(GenerateCppHeaders)
  122. .OnlyWhenStatic(() => EnvironmentInfo.IsOsx)
  123. .Executes(() =>
  124. {
  125. var project = $"{RootDirectory}/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/";
  126. var args = $"-project {project} -configuration {Parameters.Configuration} CONFIGURATION_BUILD_DIR={RootDirectory}/Build/Products/Release";
  127. ProcessTasks.StartProcess("xcodebuild", args).AssertZeroExitCode();
  128. });
  129. Target Compile => _ => _
  130. .DependsOn(Clean, CompileNative)
  131. .DependsOn(CompileHtmlPreviewer)
  132. .Executes(() =>
  133. {
  134. DotNetBuild(c => ApplySetting(c)
  135. .SetProjectFile(Parameters.MSBuildSolution)
  136. );
  137. });
  138. Target OutputVersion => _ => _
  139. .Requires(() => VersionOutputDir)
  140. .Executes(() =>
  141. {
  142. var versionFile = Path.Combine(Parameters.VersionOutputDir, "version.txt");
  143. var currentBuildVersion = Parameters.Version;
  144. Console.WriteLine("Version is: " + currentBuildVersion);
  145. File.WriteAllText(versionFile, currentBuildVersion);
  146. var prIdFile = Path.Combine(Parameters.VersionOutputDir, "prId.txt");
  147. var prId = Environment.GetEnvironmentVariable("SYSTEM_PULLREQUEST_PULLREQUESTNUMBER");
  148. Console.WriteLine("PR Number is: " + prId);
  149. File.WriteAllText(prIdFile, prId);
  150. });
  151. void RunCoreTest(string projectName)
  152. {
  153. Information($"Running tests from {projectName}");
  154. var project = RootDirectory.GlobFiles(@$"**\{projectName}.csproj").FirstOrDefault()
  155. ?? throw new InvalidOperationException($"Project {projectName} doesn't exist");
  156. // Nuke and MSBuild tools have build-in helpers to get target frameworks from the project.
  157. // Unfortunately, it gets broken with every second SDK update, so we had to do it manually.
  158. var fileXml = XDocument.Parse(File.ReadAllText(project));
  159. var targetFrameworks = fileXml.Descendants("TargetFrameworks")
  160. .FirstOrDefault()?.Value.Split(';').Select(f => f.Trim());
  161. if (targetFrameworks is null)
  162. {
  163. var targetFramework = fileXml.Descendants("TargetFramework").FirstOrDefault()?.Value;
  164. if (targetFramework is not null)
  165. {
  166. targetFrameworks = new[] { targetFramework };
  167. }
  168. }
  169. if (targetFrameworks is null)
  170. {
  171. throw new InvalidOperationException("No target frameworks were found in the test project");
  172. }
  173. foreach (var fw in targetFrameworks)
  174. {
  175. var tfm = fw;
  176. if (tfm == "$(AvsCurrentTargetFramework)")
  177. {
  178. tfm = "net8.0";
  179. }
  180. if (tfm == "$(AvsLegacyTargetFrameworks)")
  181. {
  182. tfm = "net6.0";
  183. }
  184. if (tfm.StartsWith("net4")
  185. && (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
  186. && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
  187. {
  188. Information($"Skipping {projectName} ({tfm}) tests on *nix - https://github.com/mono/mono/issues/13969");
  189. continue;
  190. }
  191. Information($"Running for {projectName} ({tfm}) ...");
  192. DotNetTest(c => ApplySetting(c)
  193. .SetProjectFile(project)
  194. .SetFramework(tfm)
  195. .EnableNoBuild()
  196. .EnableNoRestore()
  197. .When(Parameters.PublishTestResults, _ => _
  198. .SetLoggers("trx")
  199. .SetResultsDirectory(Parameters.TestResultsRoot)));
  200. }
  201. }
  202. Target RunHtmlPreviewerTests => _ => _
  203. .DependsOn(CompileHtmlPreviewer)
  204. .OnlyWhenStatic(() => !(Parameters.SkipPreviewer || Parameters.SkipTests))
  205. .Executes(() =>
  206. {
  207. var webappTestDir = RootDirectory / "tests" / "Avalonia.DesignerSupport.Tests" / "Remote" / "HtmlTransport" / "webapp";
  208. NpmTasks.NpmInstall(c => c
  209. .SetProcessWorkingDirectory(webappTestDir)
  210. .SetProcessArgumentConfigurator(a => a.Add("--silent")));
  211. NpmTasks.NpmRun(c => c
  212. .SetProcessWorkingDirectory(webappTestDir)
  213. .SetCommand("test"));
  214. });
  215. Target RunCoreLibsTests => _ => _
  216. .OnlyWhenStatic(() => !Parameters.SkipTests)
  217. .DependsOn(Compile)
  218. .Executes(() =>
  219. {
  220. RunCoreTest("Avalonia.Base.UnitTests");
  221. RunCoreTest("Avalonia.Controls.UnitTests");
  222. RunCoreTest("Avalonia.Markup.UnitTests");
  223. RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
  224. RunCoreTest("Avalonia.Skia.UnitTests");
  225. RunCoreTest("Avalonia.ReactiveUI.UnitTests");
  226. RunCoreTest("Avalonia.Headless.NUnit.UnitTests");
  227. RunCoreTest("Avalonia.Headless.XUnit.UnitTests");
  228. });
  229. Target RunRenderTests => _ => _
  230. .OnlyWhenStatic(() => !Parameters.SkipTests)
  231. .DependsOn(Compile)
  232. .Executes(() =>
  233. {
  234. RunCoreTest("Avalonia.Skia.RenderTests");
  235. if (Parameters.IsRunningOnWindows)
  236. RunCoreTest("Avalonia.Direct2D1.RenderTests");
  237. });
  238. Target RunToolsTests => _ => _
  239. .OnlyWhenStatic(() => !Parameters.SkipTests)
  240. .DependsOn(Compile)
  241. .Executes(() =>
  242. {
  243. RunCoreTest("Avalonia.Generators.Tests");
  244. if (Parameters.IsRunningOnWindows)
  245. RunCoreTest("Avalonia.DesignerSupport.Tests");
  246. });
  247. Target RunLeakTests => _ => _
  248. .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
  249. .DependsOn(Compile)
  250. .Executes(() =>
  251. {
  252. void DoMemoryTest()
  253. {
  254. RunCoreTest("Avalonia.LeakTests");
  255. }
  256. ControlFlow.ExecuteWithRetry(DoMemoryTest, delay: TimeSpan.FromMilliseconds(3));
  257. });
  258. Target ZipFiles => _ => _
  259. .After(CreateNugetPackages, Compile, RunCoreLibsTests, Package)
  260. .Executes(() =>
  261. {
  262. var data = Parameters;
  263. Zip(data.ZipNuGetArtifacts, data.NugetRoot);
  264. });
  265. Target CreateIntermediateNugetPackages => _ => _
  266. .DependsOn(Compile)
  267. .After(RunTests)
  268. .Executes(() =>
  269. {
  270. DotNetPack(c => ApplySetting(c).SetProject(Parameters.MSBuildSolution));
  271. });
  272. Target CreateNugetPackages => _ => _
  273. .DependsOn(CreateIntermediateNugetPackages)
  274. .Executes(() =>
  275. {
  276. BuildTasksPatcher.PatchBuildTasksInPackage(Parameters.NugetIntermediateRoot / "Avalonia.Build.Tasks." +
  277. Parameters.Version + ".nupkg",
  278. IlRepackTool);
  279. var config = Numerge.MergeConfiguration.LoadFile(RootDirectory / "nukebuild" / "numerge.config");
  280. EnsureCleanDirectory(Parameters.NugetRoot);
  281. if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
  282. new NumergeNukeLogger()))
  283. throw new Exception("Package merge failed");
  284. RefAssemblyGenerator.GenerateRefAsmsInPackage(
  285. Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.nupkg",
  286. Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.snupkg");
  287. });
  288. Target ValidateApiDiff => _ => _
  289. .DependsOn(CreateNugetPackages)
  290. .Executes(async () =>
  291. {
  292. await Task.WhenAll(
  293. Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.ValidatePackage(
  294. ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
  295. Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
  296. });
  297. Target OutputApiDiff => _ => _
  298. .DependsOn(CreateNugetPackages)
  299. .Executes(async () =>
  300. {
  301. await Task.WhenAll(
  302. Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.GetDiff(
  303. ApiGenTool, RootDirectory / "api" / "diff",
  304. nugetPackage, Parameters.ApiValidationBaseline)));
  305. });
  306. Target RunTests => _ => _
  307. .DependsOn(RunCoreLibsTests)
  308. .DependsOn(RunRenderTests)
  309. .DependsOn(RunToolsTests)
  310. .DependsOn(RunHtmlPreviewerTests)
  311. .DependsOn(RunLeakTests);
  312. Target Package => _ => _
  313. .DependsOn(RunTests)
  314. .DependsOn(CreateNugetPackages)
  315. .DependsOn(ValidateApiDiff);
  316. Target CiAzureLinux => _ => _
  317. .DependsOn(RunTests);
  318. Target CiAzureOSX => _ => _
  319. .DependsOn(Package)
  320. .DependsOn(ZipFiles);
  321. Target CiAzureWindows => _ => _
  322. .DependsOn(Package)
  323. .DependsOn(VerifyXamlCompilation)
  324. .DependsOn(ZipFiles);
  325. Target BuildToNuGetCache => _ => _
  326. .DependsOn(CreateNugetPackages)
  327. .Executes(() =>
  328. {
  329. if (!Parameters.IsPackingToLocalCache)
  330. throw new InvalidOperationException();
  331. var globalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(
  332. Settings.LoadDefaultSettings(RootDirectory));
  333. foreach (var path in Parameters.NugetRoot.GlobFiles("*.nupkg"))
  334. {
  335. using var f = File.Open(path.ToString(), FileMode.Open, FileAccess.Read);
  336. using var zip = new ZipArchive(f, ZipArchiveMode.Read);
  337. var nuspecEntry = zip.Entries.First(e => e.FullName.EndsWith(".nuspec") && e.FullName == e.Name);
  338. var packageId = XDocument.Load(nuspecEntry.Open()).Document.Root
  339. .Elements().First(x => x.Name.LocalName == "metadata")
  340. .Elements().First(x => x.Name.LocalName == "id").Value;
  341. var packagePath = Path.Combine(
  342. globalPackagesFolder,
  343. packageId.ToLowerInvariant(),
  344. BuildParameters.LocalBuildVersion);
  345. if (Directory.Exists(packagePath))
  346. Directory.Delete(packagePath, true);
  347. Directory.CreateDirectory(packagePath);
  348. zip.ExtractToDirectory(packagePath);
  349. File.WriteAllText(Path.Combine(packagePath, ".nupkg.metadata"), @"{
  350. ""version"": 2,
  351. ""contentHash"": ""e900dFK7jHJ2WcprLcgJYQoOMc6ejRTwAAMi0VGOFbSczcF98ZDaqwoQIiyqpAwnja59FSbV+GUUXfc3vaQ2Jg=="",
  352. ""source"": ""https://api.nuget.org/v3/index.json""
  353. }");
  354. }
  355. });
  356. Target GenerateCppHeaders => _ => _.Executes(() =>
  357. {
  358. var file = MicroComCodeGenerator.Parse(
  359. File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
  360. File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
  361. file.GenerateCppHeader());
  362. });
  363. Target VerifyXamlCompilation => _ => _
  364. .DependsOn(CreateNugetPackages)
  365. .Executes(() =>
  366. {
  367. var buildTestsDirectory = RootDirectory / "tests" / "BuildTests";
  368. var artifactsDirectory = buildTestsDirectory / "artifacts";
  369. var nugetCacheDirectory = artifactsDirectory / "nuget-cache";
  370. DeleteDirectory(artifactsDirectory);
  371. BuildTestsAndVerify("Debug");
  372. BuildTestsAndVerify("Release");
  373. void BuildTestsAndVerify(string configuration)
  374. {
  375. var configName = configuration.ToLowerInvariant();
  376. DotNetBuild(settings => settings
  377. .SetConfiguration(configuration)
  378. .SetProperty("AvaloniaVersion", Parameters.Version)
  379. .SetProperty("NuGetPackageRoot", nugetCacheDirectory)
  380. .SetPackageDirectory(nugetCacheDirectory)
  381. .SetProjectFile(buildTestsDirectory / "BuildTests.sln")
  382. .SetProcessArgumentConfigurator(arguments => arguments.Add("--nodeReuse:false")));
  383. // Standard compilation - should have compiled XAML
  384. VerifyBuildTestAssembly("bin", "BuildTests");
  385. VerifyBuildTestAssembly("bin", "BuildTests.Android");
  386. VerifyBuildTestAssembly("bin", "BuildTests.Browser");
  387. VerifyBuildTestAssembly("bin", "BuildTests.Desktop");
  388. VerifyBuildTestAssembly("bin", "BuildTests.FSharp");
  389. VerifyBuildTestAssembly("bin", "BuildTests.iOS");
  390. VerifyBuildTestAssembly("bin", "BuildTests.WpfHybrid");
  391. // Publish previously built project without rebuilding - should have compiled XAML
  392. PublishBuildTestProject("BuildTests.Desktop", noBuild: true);
  393. VerifyBuildTestAssembly("publish", "BuildTests.Desktop");
  394. // Publish NativeAOT build, then run it - should not crash and have the expected output
  395. PublishBuildTestProject("BuildTests.NativeAot");
  396. var exeExtension = OperatingSystem.IsWindows() ? ".exe" : null;
  397. XamlCompilationVerifier.VerifyNativeAot(
  398. GetBuildTestOutputPath("publish", "BuildTests.NativeAot", exeExtension));
  399. void PublishBuildTestProject(string projectName, bool? noBuild = null)
  400. => DotNetPublish(settings => settings
  401. .SetConfiguration(configuration)
  402. .SetProperty("AvaloniaVersion", Parameters.Version)
  403. .SetProperty("NuGetPackageRoot", nugetCacheDirectory)
  404. .SetPackageDirectory(nugetCacheDirectory)
  405. .SetNoBuild(noBuild)
  406. .SetProject(buildTestsDirectory / projectName / (projectName + ".csproj"))
  407. .SetProcessArgumentConfigurator(arguments => arguments.Add("--nodeReuse:false")));
  408. void VerifyBuildTestAssembly(string folder, string projectName)
  409. => XamlCompilationVerifier.VerifyAssemblyCompiledXaml(
  410. GetBuildTestOutputPath(folder, projectName, ".dll"));
  411. AbsolutePath GetBuildTestOutputPath(string folder, string projectName, string extension)
  412. => artifactsDirectory / folder / projectName / configName / (projectName + extension);
  413. }
  414. });
  415. public static int Main() =>
  416. RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
  417. ? Execute<Build>(x => x.Package)
  418. : Execute<Build>(x => x.RunTests);
  419. }
  420. public static class ToolSettingsExtensions
  421. {
  422. public static T Apply<T>(this T settings, Configure<T> configurator)
  423. {
  424. return configurator != null ? configurator(settings) : settings;
  425. }
  426. }