/////////////////////////////////////////////////////////////////////////////// // TOOLS / ADDINS /////////////////////////////////////////////////////////////////////////////// #tool dotnet:?package=NuGetKeyVaultSignTool&version=3.2.3 #tool dotnet:?package=AzureSignTool&version=4.0.1 #tool dotnet:?package=GitReleaseManager.Tool&version=0.15.0 #tool dotnet:?package=XamlStyler.Console&version=3.2404.2 #tool nuget:?package=GitVersion.CommandLine&version=5.12.0 /////////////////////////////////////////////////////////////////////////////// // ARGUMENTS /////////////////////////////////////////////////////////////////////////////// var target = Argument("target", "Default"); /////////////////////////////////////////////////////////////////////////////// // PREPARATION /////////////////////////////////////////////////////////////////////////////// var repoName = "MahApps.Metro"; var baseDir = MakeAbsolute(Directory(".")).ToString(); var srcDir = baseDir + "/src"; var solution = srcDir + "/MahApps.Metro.sln"; var publishDir = baseDir + "/Publish"; var testResultsDir = Directory(baseDir + "/TestResults"); var gitVersionPath = Context.Tools.Resolve("gitversion.exe"); var styler = Context.Tools.Resolve("xstyler.exe"); var stylerFile = baseDir + "/Settings.XAMLStyler"; public class BuildData { public string Configuration { get; } public Verbosity Verbosity { get; } public DotNetVerbosity DotNetVerbosity { get; } public bool IsLocalBuild { get; set; } public bool IsPullRequest { get; set; } public bool IsPrerelease { get; set; } public GitVersion GitVersion { get; set; } public BuildData( string configuration, Verbosity verbosity, DotNetVerbosity dotNetVerbosity ) { Configuration = configuration; Verbosity = verbosity; DotNetVerbosity = dotNetVerbosity; } public void SetGitVersion(GitVersion gitVersion) { GitVersion = gitVersion; IsPrerelease = GitVersion.NuGetVersion.Contains("-"); } } /////////////////////////////////////////////////////////////////////////////// // SETUP / TEARDOWN /////////////////////////////////////////////////////////////////////////////// Setup<BuildData>(ctx => { if (!IsRunningOnWindows()) { throw new NotImplementedException($"{repoName} will only build on Windows because it's not possible to target WPF and Windows Forms from UNIX."); } Spectre.Console.AnsiConsole.Write(new Spectre.Console.FigletText(repoName)); var buildData = new BuildData( configuration: Argument("configuration", "Release"), verbosity: Argument("verbosity", Verbosity.Minimal), dotNetVerbosity: Argument("dotNetVerbosity", DotNetVerbosity.Minimal) ) { IsLocalBuild = BuildSystem.IsLocalBuild, IsPullRequest = (BuildSystem.GitHubActions.IsRunningOnGitHubActions && BuildSystem.GitHubActions.Environment.PullRequest.IsPullRequest) || (BuildSystem.AppVeyor.IsRunningOnAppVeyor && BuildSystem.AppVeyor.Environment.PullRequest.IsPullRequest) }; // Set build version for CI if (buildData.IsLocalBuild == false || buildData.Verbosity == Verbosity.Verbose) { GitVersion(new GitVersionSettings { ToolPath = gitVersionPath, OutputType = GitVersionOutput.BuildServer }); } buildData.SetGitVersion(GitVersion(new GitVersionSettings { ToolPath = gitVersionPath, OutputType = GitVersionOutput.Json })); Information("GitVersion : {0}", gitVersionPath); Information("Branch : {0}", buildData.GitVersion.BranchName); Information("Configuration : {0}", buildData.Configuration); Information("IsLocalBuild : {0}", buildData.IsLocalBuild); Information("IsPrerelease : {0}", buildData.IsPrerelease); Information("Informational Version: {0}", buildData.GitVersion.InformationalVersion); Information("SemVer Version: {0}", buildData.GitVersion.SemVer); Information("AssemblySemVer Version: {0}", buildData.GitVersion.AssemblySemVer); Information("MajorMinorPatch Version: {0}", buildData.GitVersion.MajorMinorPatch); Information("NuGet Version: {0}", buildData.GitVersion.NuGetVersion); Information("Verbosity : {0}", buildData.Verbosity); Information("Publish folder : {0}", publishDir); return buildData; }); Teardown(ctx => { }); /////////////////////////////////////////////////////////////////////////////// // TASKS /////////////////////////////////////////////////////////////////////////////// Task("Clean") .ContinueOnError() .Does(() => { var filesToDelete = GetFiles("**/*_wpftmp.csproj"); DeleteFiles(filesToDelete); var directoriesToDelete = GetDirectories("./**/obj") .Concat(GetDirectories("./**/bin")) .Concat(GetDirectories("./**/Publish")) ; DeleteDirectories(directoriesToDelete, new DeleteDirectorySettings { Recursive = true, Force = true }); }); Task("Restore") .Does<BuildData>(data => { DotNetRestore(solution); }); Task("Build") .Does<BuildData>(data => { var msbuildSettings = new DotNetMSBuildSettings { MaxCpuCount = 0, Version = data.GitVersion.NuGetVersion, AssemblyVersion = data.GitVersion.AssemblySemVer, FileVersion = data.GitVersion.AssemblySemFileVer, InformationalVersion = data.GitVersion.InformationalVersion, ContinuousIntegrationBuild = true, ArgumentCustomization = args => args.Append("/m").Append("/nr:false") // The /nr switch tells msbuild to quite once it's done }; // msbuildSettings.FileLoggers.Add( // new MSBuildFileLoggerSettings // { // LogFile = buildLogFile, // AppendToLogFile = true, // Verbosity = data.DotNetVerbosity // } // ); var settings = new DotNetBuildSettings { MSBuildSettings = msbuildSettings, Configuration = data.Configuration, Verbosity = data.DotNetVerbosity, NoRestore = true }; DotNetBuild(solution, settings); }); Task("Pack") .ContinueOnError() .Does<BuildData>(data => { EnsureDirectoryExists(Directory(publishDir)); var msbuildSettings = new DotNetMSBuildSettings { MaxCpuCount = 0, Version = data.GitVersion.NuGetVersion, AssemblyVersion = data.GitVersion.AssemblySemVer, FileVersion = data.GitVersion.AssemblySemFileVer, InformationalVersion = data.GitVersion.InformationalVersion } .WithProperty("IncludeBuildOutput", "true") .WithProperty("RepositoryBranch", data.GitVersion.BranchName) .WithProperty("RepositoryCommit", data.GitVersion.Sha) .WithProperty("Description", "The goal of MahApps.Metro is to allow devs to quickly and easily cobble together a 'Modern' UI for their WPF apps (.Net 4.6.2 and later), with minimal effort.") ; // msbuildSettings.FileLoggers.Add( // new MSBuildFileLoggerSettings // { // LogFile = buildLogFile, // AppendToLogFile = true, // Verbosity = DotNetVerbosity.Minimal // } // ); var settings = new DotNetPackSettings { Configuration = data.Configuration, OutputDirectory = MakeAbsolute(Directory(publishDir)).FullPath, MSBuildSettings = msbuildSettings, NoBuild = true, NoRestore = true }; var project = "./src/MahApps.Metro/MahApps.Metro.csproj"; DotNetPack(project, settings); }); Task("Sign") .WithCriteria<BuildData>((context, data) => !data.IsPullRequest) .ContinueOnError() .Does(() => { var files = GetFiles("./src/MahApps.Metro/bin/**/*/MahApps.Metro*.dll"); SignFiles(files, "MahApps.Metro, a toolkit for creating Metro / Modern UI styled WPF applications."); files = GetFiles("./src/MahApps.Metro.Samples/**/bin/**/*.exe"); SignFiles(files, "Demo application of MahApps.Metro, a toolkit for creating Metro / Modern UI styled WPF applications."); }); Task("SignNuGet") .WithCriteria<BuildData>((context, data) => !data.IsPullRequest) .WithCriteria<BuildData>((context, data) => DirectoryExists(Directory(publishDir))) .ContinueOnError() .Does(() => { var vurl = EnvironmentVariable("azure-key-vault-url"); if(string.IsNullOrWhiteSpace(vurl)) { Error("Could not resolve signing url."); return; } var vcid = EnvironmentVariable("azure-key-vault-client-id"); if(string.IsNullOrWhiteSpace(vcid)) { Error("Could not resolve signing client id."); return; } var vctid = EnvironmentVariable("azure-key-vault-tenant-id"); if(string.IsNullOrWhiteSpace(vctid)) { Error("Could not resolve signing client tenant id."); return; } var vcs = EnvironmentVariable("azure-key-vault-client-secret"); if(string.IsNullOrWhiteSpace(vcs)) { Error("Could not resolve signing client secret."); return; } var vc = EnvironmentVariable("azure-key-vault-certificate"); if(string.IsNullOrWhiteSpace(vc)) { Error("Could not resolve signing certificate."); return; } var nugetFiles = GetFiles(publishDir + "/*.nupkg"); var signTool = Context.Tools.Resolve("NuGetKeyVaultSignTool.exe"); foreach(var file in nugetFiles) { Information($"Sign file: {file}"); ExecuteProcess(signTool, new ProcessArgumentBuilder() .Append("sign") .Append(MakeAbsolute(file).FullPath) .Append("--force") .AppendSwitchQuoted("--file-digest", "sha256") .AppendSwitchQuoted("--timestamp-rfc3161", "http://timestamp.digicert.com") .AppendSwitchQuoted("--timestamp-digest", "sha256") .AppendSwitchQuoted("--azure-key-vault-url", vurl) .AppendSwitchQuotedSecret("--azure-key-vault-client-id", vcid) .AppendSwitchQuotedSecret("--azure-key-vault-tenant-id", vctid) .AppendSwitchQuotedSecret("--azure-key-vault-client-secret", vcs) .AppendSwitchQuotedSecret("--azure-key-vault-certificate", vc) ); } }); Task("Zip") .Does<BuildData>(data => { EnsureDirectoryExists(Directory(publishDir)); Zip("./src/MahApps.Metro.Samples/MahApps.Metro.Demo/bin/" + data.Configuration, publishDir + "/MahApps.Metro.Demo-v" + data.GitVersion.NuGetVersion + ".zip"); Zip("./src/MahApps.Metro.Samples/MahApps.Metro.Caliburn.Demo/bin/" + data.Configuration, publishDir + "/MahApps.Metro.Caliburn.Demo-v" + data.GitVersion.NuGetVersion + ".zip"); }); Task("Tests") .ContinueOnError() .Does<BuildData>(data => { CleanDirectory(testResultsDir); var settings = new DotNetTestSettings { Configuration = data.Configuration, NoBuild = true, NoRestore = true, Loggers = new[] { "trx" }, ResultsDirectory = testResultsDir, Verbosity = data.DotNetVerbosity }; DotNetTest("./src/Mahapps.Metro.Tests/Mahapps.Metro.Tests.csproj", settings); }); Task("StyleXaml") .Description("Ensures XAML Formatting is Clean") .Does(() => { Func<IFileSystemInfo, bool> exclude_Dir = fileSystemInfo => !fileSystemInfo.Path.Segments.Contains("obj") && !fileSystemInfo.Path.ToString().Contains("Styles/Themes"); var files = GetFiles(srcDir + "/**/*.xaml", new GlobberSettings { Predicate = exclude_Dir }); Information("\nChecking " + files.Count() + " file(s) for XAML Structure"); ExecuteProcess(styler, "-f \"" + string.Join(",", files.Select(f => f.ToString())) + "\" -c \"" + stylerFile + "\""); }); Task("CreateRelease") .WithCriteria<BuildData>((context, data) => !data.IsPullRequest) .Does<BuildData>(data => { var token = EnvironmentVariable("GITHUB_TOKEN"); if (string.IsNullOrEmpty(token)) { throw new Exception("The GITHUB_TOKEN environment variable is not defined."); } GitReleaseManagerCreate(token, "MahApps", repoName, new GitReleaseManagerCreateSettings { Milestone = data.GitVersion.MajorMinorPatch, Name = data.GitVersion.AssemblySemFileVer, Prerelease = data.IsPrerelease, TargetCommitish = data.GitVersion.BranchName, WorkingDirectory = "." }); }); /////////////////////////////////////////////////////////////////////////////// // HELPER /////////////////////////////////////////////////////////////////////////////// void SignFiles(IEnumerable<FilePath> files, string description) { var vurl = EnvironmentVariable("azure-key-vault-url"); if(string.IsNullOrWhiteSpace(vurl)) { Error("Could not resolve signing url."); return; } var vcid = EnvironmentVariable("azure-key-vault-client-id"); if(string.IsNullOrWhiteSpace(vcid)) { Error("Could not resolve signing client id."); return; } var vctid = EnvironmentVariable("azure-key-vault-tenant-id"); if(string.IsNullOrWhiteSpace(vctid)) { Error("Could not resolve signing client tenant id."); return; } var vcs = EnvironmentVariable("azure-key-vault-client-secret"); if(string.IsNullOrWhiteSpace(vcs)) { Error("Could not resolve signing client secret."); return; } var vc = EnvironmentVariable("azure-key-vault-certificate"); if(string.IsNullOrWhiteSpace(vc)) { Error("Could not resolve signing certificate."); return; } var filesToSign = string.Join(" ", files.Select(f => MakeAbsolute(f).FullPath)); var azureSignTool = Context.Tools.Resolve("azuresigntool.exe"); ExecuteProcess(azureSignTool, new ProcessArgumentBuilder() .Append("sign") .Append(filesToSign) .AppendSwitchQuoted("--file-digest", "sha256") .AppendSwitchQuoted("--description", description) .AppendSwitchQuoted("--description-url", "https://github.com/MahApps/MahApps.Metro") .Append("--no-page-hashing") .AppendSwitchQuoted("--timestamp-rfc3161", "http://timestamp.digicert.com") .AppendSwitchQuoted("--timestamp-digest", "sha256") .AppendSwitchQuoted("--azure-key-vault-url", vurl) .AppendSwitchQuotedSecret("--azure-key-vault-client-id", vcid) .AppendSwitchQuotedSecret("--azure-key-vault-tenant-id", vctid) .AppendSwitchQuotedSecret("--azure-key-vault-client-secret", vcs) .AppendSwitchQuotedSecret("--azure-key-vault-certificate", vc) ); } void ExecuteProcess(FilePath fileName, ProcessArgumentBuilder arguments, string workingDirectory = null) { if (!FileExists(fileName)) { throw new Exception($"File not found: {fileName}"); } var processSettings = new ProcessSettings { RedirectStandardOutput = true, RedirectStandardError = true, Arguments = arguments }; if (!string.IsNullOrEmpty(workingDirectory)) { processSettings.WorkingDirectory = workingDirectory; } Information($"Arguments: {arguments.RenderSafe()}"); using(var process = StartAndReturnProcess(fileName, processSettings)) { process.WaitForExit(); if (process.GetStandardOutput().Any()) { Information($"Output:{Environment.NewLine} {string.Join(Environment.NewLine, process.GetStandardOutput())}"); } if (process.GetStandardError().Any()) { // Information($"Errors occurred:{Environment.NewLine} {string.Join(Environment.NewLine, process.GetStandardError())}"); throw new Exception($"Errors occurred:{Environment.NewLine} {string.Join(Environment.NewLine, process.GetStandardError())}"); } // This should output 0 as valid arguments supplied var exitCode = process.GetExitCode(); Information($"Exit code: {exitCode}"); if (exitCode > 0) { throw new Exception($"Exit code: {exitCode}"); } } } /////////////////////////////////////////////////////////////////////////////// // TASK TARGETS /////////////////////////////////////////////////////////////////////////////// Task("Default") .IsDependentOn("Clean") .IsDependentOn("Restore") .IsDependentOn("StyleXaml") .IsDependentOn("Build") .IsDependentOn("Tests") ; Task("ci") .IsDependentOn("Default") .IsDependentOn("Sign") .IsDependentOn("Pack") .IsDependentOn("SignNuGet") .IsDependentOn("Zip") ; /////////////////////////////////////////////////////////////////////////////// // EXECUTION /////////////////////////////////////////////////////////////////////////////// RunTarget(target);