From c409e49005bb00377ad79c32a76c735b06c0c1fc Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Fri, 12 Dec 2025 12:31:48 +1100 Subject: [PATCH 1/4] Continue refactoring the calamari builds # Conflicts: # build/Build.cs --- build/Build.AzureWebAppNetCoreShim.cs | 40 +++ build/Build.Consolidation.cs | 102 ++++++ build/Build.InstallDotNetSdk.cs | 405 +++++++++++----------- build/Build.LocalPackages.cs | 61 ++++ build/Build.NukeBuild.cs | 23 ++ build/Build.PackageCalamariProject.cs | 215 ++++++++++++ build/Build.cs | 480 +------------------------- build/GlobalUsings.cs | 11 + build/KnownPaths.cs | 9 + build/Utilities/Ci.cs | 4 +- 10 files changed, 673 insertions(+), 677 deletions(-) create mode 100644 build/Build.AzureWebAppNetCoreShim.cs create mode 100644 build/Build.Consolidation.cs create mode 100644 build/Build.LocalPackages.cs create mode 100644 build/Build.NukeBuild.cs create mode 100644 build/Build.PackageCalamariProject.cs create mode 100644 build/GlobalUsings.cs diff --git a/build/Build.AzureWebAppNetCoreShim.cs b/build/Build.AzureWebAppNetCoreShim.cs new file mode 100644 index 000000000..be067418e --- /dev/null +++ b/build/Build.AzureWebAppNetCoreShim.cs @@ -0,0 +1,40 @@ +using System; +using Calamari.Build.Utilities; +using JetBrains.Annotations; + +namespace Calamari.Build; + +public partial class Build +{ + [PublicAPI] + Target PublishAzureWebAppNetCoreShim => + _ => _.DependsOn(Clean) + .Executes(() => + { + if (!OperatingSystem.IsWindows()) + { + Log.Warning("Unable to build Calamari.AzureWebApp.NetCoreShim as it's a .NET 4.6.2 application and can only be compiled on Windows"); + return; + } + + var project = Solution.GetProject("Calamari.AzureWebApp.NetCoreShim"); + if (project is null) + { + Log.Error("Failed to find Calamari.AzureWebApp.NetCoreShim project"); + return; + } + + var publishPath = PublishDirectory / project.Name; + + //as this is the only Net 4.6.2 application left, we do a build and restore here + DotNetPublish(s => s + .SetConfiguration(Configuration) + .SetProject(project.Path) + .SetFramework("net462") + .SetVersion(NugetVersion.Value) + .SetInformationalVersion(OctoVersionInfo.Value?.InformationalVersion) + .SetOutput(publishPath)); + + Ci.ZipFolderAndUploadArtifact(publishPath, ArtifactsDirectory / "netcoreshim.zip"); + }); +} \ No newline at end of file diff --git a/build/Build.Consolidation.cs b/build/Build.Consolidation.cs new file mode 100644 index 000000000..53b9b9bba --- /dev/null +++ b/build/Build.Consolidation.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using System.IO.Compression; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using NuGet.Packaging; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.NuGet; +using Octopus.Calamari.ConsolidatedPackage; + +namespace Calamari.Build; + +public partial class Build +{ + static readonly List NuGetPackagesToExcludeFromConsolidation = + [ + "Octopus.Calamari.CloudAccounts", + "Octopus.Calamari.Common", + "Octopus.Calamari.ConsolidateCalamariPackages", + "Octopus.Calamari.ConsolidatedPackage", + "Octopus.Calamari.ConsolidatedPackage.Api" + ]; + + static AbsolutePath ConsolidateCalamariPackagesProject => KnownPaths.SourceDirectory / "Calamari.ConsolidateCalamariPackages.Tests" / "Calamari.ConsolidateCalamariPackages.Tests.csproj"; + static AbsolutePath ConsolidatedPackageDirectory => KnownPaths.ArtifactsDirectory / "consolidated"; + + Target PackageConsolidatedCalamariZip => + d => + d.Executes(() => + { + var artifacts = Directory.GetFiles(ArtifactsDirectory, "*.nupkg") + .Where(a => !NuGetPackagesToExcludeFromConsolidation.Any(a.Contains)); + + var packageReferences = new List(); + foreach (var artifact in artifacts) + { + using var zip = ZipFile.OpenRead(artifact); + var nuspecFileStream = zip.Entries.First(e => e.Name.EndsWith(".nuspec")).Open(); + var nuspecReader = new NuspecReader(nuspecFileStream); + var metadata = nuspecReader.GetMetadata().ToList(); + packageReferences.Add(new BuildPackageReference + { + Name = Regex.Replace(metadata.Where(kvp => kvp.Key == "id").Select(i => i.Value).First(), @"^Octopus\.", ""), + Version = metadata.Where(kvp => kvp.Key == "version").Select(i => i.Value).First(), + PackagePath = artifact + }); + } + + foreach (var flavour in GetCalamariFlavours()) + { + if (Solution.GetProject(flavour) != null) + { + packageReferences.Add(new BuildPackageReference + { + Name = flavour, + Version = NugetVersion.Value, + PackagePath = ArtifactsDirectory / $"{flavour}.zip" + }); + } + } + + Directory.CreateDirectory(ConsolidatedPackageDirectory); + var (result, packageFilename) = new Consolidate(Log.Logger).Execute(ConsolidatedPackageDirectory, packageReferences); + + if (!result) + throw new Exception("Failed to consolidate calamari Packages"); + + ConsolidatedPackagePath = packageFilename; + Log.Information("Created consolidated package zip: {PackageFilename}", packageFilename); + }); + + Target CalamariConsolidationVerification => + d => + d.DependsOn(PackageConsolidatedCalamariZip) + .OnlyWhenDynamic(() => string.IsNullOrEmpty(TargetRuntime), "TargetRuntime is not restricted") + .Executes(() => + { + Environment.SetEnvironmentVariable("CONSOLIDATED_ZIP", ConsolidatedPackagePath); + Environment.SetEnvironmentVariable("EXPECTED_VERSION", NugetVersion.Value); + Environment.SetEnvironmentVariable("IS_WINDOWS", OperatingSystem.IsWindows().ToString()); + + DotNetTest(s => s + .SetProjectFile(ConsolidateCalamariPackagesProject) + .SetConfiguration(Configuration) + .SetProcessArgumentConfigurator(args => + args.Add("--logger:\"console;verbosity=detailed\"") + .Add("--") + .Add("NUnit.ShowInternalProperties=true"))); + }); + + [PublicAPI] + Target PackCalamariConsolidatedNugetPackage => + d => + d.DependsOn(CalamariConsolidationVerification) + .Executes(() => + { + NuGetPack(s => + s.SetTargetPath(BuildDirectory / "Calamari.Consolidated.nuspec") + .SetBasePath(BuildDirectory) + .SetVersion(NugetVersion.Value) + .SetOutputDirectory(ArtifactsDirectory)); + }); +} \ No newline at end of file diff --git a/build/Build.InstallDotNetSdk.cs b/build/Build.InstallDotNetSdk.cs index 203b048d5..9f1a40203 100644 --- a/build/Build.InstallDotNetSdk.cs +++ b/build/Build.InstallDotNetSdk.cs @@ -10,254 +10,249 @@ using System.Threading.Tasks; using Calamari.Build.Utilities; using JetBrains.Annotations; -using Nuke.Common; -using Nuke.Common.IO; using Nuke.Common.Tooling; -using Nuke.Common.Tools.DotNet; -using Serilog; -namespace Calamari.Build +namespace Calamari.Build; + +public partial class Build { - public partial class Build + [Parameter("Specify this if you want to install a particular version of the SDK. Otherwise the InstallDotNetSdk Target will use global.json to determine the .NET SDK Version", Name = "dotnet-version")] + public static string? dotNetVersionParameter; + + /// + /// This target only exists so you can run nuke InstallDotNetSdk outside of another Target. + /// If you have some Target that wants to install the .NET SDK, please call the InstallDotNetSdkIfRequired() + /// method directly. + /// + [PublicAPI] + public Target InstallDotNetSdk => t => t.Executes(async () => await LocateOrInstallDotNetSdk(dotNetVersionParameter)); + + /// + /// Searches for an appropriate dotnet SDK and returns the path to `dotnet.exe` (or unix equivalent). + /// Will install the SDK if it is not found. + /// Note: If an SDK is installed, it will typically go into a temporary folder and not the system-wide one. + /// This is why it's important to use returned path. If you call this method and later just shell out to "dotnet" + /// you may get the wrong thing. + /// + /// + /// This implements the "rollForward" feature that is typically specified in a global.json file. + /// If `specificVersion` is not supplied, and we resolve a global.json that says something like + /// { "version": "6.0.403", "rollForward": "latestFeature" } + /// then it will go search the internet for the latest 6.0.x release and install that instead. + /// + /// If `specificVersion` is supplied and is a 2-part number, this invokes the rollForward behaviour as well, + /// e.g. "6.0" will install the latest 6.0.x release. + /// + /// + /// If set, will find or install a particular version of the .NET SDK e.g. 6.0.417. + /// If not set, will look for an appropriate version by scanning for a global.json file + /// + async Task LocateOrInstallDotNetSdk(string? specificVersion = null) { - [Parameter("Specify this if you want to install a particular version of the SDK. Otherwise the InstallDotNetSdk Target will use global.json to determine the .NET SDK Version", Name = "dotnet-version")] - public static string? dotNetVersionParameter; - - /// - /// This target only exists so you can run nuke InstallDotNetSdk outside of another Target. - /// If you have some Target that wants to install the .NET SDK, please call the InstallDotNetSdkIfRequired() - /// method directly. - /// - [PublicAPI] - public Target InstallDotNetSdk => t => t.Executes(async () => await LocateOrInstallDotNetSdk(dotNetVersionParameter)); - - /// - /// Searches for an appropriate dotnet SDK and returns the path to `dotnet.exe` (or unix equivalent). - /// Will install the SDK if it is not found. - /// Note: If an SDK is installed, it will typically go into a temporary folder and not the system-wide one. - /// This is why it's important to use returned path. If you call this method and later just shell out to "dotnet" - /// you may get the wrong thing. - /// - /// - /// This implements the "rollForward" feature that is typically specified in a global.json file. - /// If `specificVersion` is not supplied, and we resolve a global.json that says something like - /// { "version": "6.0.403", "rollForward": "latestFeature" } - /// then it will go search the internet for the latest 6.0.x release and install that instead. - /// - /// If `specificVersion` is supplied and is a 2-part number, this invokes the rollForward behaviour as well, - /// e.g. "6.0" will install the latest 6.0.x release. - /// - /// - /// If set, will find or install a particular version of the .NET SDK e.g. 6.0.417. - /// If not set, will look for an appropriate version by scanning for a global.json file - /// - async Task LocateOrInstallDotNetSdk(string? specificVersion = null) - { - var httpClient = new Lazy(() => new HttpClient(), isThreadSafe: true); + var httpClient = new Lazy(() => new HttpClient(), isThreadSafe: true); - DotNetDownloadStrategy strategy; - if (specificVersion != null) - { - Log.Information("Request to install .NET SDK using command-line parameter: {DotNetVersion}", specificVersion); - strategy = GlobalJson.DetermineDownloadStrategy(specificVersion, null); - } - else - { - var assemblyLocation = Assembly.GetExecutingAssembly().Location; - var directory = Path.GetDirectoryName(assemblyLocation) ?? throw new InvalidOperationException("can't determine directory for executing assembly"); + DotNetDownloadStrategy strategy; + if (specificVersion != null) + { + Log.Information("Request to install .NET SDK using command-line parameter: {DotNetVersion}", specificVersion); + strategy = GlobalJson.DetermineDownloadStrategy(specificVersion, null); + } + else + { + var assemblyLocation = Assembly.GetExecutingAssembly().Location; + var directory = Path.GetDirectoryName(assemblyLocation) ?? throw new InvalidOperationException("can't determine directory for executing assembly"); - var globalJsonFile = GlobalJson.Find(directory, Log.Logger); - if (globalJsonFile == null) throw new Exception("--dotnet-version parameter was not supplied, and could not find a global.json file to tell us the SDK version to install; aborting"); + var globalJsonFile = GlobalJson.Find(directory, Log.Logger); + if (globalJsonFile == null) throw new Exception("--dotnet-version parameter was not supplied, and could not find a global.json file to tell us the SDK version to install; aborting"); - var parsed = GlobalJson.Parse(globalJsonFile); - Log.Information("Request to install .NET SDK using {GlobalJsonPath} with Version {Version} and RollForward {RollForward}", - globalJsonFile, parsed.Version, parsed.RollForward); + var parsed = GlobalJson.Parse(globalJsonFile); + Log.Information("Request to install .NET SDK using {GlobalJsonPath} with Version {Version} and RollForward {RollForward}", + globalJsonFile, parsed.Version, parsed.RollForward); - strategy = GlobalJson.DetermineDownloadStrategy(parsed.Version, parsed.RollForward); - } + strategy = GlobalJson.DetermineDownloadStrategy(parsed.Version, parsed.RollForward); + } - string targetSdkVersion; - switch (strategy) - { - case DotNetDownloadStrategy.Exact exact: - targetSdkVersion = exact.Version; - break; + string targetSdkVersion; + switch (strategy) + { + case DotNetDownloadStrategy.Exact exact: + targetSdkVersion = exact.Version; + break; - case DotNetDownloadStrategy.LatestInChannel latest: - targetSdkVersion = await DetermineLatestVersion(httpClient, latest.Channel); - Log.Information("Using LatestInChannel strategy; found target version {Version} in channel {Channel}", targetSdkVersion, latest.Channel); - break; + case DotNetDownloadStrategy.LatestInChannel latest: + targetSdkVersion = await DetermineLatestVersion(httpClient, latest.Channel); + Log.Information("Using LatestInChannel strategy; found target version {Version} in channel {Channel}", targetSdkVersion, latest.Channel); + break; - default: - throw new NotSupportedException($"Unhandled download strategy {strategy}"); - } + default: + throw new NotSupportedException($"Unhandled download strategy {strategy}"); + } - if (DotNetSdkIsInstalled(targetSdkVersion)) - { - // assume if it already exists we don't need to chmod - Log.Information(".NET {DotNetVersion} is already installed", targetSdkVersion); - return DotNetTasks.DotNetPath; // DotNetTasks.DotNetPath finds the system-default dotnet in program files or equivalent - } + if (DotNetSdkIsInstalled(targetSdkVersion)) + { + // assume if it already exists we don't need to chmod + Log.Information(".NET {DotNetVersion} is already installed", targetSdkVersion); + return DotNetTasks.DotNetPath; // DotNetTasks.DotNetPath finds the system-default dotnet in program files or equivalent + } - var temporaryDotNetDirectory = TemporaryDirectory / $"dotnet-{targetSdkVersion}"; - if (Directory.Exists(temporaryDotNetDirectory)) - { - Log.Information(".NET {DotNetVersion} is not known to `dotnet --list-sdks` but {temporaryDotNetDirectory} exists, assuming it is there", - targetSdkVersion, temporaryDotNetDirectory); + var temporaryDotNetDirectory = TemporaryDirectory / $"dotnet-{targetSdkVersion}"; + if (Directory.Exists(temporaryDotNetDirectory)) + { + Log.Information(".NET {DotNetVersion} is not known to `dotnet --list-sdks` but {temporaryDotNetDirectory} exists, assuming it is there", + targetSdkVersion, temporaryDotNetDirectory); - // as above, assume if it already exists we don't need to chmod - return temporaryDotNetDirectory / (OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); - } + // as above, assume if it already exists we don't need to chmod + return temporaryDotNetDirectory / (OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + } - Log.Information("{DotNetVersion} is not installed. Downloading the .NET sdk zip file", targetSdkVersion); + Log.Information("{DotNetVersion} is not installed. Downloading the .NET sdk zip file", targetSdkVersion); - var platform = OperatingSystem.IsWindows() - ? "win" - : OperatingSystem.IsMacOS() - ? "osx" - : "linux"; // there are distro-specific packages e.g. debian, they aren't used anymore + var platform = OperatingSystem.IsWindows() + ? "win" + : OperatingSystem.IsMacOS() + ? "osx" + : "linux"; // there are distro-specific packages e.g. debian, they aren't used anymore - var temporaryArchivePath = await DownloadDotNetSdk(httpClient, targetSdkVersion, platform, ResolveDotNetArchitectureString(RuntimeInformation.OSArchitecture)); - try + var temporaryArchivePath = await DownloadDotNetSdk(httpClient, targetSdkVersion, platform, ResolveDotNetArchitectureString(RuntimeInformation.OSArchitecture)); + try + { + Log.Information("Extracting {DotNetVersion} into {temporaryDotNetDirectory}", targetSdkVersion, temporaryDotNetDirectory); + if (OperatingSystem.IsWindows()) { - Log.Information("Extracting {DotNetVersion} into {temporaryDotNetDirectory}", targetSdkVersion, temporaryDotNetDirectory); - if (OperatingSystem.IsWindows()) - { - Directory.CreateDirectory(temporaryDotNetDirectory); - ZipFile.ExtractToDirectory(temporaryArchivePath, temporaryDotNetDirectory, overwriteFiles: true); - return temporaryDotNetDirectory / "dotnet.exe"; - } - else - { - Directory.CreateDirectory(temporaryDotNetDirectory); - await using (var gzipStream = new GZipStream(File.OpenRead(temporaryArchivePath), CompressionMode.Decompress)) - { - await TarFile.ExtractToDirectoryAsync(gzipStream, temporaryDotNetDirectory, overwriteFiles: true); - } - - var executablePath = temporaryDotNetDirectory / "dotnet"; - // On unix we need to chmod +x the executable so later tasks can run it - executablePath.SetExecutable(); - return executablePath; - } + Directory.CreateDirectory(temporaryDotNetDirectory); + ZipFile.ExtractToDirectory(temporaryArchivePath, temporaryDotNetDirectory, overwriteFiles: true); + return temporaryDotNetDirectory / "dotnet.exe"; } - finally + else { - try - { - File.Delete(temporaryArchivePath); - } - catch + Directory.CreateDirectory(temporaryDotNetDirectory); + await using (var gzipStream = new GZipStream(File.OpenRead(temporaryArchivePath), CompressionMode.Decompress)) { - // Deliberate empty catch-block; we can't do much if we can't delete the temp file. Not a big deal + await TarFile.ExtractToDirectoryAsync(gzipStream, temporaryDotNetDirectory, overwriteFiles: true); } + + var executablePath = temporaryDotNetDirectory / "dotnet"; + // On unix we need to chmod +x the executable so later tasks can run it + executablePath.SetExecutable(); + return executablePath; } } - - static bool DotNetSdkIsInstalled(string version) + finally { - // Format: - // 6.0.414 [/usr/local/share/dotnet/sdk] - var versions = DotNetTasks.DotNet("--list-sdks").Select(o => o.Text.Split(" ").First()); - return versions.Contains(version); + try + { + File.Delete(temporaryArchivePath); + } + catch + { + // Deliberate empty catch-block; we can't do much if we can't delete the temp file. Not a big deal + } } + } - // possible values for architecture are [amd64, x64, x86, arm64, arm] - // refer to Get-Machine-Architecture in build.ps1 - static string ResolveDotNetArchitectureString(Architecture architecture) => architecture switch - { - Architecture.Arm or Architecture.Armv6 => "arm", - Architecture.Arm64 or Architecture.LoongArch64 => "arm64", - Architecture.X86 => "x86", - Architecture.X64 => "x64", - // explicitly reference known architectures so the compiler can tell us about new unknown ones when they are added. - Architecture.Wasm or Architecture.S390x => throw new NotSupportedException($"Unsupported OS architecture {architecture}"), - _ => throw new NotSupportedException($"Unknown OS architecture {architecture}"), - }; - - static async Task DetermineLatestVersion(Lazy httpClient, string requestedFuzzyVersion) - { - return await PerformOperationWithFeedAndRetries(async feed => - { - var downloadUrl = $"{feed}/Sdk/{requestedFuzzyVersion}/latest.version"; + static bool DotNetSdkIsInstalled(string version) + { + // Format: + // 6.0.414 [/usr/local/share/dotnet/sdk] + var versions = DotNetTasks.DotNet("--list-sdks").Select(o => o.Text.Split(" ").First()); + return versions.Contains(version); + } - Log.Information($"Attempting download of {downloadUrl}"); - var response = await httpClient.Value.GetAsync(downloadUrl); - response.EnsureSuccessStatusCode(); + // possible values for architecture are [amd64, x64, x86, arm64, arm] + // refer to Get-Machine-Architecture in build.ps1 + static string ResolveDotNetArchitectureString(Architecture architecture) => architecture switch + { + Architecture.Arm or Architecture.Armv6 => "arm", + Architecture.Arm64 or Architecture.LoongArch64 => "arm64", + Architecture.X86 => "x86", + Architecture.X64 => "x64", + // explicitly reference known architectures so the compiler can tell us about new unknown ones when they are added. + Architecture.Wasm or Architecture.S390x => throw new NotSupportedException($"Unsupported OS architecture {architecture}"), + _ => throw new NotSupportedException($"Unknown OS architecture {architecture}"), + }; + + static async Task DetermineLatestVersion(Lazy httpClient, string requestedFuzzyVersion) + { + return await PerformOperationWithFeedAndRetries(async feed => + { + var downloadUrl = $"{feed}/Sdk/{requestedFuzzyVersion}/latest.version"; - var versionString = await response.Content.ReadAsStringAsync(); + Log.Information($"Attempting download of {downloadUrl}"); + var response = await httpClient.Value.GetAsync(downloadUrl); + response.EnsureSuccessStatusCode(); - // sanity check, we should get an exact version number such as 6.0.417 - if (versionString.Count(c => c == '.') != 2) throw new Exception($"Unexpected response {versionString} from {downloadUrl}, expecting a version number such as 8.0.100"); + var versionString = await response.Content.ReadAsStringAsync(); - return versionString.Trim(); - }); - } + // sanity check, we should get an exact version number such as 6.0.417 + if (versionString.Count(c => c == '.') != 2) throw new Exception($"Unexpected response {versionString} from {downloadUrl}, expecting a version number such as 8.0.100"); - // This function is ported from https://dot.net/v1/dotnet-install.ps1 (and .sh variant for unix) - // You're supposed to fetch and execute the script so Microsoft can keep it up to date, - // but powershell downloading and executing scripts is slow and painful, particularly on older windows like 2016 where TLS1.2 isn't enabled. - // So we rather just do it ourselves. - // NOTE: Whenever we do a major .NET migration we should review Microsoft's dotnet-install.ps1 script - // and update our code if they've changed any of the download links/etc. Last checked on the release of .NET 8 - // - // returns a path to a temporary file containing the zip or tar.gz that has been downloaded - static async Task DownloadDotNetSdk(Lazy httpClient, string requestedVersion, string platform, string architecture) - { - return await PerformOperationWithFeedAndRetries(async feed => - { - var fileExtension = platform == "win" ? "zip" : "tar.gz"; - // Note: Version must be an exact specific version like 6.0.401. + return versionString.Trim(); + }); + } - // refer Get-Download-Link in dotnet-install.ps1 - // Note this URL works for full releases of .NET but isn't quite right for release candidates; the two copies of - // `requestedVersion` differ when fetching an RC. Next time we want to download an RC SDK we'll need to fix this - var downloadUrl = $"{feed}/Sdk/{requestedVersion}/dotnet-sdk-{requestedVersion}-{platform}-{architecture}.{fileExtension}"; + // This function is ported from https://dot.net/v1/dotnet-install.ps1 (and .sh variant for unix) + // You're supposed to fetch and execute the script so Microsoft can keep it up to date, + // but powershell downloading and executing scripts is slow and painful, particularly on older windows like 2016 where TLS1.2 isn't enabled. + // So we rather just do it ourselves. + // NOTE: Whenever we do a major .NET migration we should review Microsoft's dotnet-install.ps1 script + // and update our code if they've changed any of the download links/etc. Last checked on the release of .NET 8 + // + // returns a path to a temporary file containing the zip or tar.gz that has been downloaded + static async Task DownloadDotNetSdk(Lazy httpClient, string requestedVersion, string platform, string architecture) + { + return await PerformOperationWithFeedAndRetries(async feed => + { + var fileExtension = platform == "win" ? "zip" : "tar.gz"; + // Note: Version must be an exact specific version like 6.0.401. - Log.Information($"Attempting download of {downloadUrl}"); - var targetFile = Path.GetTempFileName(); - await using var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.ReadWrite); + // refer Get-Download-Link in dotnet-install.ps1 + // Note this URL works for full releases of .NET but isn't quite right for release candidates; the two copies of + // `requestedVersion` differ when fetching an RC. Next time we want to download an RC SDK we'll need to fix this + var downloadUrl = $"{feed}/Sdk/{requestedVersion}/dotnet-sdk-{requestedVersion}-{platform}-{architecture}.{fileExtension}"; - var response = await httpClient.Value.GetAsync(downloadUrl); - response.EnsureSuccessStatusCode(); + Log.Information($"Attempting download of {downloadUrl}"); + var targetFile = Path.GetTempFileName(); + await using var fileStream = new FileStream(targetFile, FileMode.Create, FileAccess.ReadWrite); - await response.Content.CopyToAsync(fileStream); + var response = await httpClient.Value.GetAsync(downloadUrl); + response.EnsureSuccessStatusCode(); - return targetFile; - }); - } + await response.Content.CopyToAsync(fileStream); - // feeds are tried in this order - static readonly string[] Feeds = - { - // CDN's - "https://builds.dotnet.microsoft.com/dotnet", - "https://ci.dot.net/public", + return targetFile; + }); + } + + // feeds are tried in this order + static readonly string[] Feeds = + { + // CDN's + "https://builds.dotnet.microsoft.com/dotnet", + "https://ci.dot.net/public", - // direct - "https://dotnetcli.blob.core.windows.net/dotnet", - "https://dotnetbuilds.blob.core.windows.net/public" - }; + // direct + "https://dotnetcli.blob.core.windows.net/dotnet", + "https://dotnetbuilds.blob.core.windows.net/public" + }; - static async Task PerformOperationWithFeedAndRetries(Func> performOperation) + static async Task PerformOperationWithFeedAndRetries(Func> performOperation) + { + ExceptionDispatchInfo? lastException = null; + foreach (var feed in Feeds.Concat(Feeds)) // get a retry on each feed with sneaky concat { - ExceptionDispatchInfo? lastException = null; - foreach (var feed in Feeds.Concat(Feeds)) // get a retry on each feed with sneaky concat + try { - try - { - return await performOperation(feed); - } - catch (Exception ex) - { - lastException = ExceptionDispatchInfo.Capture(ex); - Log.Warning(ex, $"Exception occurred using feed {feed}"); - // carry on, let the foreach loop roll over to the next mirror - } + return await performOperation(feed); + } + catch (Exception ex) + { + lastException = ExceptionDispatchInfo.Capture(ex); + Log.Warning(ex, $"Exception occurred using feed {feed}"); + // carry on, let the foreach loop roll over to the next mirror } - - lastException?.Throw(); - throw new Exception("PerformOperationWithFeedAndRetries did not return a result, but caught no exception? Are there any feeds?"); // shouldn't happen; last resort } + + lastException?.Throw(); + throw new Exception("PerformOperationWithFeedAndRetries did not return a result, but caught no exception? Are there any feeds?"); // shouldn't happen; last resort } } \ No newline at end of file diff --git a/build/Build.LocalPackages.cs b/build/Build.LocalPackages.cs new file mode 100644 index 000000000..950ee8639 --- /dev/null +++ b/build/Build.LocalPackages.cs @@ -0,0 +1,61 @@ +using System.Text.RegularExpressions; + +namespace Calamari.Build; + +public partial class Build +{ + + Target CopyToLocalPackages => + d => + d.Requires(() => IsLocalBuild) + .DependsOn(PublishCalamariProjects) + .Executes(() => + { + Directory.CreateDirectory(LocalPackagesDirectory); + foreach (AbsolutePath file in Directory.GetFiles(ArtifactsDirectory, "Octopus.Calamari.*.nupkg")) + { + var target = LocalPackagesDirectory / Path.GetFileName(file); + file.Copy(target, ExistsPolicy.FileOverwrite); + } + }); + + Target UpdateCalamariVersionOnOctopusServer => + d => + d.OnlyWhenStatic(() => SetOctopusServerVersion) + .Requires(() => IsLocalBuild) + .DependsOn(CopyToLocalPackages) + .Executes(() => + { + var serverProjectFile = RootDirectory / ".." / "OctopusDeploy" / "source" / "Octopus.Server" / "Octopus.Server.csproj"; + if (File.Exists(serverProjectFile)) + { + Log.Information("Setting Calamari version in Octopus Server " + + "project {ServerProjectFile} to {NugetVersion}", + serverProjectFile, NugetVersion.Value); + + SetOctopusServerCalamariVersion(serverProjectFile); + } + else + { + Log.Warning("Could not set Calamari version in Octopus Server project " + + "{ServerProjectFile} to {NugetVersion} as could not find " + + "project file", + serverProjectFile, NugetVersion.Value); + } + }); + + Target BuildLocal => d => + d.Requires(() => IsLocalBuild) + .DependsOn(PublishCalamariProjects) + .DependsOn(PackCalamariConsolidatedNugetPackage) + .DependsOn(UpdateCalamariVersionOnOctopusServer); + + // Sets the Octopus.Server.csproj Calamari.Consolidated package version + void SetOctopusServerCalamariVersion(string projectFile) + { + var text = File.ReadAllText(projectFile); + text = Regex.Replace(text, @"([\S])+", + $"{NugetVersion.Value}"); + File.WriteAllText(projectFile, text); + } +} \ No newline at end of file diff --git a/build/Build.NukeBuild.cs b/build/Build.NukeBuild.cs new file mode 100644 index 000000000..8e544f588 --- /dev/null +++ b/build/Build.NukeBuild.cs @@ -0,0 +1,23 @@ +using Calamari.Build.Utilities; + +namespace Calamari.Build; + +public partial class Build +{ + Target PublishNukeBuild => + d => + d.Executes(() => + { + const string runtime = "win-x64"; + var nukeBuildOutputDirectory = BuildDirectory / "outputs" / runtime / "nukebuild"; + nukeBuildOutputDirectory.CreateOrCleanDirectory(); + + DotNetPublish(p => p + .SetProject(RootDirectory / "build" / "_build.csproj") + .SetConfiguration(Configuration) + .SetRuntime(runtime) + .EnableSelfContained()); + + Ci.ZipFolderAndUploadArtifact(nukeBuildOutputDirectory, ArtifactsDirectory / $"nukebuild.{runtime}.zip"); + }); +} \ No newline at end of file diff --git a/build/Build.PackageCalamariProject.cs b/build/Build.PackageCalamariProject.cs new file mode 100644 index 000000000..af31f5202 --- /dev/null +++ b/build/Build.PackageCalamariProject.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Nuke.Common.ProjectModel; +using Octopus.Calamari.ConsolidatedPackage; + +namespace Calamari.Build; + +public partial class Build +{ + // By default, all projects are built unless the parameter is set specifically + // Although all libraries/flavours now support .NET Core, ServiceFabric can currently only be built on Windows devices + // This is here purely to make the local build experience on non-Windows devices (with testing) workable + [Parameter(Name = "ProjectToBuild")] readonly string[] ProjectsToBuild = BuildableCalamariProjects.GetCalamariProjectsToBuild(OperatingSystem.IsWindows()); + + + + List PackagesToPublish = new(); + List CalamariProjects = new(); + + Target GetCalamariFlavourProjectsToPublish => + d => + d.DependsOn(RestoreSolution) + .Executes(() => + { + // Calamari.Scripting is a library that other calamari flavours depend on; not a flavour on its own right. + // Unlike other *Calamari* tests, we would still want to produce Calamari.Scripting.Zip and its tests, like its flavours. + string[] projectNames = [..ProjectsToBuild, "Calamari.Scripting"]; + + //its assumed each project has a corresponding test project + var testProjectNames = projectNames.Select(p => $"{p}.Tests"); + + var allProjectNames = projectNames.Concat(testProjectNames).ToHashSet(); + + var calamariProjects = Solution.Projects.Where(project => allProjectNames.Contains(project.Name)).ToList(); + + CalamariProjects = calamariProjects; + + //all packages are cross-platform + var packages = calamariProjects + .SelectMany(project => GetRuntimeIdentifiers(project) + .Select(rid => + { + //we are making the bold assumption all projects only have a single target framework and that if they don't, it's .NET 8.0 + var framework = project.GetTargetFrameworks()?.Single() ?? Frameworks.Net80; + return new CalamariPackageMetadata(project, framework, rid); + })) + .ToList(); + + PackagesToPublish = packages; + + Log.Information("Packages to publish:"); + foreach (var calamariPackageMetadata in packages) + { + Log.Information("Project: {Project}, Framework: {Framework}, Arch: {Architecture}", calamariPackageMetadata.Project.Name, calamariPackageMetadata.Framework, calamariPackageMetadata.Architecture); + } + }); + + Target BuildCalamariProjects => + d => + d.DependsOn(GetCalamariFlavourProjectsToPublish) + .Executes(async () => + { + var globalSemaphore = new SemaphoreSlim(1); + var semaphores = new ConcurrentDictionary(); + + var buildTasks = PackagesToPublish.Select(async calamariPackageMetadata => + { + var projectName = calamariPackageMetadata.Project.Name; + var projectSemaphore = semaphores.GetOrAdd(projectName, _ => new SemaphoreSlim(1, 1)); + var architectureSemaphore = semaphores.GetOrAdd(calamariPackageMetadata.Architecture, _ => new SemaphoreSlim(1, 1)); + + await globalSemaphore.WaitAsync(); + await projectSemaphore.WaitAsync(); + await architectureSemaphore.WaitAsync(); + try + { + Log.Information($"Building {calamariPackageMetadata.Project.Name} for framework '{calamariPackageMetadata.Framework}' and arch '{calamariPackageMetadata.Architecture}'"); + + await Task.Run(() => DotNetBuild(s => + s.SetProjectFile(calamariPackageMetadata.Project) + .SetConfiguration(Configuration) + .SetFramework(calamariPackageMetadata.Framework) + .SetRuntime(calamariPackageMetadata.Architecture) + .EnableSelfContained())); + } + finally + { + projectSemaphore.Release(); + architectureSemaphore.Release(); + globalSemaphore.Release(); + } + }); + + await Task.WhenAll(buildTasks); + }); + + Target PublishCalamariProjects => + d => + d.DependsOn(BuildCalamariProjects) + .Executes(async () => + { + var semaphore = new SemaphoreSlim(4); + var outputPaths = new ConcurrentBag(); + + Log.Information("Publishing projects..."); + var publishTasks = PackagesToPublish.Select(async package => + { + await semaphore.WaitAsync(); + try + { + var outputPath = await PublishPackageAsync(package); + outputPaths.Add(outputPath); + } + finally + { + semaphore.Release(); + } + }); + + await Task.WhenAll(publishTasks); + + // Sign and compress tasks + Log.Information("Signing published binaries..."); + var signTasks = outputPaths + .Where(output => output != null && !output.ToString().Contains("Tests")) + .Select(output => Task.Run(() => SignDirectory(output!))) + .ToList(); + + await Task.WhenAll(signTasks); + + Log.Information("Compressing published projects..."); + var compressTasks = CalamariProjects.Select(async proj => await CompressCalamariProject(proj)); + await Task.WhenAll(compressTasks); + }); + + async Task PublishPackageAsync(CalamariPackageMetadata calamariPackageMetadata) + { + Log.Information("Publishing {ProjectName} for framework '{Framework}' and arch '{Architecture}'", calamariPackageMetadata.Project.Name, calamariPackageMetadata.Framework, calamariPackageMetadata.Architecture); + + var project = calamariPackageMetadata.Project; + var outputDirectory = PublishDirectory / project.Name / calamariPackageMetadata.Architecture; + + await Task.Run(() => + DotNetPublish(s => + s.SetConfiguration(Configuration) + .SetProject(project) + .SetFramework(calamariPackageMetadata.Framework) + .SetRuntime(calamariPackageMetadata.Architecture) + .EnableNoBuild() + .EnableNoRestore() + .SetSelfContained(OperatingSystem.IsWindows() || !IsLocalBuild) // This is here purely to make the local build experience on non-Windows devices workable - Publish breaks on non-Windows platforms with SelfContained = true + .SetOutput(outputDirectory))); + + File.Copy(RootDirectory / "global.json", outputDirectory / "global.json"); + + return outputDirectory; + } + + async Task CompressCalamariProject(Project project) + { + Log.Information($"Compressing Calamari flavour {PublishDirectory}/{project.Name}"); + var compressionSource = PublishDirectory / project.Name; + if (!Directory.Exists(compressionSource)) + { + Log.Information($"Skipping compression for {project.Name} since nothing was built"); + return; + } + + await Task.Run(() => compressionSource.CompressTo($"{ArtifactsDirectory / project.Name}.zip")); + } + + IReadOnlyCollection GetRuntimeIdentifiers(Project? project) + { + if (project is null) + return []; + + var runtimes = project.GetRuntimeIdentifiers(); + + if (!string.IsNullOrWhiteSpace(TargetRuntime)) + runtimes = runtimes?.Where(x => x == TargetRuntime).ToList().AsReadOnly(); + + return runtimes ?? []; + } + + HashSet ListAllRuntimeIdentifiersInSolution() + { + var allRuntimes = Solution.AllProjects + .SelectMany(p => p.GetRuntimeIdentifiers() ?? []) + .Distinct() + .Where(rid => rid != "win7-x86") //I have no idea where this is coming from, but let's ignore it. My theory is it's coming from the netstandard libs + .ToHashSet(); + + if (!string.IsNullOrWhiteSpace(TargetRuntime)) + allRuntimes = allRuntimes.Where(x => x == TargetRuntime).ToHashSet(); + + return allRuntimes; + } + + void SignDirectory(string directory) + { + if (!WillSignBinaries) + return; + + Log.Information("Signing directory: {Directory} and sub-directories", directory); + var binariesFolders = Directory.GetDirectories(directory, "*", new EnumerationOptions { RecurseSubdirectories = true }); + foreach (var subDirectory in binariesFolders.Append(directory)) + { + Signing.SignAndTimestampBinaries(subDirectory, AzureKeyVaultUrl, AzureKeyVaultAppId, + AzureKeyVaultAppSecret, AzureKeyVaultTenantId, AzureKeyVaultCertificateName, + SigningCertificatePath, SigningCertificatePassword); + } + } +} \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs index 4e2ac3527..a08797f01 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -1,28 +1,12 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; -using Calamari.Build.Utilities; -using NuGet.Packaging; -using Nuke.Common; -using Nuke.Common.CI.TeamCity; -using Nuke.Common.IO; +using JetBrains.Annotations; using Nuke.Common.ProjectModel; using Nuke.Common.Tooling; -using Nuke.Common.Tools.DotNet; -using Nuke.Common.Tools.NuGet; using Nuke.Common.Tools.OctoVersion; using Nuke.Common.Utilities.Collections; -using Octopus.Calamari.ConsolidatedPackage; -using Serilog; -using static Nuke.Common.Tools.DotNet.DotNetTasks; -using static Nuke.Common.Tools.Git.GitTasks; -using static Nuke.Common.Tools.NuGet.NuGetTasks; namespace Calamari.Build; @@ -30,7 +14,7 @@ partial class Build : NukeBuild { [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; - [Required] readonly Solution Solution = SolutionModelTasks.ParseSolution(SourceDirectory / "Calamari.sln"); + [Required] readonly Solution Solution = SolutionModelTasks.ParseSolution(KnownPaths.SourceDirectory / "Calamari.sln"); [Parameter] readonly DotNetVerbosity BuildVerbosity = DotNetVerbosity.minimal; @@ -66,17 +50,10 @@ partial class Build : NukeBuild [Parameter($"The name of the current git branch. OctoVersion will use this to calculate the version number. This can be set via the environment variable {CiBranchNameEnvVariable}.", Name = CiBranchNameEnvVariable)] string? BranchName { get; set; } - - [Parameter] readonly string? ProjectToBuild; - + //this is instantiated in the constructor readonly Lazy OctoVersionInfo; - static readonly List NuGetPackagesToExcludeFromConsolidation = new() { "Octopus.Calamari.CloudAccounts", "Octopus.Calamari.Common", "Octopus.Calamari.ConsolidateCalamariPackages", "Octopus.Calamari.ConsolidatedPackage", "Octopus.Calamari.ConsolidatedPackage.Api" }; - - List PackagesToPublish = new(); - List CalamariProjects = new(); - string ConsolidatedPackagePath = ""; public Build() @@ -98,14 +75,12 @@ public Build() // perform actions such as GetRuntimeIdentifiers() on projects. ProjectModelTasks.Initialize(); } - - static AbsolutePath SourceDirectory => RootDirectory / "source"; + static AbsolutePath BuildDirectory => RootDirectory / "build"; static AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; static AbsolutePath PublishDirectory => RootDirectory / "publish"; static AbsolutePath LocalPackagesDirectory => RootDirectory / ".." / "LocalPackages"; - static AbsolutePath ConsolidateCalamariPackagesProject => SourceDirectory / "Calamari.ConsolidateCalamariPackages.Tests" / "Calamari.ConsolidateCalamariPackages.Tests.csproj"; - static AbsolutePath ConsolidatedPackageDirectory => ArtifactsDirectory / "consolidated"; + Lazy NugetVersion { get; } @@ -139,9 +114,10 @@ public Build() d.DependsOn(CheckForbiddenWords) .Executes(() => { - SourceDirectory.GlobDirectories("**/bin", "**/obj", "**/TestResults").ForEach(d => d.DeleteDirectory()); - ArtifactsDirectory.CreateOrCleanDirectory(); - PublishDirectory.CreateOrCleanDirectory(); + KnownPaths.SourceDirectory.GlobDirectories("**/bin", "**/obj", "**/TestResults").ForEach(d => d.DeleteDirectory()); + + KnownPaths.ArtifactsDirectory.CreateOrCleanDirectory(); + KnownPaths.PublishDirectory.CreateOrCleanDirectory(); }); Target RestoreSolution => @@ -159,415 +135,15 @@ public Build() DotNetRestore(s => s.SetProjectFile(Solution).SetRuntime(runtimeId)); } }); - - Target GetCalamariFlavourProjectsToPublish => - d => - d.DependsOn(RestoreSolution) - .Executes(() => - { - var projectNames = GetCalamariFlavours(); - - // Calamari.Scripting is a library that other calamari flavours depend on; not a flavour on its own right. - // Unlike other *Calamari* tests, we would still want to produce Calamari.Scripting.Zip and its tests, like its flavours. - //We put this at the front of the list so they build in the correct order? - projectNames = [..projectNames, "Calamari.Scripting"]; - - //its assumed each project has a corresponding test project - var testProjectNames = projectNames.Select(f => $"{f}.Tests"); - var allProjectNames = projectNames.Concat(testProjectNames).ToHashSet(); - - var calamariProjects = Solution.Projects - .Where(project => allProjectNames.Contains(project.Name)) - .ToList(); - - //if this is not - if (!string.IsNullOrEmpty(ProjectToBuild)) - { - calamariProjects = calamariProjects.Where(p => p.Name == ProjectToBuild || p.Name == $"{ProjectToBuild}.Tests").ToList(); - } - - CalamariProjects = calamariProjects; - - //all packages are cross-platform - var packages = calamariProjects - .SelectMany(project => GetRuntimeIdentifiers(project) - .Select(rid => - { - //we are making the bold assumption all projects only have a single target framework - var framework = project.GetTargetFrameworks()?.Single() ?? Frameworks.Net80; - return new CalamariPackageMetadata(project, framework, rid); - })) - .ToList(); - - PackagesToPublish = packages; - - Log.Information("Packages to publish:"); - foreach (var calamariPackageMetadata in packages) - { - Log.Information("Project: {Project}, Framework: {Framework}, Arch: {Architecture}", calamariPackageMetadata.Project.Name, calamariPackageMetadata.Framework, calamariPackageMetadata.Architecture); - } - }); - - Target BuildCalamariProjects => - d => - d.DependsOn(GetCalamariFlavourProjectsToPublish) - .DependsOn(PublishAzureWebAppNetCoreShim) - .Executes(async () => - { - var globalSemaphore = new SemaphoreSlim(1); - var semaphores = new ConcurrentDictionary(); - - var buildTasks = PackagesToPublish.Select(async calamariPackageMetadata => - { - var projectName = calamariPackageMetadata.Project.Name; - var projectSemaphore = semaphores.GetOrAdd(projectName, _ => new SemaphoreSlim(1, 1)); - var architectureSemaphore = semaphores.GetOrAdd(calamariPackageMetadata.Architecture, _ => new SemaphoreSlim(1, 1)); - - await globalSemaphore.WaitAsync(); - await projectSemaphore.WaitAsync(); - await architectureSemaphore.WaitAsync(); - try - { - Log.Information($"Building {calamariPackageMetadata.Project.Name} for framework '{calamariPackageMetadata.Framework}' and arch '{calamariPackageMetadata.Architecture}'"); - - await Task.Run(() => DotNetBuild(s => - s.SetProjectFile(calamariPackageMetadata.Project) - .SetConfiguration(Configuration) - .SetFramework(calamariPackageMetadata.Framework) - .SetRuntime(calamariPackageMetadata.Architecture) - .EnableSelfContained())); - } - finally - { - projectSemaphore.Release(); - architectureSemaphore.Release(); - globalSemaphore.Release(); - } - }); - - await Task.WhenAll(buildTasks); - }); - - Target PublishCalamariProjects => - d => - d.DependsOn(BuildCalamariProjects) - .Executes(async () => - { - var semaphore = new SemaphoreSlim(4); - var outputPaths = new ConcurrentBag(); - - Log.Information("Publishing projects..."); - var publishTasks = PackagesToPublish.Select(async package => - { - await semaphore.WaitAsync(); - try - { - var outputPath = await PublishPackageAsync(package); - outputPaths.Add(outputPath); - } - finally - { - semaphore.Release(); - } - }); - - await Task.WhenAll(publishTasks); - - // Sign and compress tasks - Log.Information("Signing published binaries..."); - var signTasks = outputPaths - .Where(output => output != null && !output.ToString().Contains("Tests")) - .Select(output => Task.Run(() => SignDirectory(output!))) - .ToList(); - - await Task.WhenAll(signTasks); - - Log.Information("Compressing published projects..."); - var compressTasks = CalamariProjects.Select(async proj => await CompressCalamariProject(proj)); - await Task.WhenAll(compressTasks); - }); - - async Task PublishPackageAsync(CalamariPackageMetadata calamariPackageMetadata) - { - Log.Information("Publishing {ProjectName} for framework '{Framework}' and arch '{Architecture}'", calamariPackageMetadata.Project.Name, calamariPackageMetadata.Framework, calamariPackageMetadata.Architecture); - - var project = calamariPackageMetadata.Project; - var outputDirectory = PublishDirectory / project.Name / calamariPackageMetadata.Architecture; - - await Task.Run(() => - DotNetPublish(s => - s.SetConfiguration(Configuration) - .SetProject(project) - .SetFramework(calamariPackageMetadata.Framework) - .SetRuntime(calamariPackageMetadata.Architecture) - .EnableNoBuild() - .EnableNoRestore() - .SetSelfContained(OperatingSystem.IsWindows()) // This is here purely to make the local build experience on non-Windows devices workable - Publish breaks on non-Windows platforms with SelfContained = true - .SetOutput(outputDirectory))); - - File.Copy(RootDirectory / "global.json", outputDirectory / "global.json"); - - return outputDirectory; - } - - async Task CompressCalamariProject(Project project) - { - Log.Information($"Compressing Calamari flavour {PublishDirectory}/{project.Name}"); - var compressionSource = PublishDirectory / project.Name; - if (!Directory.Exists(compressionSource)) - { - Log.Information($"Skipping compression for {project.Name} since nothing was built"); - return; - } - - await Task.Run(() => compressionSource.CompressTo($"{ArtifactsDirectory / project.Name}.zip")); - } - - Target PublishAzureWebAppNetCoreShim => - _ => _.DependsOn(RestoreSolution) - .DependsOn(GetCalamariFlavourProjectsToPublish) - //we only build the net core shim when there is the AzureWebApp project is being built - .OnlyWhenDynamic(() => CalamariProjects.Any(p => p.Name == "Calamari.AzureWebApp")) - .Executes(() => - { - if (!OperatingSystem.IsWindows()) - { - Log.Warning("Unable to build Calamari.AzureWebApp.NetCoreShim as it's a Full Framework application and can only be compiled on Windows"); - return; - } - - var project = Solution.GetProject("Calamari.AzureWebApp.NetCoreShim"); - if (project is null) - { - Log.Error("Failed to find Calamari.AzureWebApp.NetCoreShim project"); - return; - } - - var outputPath = PublishDirectory / project.Name; - - //as this is the only Net 4.6.2 application left, we do a build and restore here - DotNetPublish(s => s - .SetConfiguration(Configuration) - .SetProject(project.Path) - .SetFramework("net462") - .SetVersion(NugetVersion.Value) - .SetInformationalVersion(OctoVersionInfo.Value?.InformationalVersion) - .SetOutput(outputPath)); - - var archivePath = SourceDirectory / "Calamari.AzureWebApp" / "netcoreshim" / "netcoreshim.zip"; - archivePath.DeleteFile(); - - outputPath.CompressTo(archivePath); - }); - - Target PackageConsolidatedCalamariZip => - d => - d.Executes(() => - { - var artifacts = Directory.GetFiles(ArtifactsDirectory, "*.nupkg") - .Where(a => !NuGetPackagesToExcludeFromConsolidation.Any(a.Contains)); - - var packageReferences = new List(); - foreach (var artifact in artifacts) - { - using var zip = ZipFile.OpenRead(artifact); - var nuspecFileStream = zip.Entries.First(e => e.Name.EndsWith(".nuspec")).Open(); - var nuspecReader = new NuspecReader(nuspecFileStream); - var metadata = nuspecReader.GetMetadata().ToList(); - packageReferences.Add(new BuildPackageReference - { - Name = Regex.Replace(metadata.Where(kvp => kvp.Key == "id").Select(i => i.Value).First(), @"^Octopus\.", ""), - Version = metadata.Where(kvp => kvp.Key == "version").Select(i => i.Value).First(), - PackagePath = artifact - }); - } - - foreach (var flavour in GetCalamariFlavours()) - { - if (Solution.GetProject(flavour) != null) - { - packageReferences.Add(new BuildPackageReference - { - Name = flavour, - Version = NugetVersion.Value, - PackagePath = ArtifactsDirectory / $"{flavour}.zip" - }); - } - } - - Directory.CreateDirectory(ConsolidatedPackageDirectory); - var (result, packageFilename) = new Consolidate(Log.Logger).Execute(ConsolidatedPackageDirectory, packageReferences); - - if (!result) - throw new Exception("Failed to consolidate calamari Packages"); - - ConsolidatedPackagePath = packageFilename; - Log.Information("Created consolidated package zip: {PackageFilename}", packageFilename); - }); - - Target CalamariConsolidationVerification => - d => - d.DependsOn(PackageConsolidatedCalamariZip) - .OnlyWhenDynamic(() => string.IsNullOrEmpty(TargetRuntime), "TargetRuntime is not restricted") - .Executes(() => - { - Environment.SetEnvironmentVariable("CONSOLIDATED_ZIP", ConsolidatedPackagePath); - Environment.SetEnvironmentVariable("EXPECTED_VERSION", NugetVersion.Value); - Environment.SetEnvironmentVariable("IS_WINDOWS", OperatingSystem.IsWindows().ToString()); - - DotNetTest(s => s - .SetProjectFile(ConsolidateCalamariPackagesProject) - .SetConfiguration(Configuration) - .SetProcessArgumentConfigurator(args => - args.Add("--logger:\"console;verbosity=detailed\"") - .Add("--") - .Add("NUnit.ShowInternalProperties=true"))); - }); - - Target PackConsolidationLibrariesNugetPackages => - d => - d.DependsOn(CalamariConsolidationVerification) - .Executes(() => - { - // Pack the Consolidation Libraries - const string consolidateCalamariPackagesProjectPrefix = "Calamari.ConsolidateCalamariPackages"; - var consolidationLibraryProjects = Solution.Projects.Where(project => project.Name.StartsWith(consolidateCalamariPackagesProjectPrefix)); - - foreach (var project in consolidationLibraryProjects) - { - Log.Information("Packaging {ProjectName}", project.Name); - - var buildDirectory = SourceDirectory / project.Name / "bin" / Configuration; - - //Build the consolidated package libraries - DotNetBuild(s => - s.SetConfiguration(Configuration) - .SetProjectFile(project)); - - File.Copy(RootDirectory / "global.json", buildDirectory / "global.json"); - - //sign the built directory - SignDirectory(buildDirectory); - - //pack the project - DotNetPack(s => s - .SetConfiguration(Configuration) - .SetOutputDirectory(ArtifactsDirectory) - .SetProject(project) - .EnableNoBuild() - .EnableNoRestore() - .EnableIncludeSource() - .SetVersion(NugetVersion.Value)); - } - }); - - Target PackCalamariConsolidatedNugetPackage => - d => - d.DependsOn(PackConsolidationLibrariesNugetPackages) - .Executes(() => - { - NuGetPack(s => s.SetTargetPath(BuildDirectory / "Calamari.Consolidated.nuspec") - .SetBasePath(BuildDirectory) - .SetVersion(NugetVersion.Value) - .SetOutputDirectory(ArtifactsDirectory)); - }); - - Target CopyToLocalPackages => - d => - d.Requires(() => IsLocalBuild) - .DependsOn(PublishCalamariProjects) - .Executes(() => - { - Directory.CreateDirectory(LocalPackagesDirectory); - foreach (AbsolutePath file in Directory.GetFiles(ArtifactsDirectory, "Octopus.Calamari.*.nupkg")) - { - var target = LocalPackagesDirectory / Path.GetFileName(file); - file.Copy(target, ExistsPolicy.FileOverwrite); - } - }); - - Target UpdateCalamariVersionOnOctopusServer => - d => - d.OnlyWhenStatic(() => SetOctopusServerVersion) - .Requires(() => IsLocalBuild) - .DependsOn(CopyToLocalPackages) - .Executes(() => - { - var serverProjectFile = RootDirectory / ".." / "OctopusDeploy" / "source" / "Octopus.Server" / "Octopus.Server.csproj"; - if (File.Exists(serverProjectFile)) - { - Log.Information("Setting Calamari version in Octopus Server " - + "project {ServerProjectFile} to {NugetVersion}", - serverProjectFile, NugetVersion.Value); - SetOctopusServerCalamariVersion(serverProjectFile); - } - else - { - Log.Warning("Could not set Calamari version in Octopus Server project " - + "{ServerProjectFile} to {NugetVersion} as could not find " - + "project file", - serverProjectFile, NugetVersion.Value); - } - }); - - Target PublishNukeBuild => - d => - d.Executes(async () => - { - const string runtime = "win-x64"; - var nukeBuildOutputDirectory = BuildDirectory / "outputs" / runtime / "nukebuild"; - nukeBuildOutputDirectory.CreateOrCleanDirectory(); - - DotNetPublish(p => p - .SetProject(RootDirectory / "build" / "_build.csproj") - .SetConfiguration(Configuration) - .SetRuntime(runtime) - .EnableSelfContained()); - - await Ci.ZipFolderAndUploadArtifact(nukeBuildOutputDirectory, ArtifactsDirectory / $"nukebuild.{runtime}.zip"); - }); - + Target SetTeamCityVersion => d => d.Executes(() => TeamCity.Instance?.SetBuildNumber(NugetVersion.Value)); - Target BuildLocal => d => - d.DependsOn(PublishCalamariProjects) - .DependsOn(PackCalamariConsolidatedNugetPackage) - .DependsOn(UpdateCalamariVersionOnOctopusServer); - Target BuildCi => d => - d.DependsOn(SetTeamCityVersion) - .DependsOn(PublishCalamariProjects) + d.DependsOn(PublishCalamariProjects) .DependsOn(PackCalamariConsolidatedNugetPackage) .DependsOn(PublishNukeBuild); - - Target BuildAndPublishProject => d => d.OnlyWhenDynamic(() => !string.IsNullOrEmpty(ProjectToBuild)).DependsOn(PublishCalamariProjects); - public static int Main() => Execute(x => IsServerBuild ? x.BuildCi : x.BuildLocal); - void SignDirectory(string directory) - { - if (!WillSignBinaries) - return; - - Log.Information("Signing directory: {Directory} and sub-directories", directory); - var binariesFolders = Directory.GetDirectories(directory, "*", new EnumerationOptions { RecurseSubdirectories = true }); - foreach (var subDirectory in binariesFolders.Append(directory)) - { - Signing.SignAndTimestampBinaries(subDirectory, AzureKeyVaultUrl, AzureKeyVaultAppId, - AzureKeyVaultAppSecret, AzureKeyVaultTenantId, AzureKeyVaultCertificateName, - SigningCertificatePath, SigningCertificatePassword); - } - } - - // Sets the Octopus.Server.csproj Calamari.Consolidated package version - void SetOctopusServerCalamariVersion(string projectFile) - { - var text = File.ReadAllText(projectFile); - text = Regex.Replace(text, @"([\S])+", - $"{NugetVersion.Value}"); - File.WriteAllText(projectFile, text); - } - string GetNugetVersion() { return AppendTimestamp @@ -575,38 +151,4 @@ string GetNugetVersion() : OctoVersionInfo.Value?.NuGetVersion ?? throw new InvalidOperationException("Unable to retrieve valid Nuget Version"); } - - IReadOnlyCollection GetRuntimeIdentifiers(Project? project) - { - if (project is null) - return []; - - var runtimes = project.GetRuntimeIdentifiers(); - - if (!string.IsNullOrWhiteSpace(TargetRuntime)) - runtimes = runtimes?.Where(x => x == TargetRuntime).ToList().AsReadOnly(); - - return runtimes ?? []; - } - - // Although all libraries/flavours now support .NET Core, ServiceFabric can currently only be built on Windows devices - // This is here purely to make the local build experience on non-Windows devices (with testing) workable - static string[] GetCalamariFlavours() - { - return BuildableCalamariProjects.GetCalamariProjectsToBuild(OperatingSystem.IsWindows()); - } - - HashSet ListAllRuntimeIdentifiersInSolution() - { - var allRuntimes = Solution.AllProjects - .SelectMany(p => p.GetRuntimeIdentifiers() ?? Array.Empty()) - .Distinct() - .Where(rid => rid != "win7-x86") //I have no idea where this is coming from, but let's ignore it. My theory is it's coming from the netstandard libs - .ToHashSet(); - - if (!string.IsNullOrWhiteSpace(TargetRuntime)) - allRuntimes = allRuntimes.Where(x => x == TargetRuntime).ToHashSet(); - - return allRuntimes; - } } \ No newline at end of file diff --git a/build/GlobalUsings.cs b/build/GlobalUsings.cs new file mode 100644 index 000000000..d8d3190ff --- /dev/null +++ b/build/GlobalUsings.cs @@ -0,0 +1,11 @@ +global using System; +global using System.IO; +global using System.Linq; +global using Nuke.Common; +global using Nuke.Common.IO; +global using Nuke.Common.Tools.DotNet; +global using Nuke.Common.CI.TeamCity; +global using Serilog; +global using static Nuke.Common.Tools.DotNet.DotNetTasks; +global using static Nuke.Common.Tools.Git.GitTasks; +global using static Nuke.Common.Tools.NuGet.NuGetTasks; \ No newline at end of file diff --git a/build/KnownPaths.cs b/build/KnownPaths.cs index 052df5947..c88341dcf 100644 --- a/build/KnownPaths.cs +++ b/build/KnownPaths.cs @@ -6,6 +6,15 @@ namespace Calamari.Build; public static class KnownPaths { public static AbsolutePath RootDirectory => NukeBuild.RootDirectory; + + public static AbsolutePath SourceDirectory => RootDirectory / "source"; + + public static AbsolutePath BuildDirectory => RootDirectory / "builds"; + + public static AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; + + public static AbsolutePath PublishDirectory => RootDirectory / "publish"; + public static AbsolutePath OutputsDirectory => RootDirectory / "outputs"; } \ No newline at end of file diff --git a/build/Utilities/Ci.cs b/build/Utilities/Ci.cs index a5f238356..a3512aa34 100644 --- a/build/Utilities/Ci.cs +++ b/build/Utilities/Ci.cs @@ -14,10 +14,8 @@ namespace Calamari.Build.Utilities { class Ci { - public static async Task ZipFolderAndUploadArtifact(AbsolutePath folderPath, AbsolutePath outputPath) + public static void ZipFolderAndUploadArtifact(AbsolutePath folderPath, AbsolutePath outputPath) { - await Task.CompletedTask; - if (!Directory.EnumerateFiles(folderPath).Any() && !Directory.EnumerateDirectories(folderPath).Any()) { From ca4c8c32e4089c1a9bb75b6ebe661dddc5c78772 Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Fri, 12 Dec 2025 15:33:41 +1100 Subject: [PATCH 2/4] More refactoring --- build/Build.Consolidation.cs | 68 ++++++--------- build/Build.PackageCalamariProject.cs | 120 ++++++++++++++------------ 2 files changed, 93 insertions(+), 95 deletions(-) diff --git a/build/Build.Consolidation.cs b/build/Build.Consolidation.cs index 53b9b9bba..3fc473a42 100644 --- a/build/Build.Consolidation.cs +++ b/build/Build.Consolidation.cs @@ -1,8 +1,5 @@ using System.Collections.Generic; -using System.IO.Compression; -using System.Text.RegularExpressions; using JetBrains.Annotations; -using NuGet.Packaging; using Nuke.Common.Tooling; using Nuke.Common.Tools.NuGet; using Octopus.Calamari.ConsolidatedPackage; @@ -19,47 +16,36 @@ public partial class Build "Octopus.Calamari.ConsolidatedPackage", "Octopus.Calamari.ConsolidatedPackage.Api" ]; - + static AbsolutePath ConsolidateCalamariPackagesProject => KnownPaths.SourceDirectory / "Calamari.ConsolidateCalamariPackages.Tests" / "Calamari.ConsolidateCalamariPackages.Tests.csproj"; - static AbsolutePath ConsolidatedPackageDirectory => KnownPaths.ArtifactsDirectory / "consolidated"; Target PackageConsolidatedCalamariZip => d => d.Executes(() => { - var artifacts = Directory.GetFiles(ArtifactsDirectory, "*.nupkg") - .Where(a => !NuGetPackagesToExcludeFromConsolidation.Any(a.Contains)); - - var packageReferences = new List(); - foreach (var artifact in artifacts) - { - using var zip = ZipFile.OpenRead(artifact); - var nuspecFileStream = zip.Entries.First(e => e.Name.EndsWith(".nuspec")).Open(); - var nuspecReader = new NuspecReader(nuspecFileStream); - var metadata = nuspecReader.GetMetadata().ToList(); - packageReferences.Add(new BuildPackageReference - { - Name = Regex.Replace(metadata.Where(kvp => kvp.Key == "id").Select(i => i.Value).First(), @"^Octopus\.", ""), - Version = metadata.Where(kvp => kvp.Key == "version").Select(i => i.Value).First(), - PackagePath = artifact - }); - } + //Look for all zip files in the artifacts directory that aren't tests + var artifacts = Directory.GetFiles(KnownPaths.ArtifactsDirectory, "*.zip") + .Where(a => !NuGetPackagesToExcludeFromConsolidation.Any(a.Contains)) + .Where(a => a.Contains("Tests")); - foreach (var flavour in GetCalamariFlavours()) - { - if (Solution.GetProject(flavour) != null) - { - packageReferences.Add(new BuildPackageReference - { - Name = flavour, - Version = NugetVersion.Value, - PackagePath = ArtifactsDirectory / $"{flavour}.zip" - }); - } - } + var packageReferences = artifacts.Select(artifactPath => (artifactPath, projectName: Path.GetFileNameWithoutExtension(artifactPath))) + .Where(x => Solution.GetProject(x.projectName) is not null) + .Select((x) => + { + var (artifactPath, projectName) = x; + return new BuildPackageReference + { + Name = projectName, + Version = NugetVersion.Value, + PackagePath = artifactPath + }; + }) + .ToList(); - Directory.CreateDirectory(ConsolidatedPackageDirectory); - var (result, packageFilename) = new Consolidate(Log.Logger).Execute(ConsolidatedPackageDirectory, packageReferences); + var consolidatedPackageDirectory = KnownPaths.ArtifactsDirectory / "consolidated"; + consolidatedPackageDirectory.CreateOrCleanDirectory(); + + var (result, packageFilename) = new Consolidate(Log.Logger).Execute(consolidatedPackageDirectory, packageReferences); if (!result) throw new Exception("Failed to consolidate calamari Packages"); @@ -76,7 +62,7 @@ public partial class Build { Environment.SetEnvironmentVariable("CONSOLIDATED_ZIP", ConsolidatedPackagePath); Environment.SetEnvironmentVariable("EXPECTED_VERSION", NugetVersion.Value); - Environment.SetEnvironmentVariable("IS_WINDOWS", OperatingSystem.IsWindows().ToString()); + Environment.SetEnvironmentVariable("IS_WINDOWS", (OperatingSystem.IsWindows() || !IsLocalBuild).ToString()); DotNetTest(s => s .SetProjectFile(ConsolidateCalamariPackagesProject) @@ -93,10 +79,10 @@ public partial class Build d.DependsOn(CalamariConsolidationVerification) .Executes(() => { - NuGetPack(s => + NuGetPack(s => s.SetTargetPath(BuildDirectory / "Calamari.Consolidated.nuspec") - .SetBasePath(BuildDirectory) - .SetVersion(NugetVersion.Value) - .SetOutputDirectory(ArtifactsDirectory)); + .SetBasePath(BuildDirectory) + .SetVersion(NugetVersion.Value) + .SetOutputDirectory(ArtifactsDirectory)); }); } \ No newline at end of file diff --git a/build/Build.PackageCalamariProject.cs b/build/Build.PackageCalamariProject.cs index af31f5202..df52a6683 100644 --- a/build/Build.PackageCalamariProject.cs +++ b/build/Build.PackageCalamariProject.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; +using Calamari.Build.Utilities; using Nuke.Common.ProjectModel; using Octopus.Calamari.ConsolidatedPackage; @@ -12,9 +13,15 @@ public partial class Build // By default, all projects are built unless the parameter is set specifically // Although all libraries/flavours now support .NET Core, ServiceFabric can currently only be built on Windows devices // This is here purely to make the local build experience on non-Windows devices (with testing) workable - [Parameter(Name = "ProjectToBuild")] readonly string[] ProjectsToBuild = BuildableCalamariProjects.GetCalamariProjectsToBuild(OperatingSystem.IsWindows()); - - + [Parameter(Name = "ProjectToBuild", Separator = "+")] + readonly string[] ProjectsToBuild = + [ + ..BuildableCalamariProjects.GetCalamariProjectsToBuild(OperatingSystem.IsWindows()), + + // Calamari.Scripting is a library that other calamari flavours depend on; not a flavour on its own right. + // Unlike other *Calamari* tests, we would still want to produce Calamari.Scripting.Zip and its tests, like its flavours. + "Calamari.Scripting" + ]; List PackagesToPublish = new(); List CalamariProjects = new(); @@ -24,14 +31,10 @@ public partial class Build d.DependsOn(RestoreSolution) .Executes(() => { - // Calamari.Scripting is a library that other calamari flavours depend on; not a flavour on its own right. - // Unlike other *Calamari* tests, we would still want to produce Calamari.Scripting.Zip and its tests, like its flavours. - string[] projectNames = [..ProjectsToBuild, "Calamari.Scripting"]; - //its assumed each project has a corresponding test project - var testProjectNames = projectNames.Select(p => $"{p}.Tests"); - - var allProjectNames = projectNames.Concat(testProjectNames).ToHashSet(); + var testProjectNames = ProjectsToBuild.Select(p => $"{p}.Tests"); + + var allProjectNames = ProjectsToBuild.Concat(testProjectNames).ToHashSet(); var calamariProjects = Solution.Projects.Where(project => allProjectNames.Contains(project.Name)).ToList(); @@ -102,33 +105,28 @@ await Task.Run(() => DotNetBuild(s => .Executes(async () => { var semaphore = new SemaphoreSlim(4); - var outputPaths = new ConcurrentBag(); - - Log.Information("Publishing projects..."); - var publishTasks = PackagesToPublish.Select(async package => - { - await semaphore.WaitAsync(); - try - { - var outputPath = await PublishPackageAsync(package); - outputPaths.Add(outputPath); - } - finally - { - semaphore.Release(); - } - }); - - await Task.WhenAll(publishTasks); - - // Sign and compress tasks - Log.Information("Signing published binaries..."); - var signTasks = outputPaths - .Where(output => output != null && !output.ToString().Contains("Tests")) - .Select(output => Task.Run(() => SignDirectory(output!))) - .ToList(); - - await Task.WhenAll(signTasks); + + Log.Information("Publishing projects and test projects..."); + var publishAndSignTasks = PackagesToPublish.Select(async package => + { + await semaphore.WaitAsync(); + try + { + var outputPath = await PublishPackageAsync(package); + + //we sign the non-test directories + if (!outputPath.Contains("Tests")) + { + SignDirectory(outputPath); + } + } + finally + { + semaphore.Release(); + } + }); + + await Task.WhenAll(publishAndSignTasks); Log.Information("Compressing published projects..."); var compressTasks = CalamariProjects.Select(async proj => await CompressCalamariProject(proj)); @@ -140,35 +138,49 @@ await Task.Run(() => DotNetBuild(s => Log.Information("Publishing {ProjectName} for framework '{Framework}' and arch '{Architecture}'", calamariPackageMetadata.Project.Name, calamariPackageMetadata.Framework, calamariPackageMetadata.Architecture); var project = calamariPackageMetadata.Project; - var outputDirectory = PublishDirectory / project.Name / calamariPackageMetadata.Architecture; - - await Task.Run(() => - DotNetPublish(s => - s.SetConfiguration(Configuration) - .SetProject(project) - .SetFramework(calamariPackageMetadata.Framework) - .SetRuntime(calamariPackageMetadata.Architecture) - .EnableNoBuild() - .EnableNoRestore() - .SetSelfContained(OperatingSystem.IsWindows() || !IsLocalBuild) // This is here purely to make the local build experience on non-Windows devices workable - Publish breaks on non-Windows platforms with SelfContained = true - .SetOutput(outputDirectory))); + var outputDirectory = KnownPaths.PublishDirectory / project.Name / calamariPackageMetadata.Architecture; + + await Task.Run(() => DotNetPublish(s => + s.SetConfiguration(Configuration) + .SetProject(project) + .SetFramework(calamariPackageMetadata.Framework) + .SetRuntime(calamariPackageMetadata.Architecture) + .EnableNoBuild() + .EnableNoRestore() + .SetSelfContained(OperatingSystem.IsWindows() || !IsLocalBuild) // This is here purely to make the local build experience on non-Windows devices workable - Publish breaks on non-Windows platforms with SelfContained = true + .SetOutput(outputDirectory))); File.Copy(RootDirectory / "global.json", outputDirectory / "global.json"); return outputDirectory; } - async Task CompressCalamariProject(Project project) + void SignDirectory(string directory) + { + if (!WillSignBinaries) + return; + + Log.Information("Signing directory: {Directory} and sub-directories", directory); + var binariesFolders = Directory.GetDirectories(directory, "*", new EnumerationOptions { RecurseSubdirectories = true }); + foreach (var subDirectory in binariesFolders.Append(directory)) + { + Signing.SignAndTimestampBinaries(subDirectory, AzureKeyVaultUrl, AzureKeyVaultAppId, + AzureKeyVaultAppSecret, AzureKeyVaultTenantId, AzureKeyVaultCertificateName, + SigningCertificatePath, SigningCertificatePassword); + } + } + + static async Task CompressCalamariProject(Project project) { - Log.Information($"Compressing Calamari flavour {PublishDirectory}/{project.Name}"); - var compressionSource = PublishDirectory / project.Name; - if (!Directory.Exists(compressionSource)) + Log.Information($"Compressing project {PublishDirectory}/{project.Name}"); + var sourceDirectory = PublishDirectory / project.Name; + if (!Directory.Exists(sourceDirectory)) { Log.Information($"Skipping compression for {project.Name} since nothing was built"); return; } - await Task.Run(() => compressionSource.CompressTo($"{ArtifactsDirectory / project.Name}.zip")); + await Task.Run(() => Ci.ZipFolderAndUploadArtifact(sourceDirectory, KnownPaths.ArtifactsDirectory / $"{project.Name}.zip")); } IReadOnlyCollection GetRuntimeIdentifiers(Project? project) From e1d08127c6d9ec04fd545ce963f14d371c4d5ed3 Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Fri, 12 Dec 2025 16:19:24 +1100 Subject: [PATCH 3/4] Move TargetRuntime --- build/Build.PackageCalamariProject.cs | 4 ++++ build/Build.cs | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/Build.PackageCalamariProject.cs b/build/Build.PackageCalamariProject.cs index df52a6683..6224af1da 100644 --- a/build/Build.PackageCalamariProject.cs +++ b/build/Build.PackageCalamariProject.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Calamari.Build.Utilities; +using JetBrains.Annotations; using Nuke.Common.ProjectModel; using Octopus.Calamari.ConsolidatedPackage; @@ -22,6 +23,8 @@ public partial class Build // Unlike other *Calamari* tests, we would still want to produce Calamari.Scripting.Zip and its tests, like its flavours. "Calamari.Scripting" ]; + + [Parameter] readonly string? TargetRuntime; List PackagesToPublish = new(); List CalamariProjects = new(); @@ -99,6 +102,7 @@ await Task.Run(() => DotNetBuild(s => await Task.WhenAll(buildTasks); }); + [PublicAPI] Target PublishCalamariProjects => d => d.DependsOn(BuildCalamariProjects) diff --git a/build/Build.cs b/build/Build.cs index a08797f01..b0eea22b6 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -44,7 +44,6 @@ partial class Build : NukeBuild [Parameter(Name = "signing_certificate_password")] [Secret] readonly string SigningCertificatePassword = "Password01!"; - [Parameter] readonly string? TargetRuntime; const string CiBranchNameEnvVariable = "OCTOVERSION_CurrentBranch"; From 50ee1204cab671c64e23f86518ce741e9ab351fd Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Fri, 16 Jan 2026 14:55:41 +1100 Subject: [PATCH 4/4] Remove dupe code --- build/Build.PackageCalamariProject.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/build/Build.PackageCalamariProject.cs b/build/Build.PackageCalamariProject.cs index 6224af1da..5389b5cd4 100644 --- a/build/Build.PackageCalamariProject.cs +++ b/build/Build.PackageCalamariProject.cs @@ -213,19 +213,4 @@ HashSet ListAllRuntimeIdentifiersInSolution() return allRuntimes; } - - void SignDirectory(string directory) - { - if (!WillSignBinaries) - return; - - Log.Information("Signing directory: {Directory} and sub-directories", directory); - var binariesFolders = Directory.GetDirectories(directory, "*", new EnumerationOptions { RecurseSubdirectories = true }); - foreach (var subDirectory in binariesFolders.Append(directory)) - { - Signing.SignAndTimestampBinaries(subDirectory, AzureKeyVaultUrl, AzureKeyVaultAppId, - AzureKeyVaultAppSecret, AzureKeyVaultTenantId, AzureKeyVaultCertificateName, - SigningCertificatePath, SigningCertificatePassword); - } - } } \ No newline at end of file