Build.cs 17 KB

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