/// <summary> /// Executes an automated build. Called from <c>Main</c>. /// </summary> /// <param name="args">The command-line arguments from <c>Main</c>.</param> /// <param name="initialize">Called to initialize the build.</param> /// <returns>The exit code for the build.</returns> public static async Task <int> ExecuteAsync(string[] args, Action <BuildApp> initialize) { if (args is null) { throw new ArgumentNullException(nameof(args)); } if (initialize is null) { throw new ArgumentNullException(nameof(initialize)); } if (Assembly.GetEntryAssembly()?.EntryPoint?.ReturnType == typeof(void)) { Console.Error.WriteLine("Application entry point returns void; it should return the result of BuildRunner.Execute."); return(2); } var commandLineApp = new CommandLineApplication(); var buildApp = new BuildApp(commandLineApp); try { initialize(buildApp); } catch (Exception exception) when(IsMessageOnlyException(exception)) { Console.Error.WriteLine(exception.Message); return(2); } var dryRunFlag = buildApp.AddFlag("-n|--dry-run", "Don't execute target actions"); var skipDependenciesFlag = buildApp.AddFlag("-s|--skip-dependencies", "Don't run any target dependencies"); var skipOption = buildApp.AddOption("--skip <targets>", "Skip the comma-delimited target dependencies"); var noColorFlag = buildApp.AddFlag("--no-color", "Disable color output"); var showTreeFlag = buildApp.AddFlag("--show-tree", "Show the dependency tree"); var helpFlag = buildApp.AddFlag("-h|-?|--help", "Show build help"); var targetsArgument = commandLineApp.Argument("targets", "The targets to build", multipleValues: true); var bullseyeTargets = new Targets(); foreach (var target in buildApp.Targets) { bullseyeTargets.Add(name: target.Name, description: target.Description, dependsOn: target.Dependencies, action: target.RunAsync); } commandLineApp.OnExecuteAsync(async _ => { var targetNames = targetsArgument.Values.WhereNotNull().ToList(); if (targetNames.Count == 0 && buildApp.Targets.Any(x => x.Name == c_defaultTarget)) { targetNames.Add(c_defaultTarget); } var skipDependencies = skipDependenciesFlag.Value; if (skipOption.Value is not null && !skipDependencies) { var skipTargetNames = new HashSet <string>(skipOption.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); var targetNamesWithDependencies = new List <string>(); void AddTargetAndDependencies(BuildTarget target) { if (!skipTargetNames.Contains(target.Name) && !targetNamesWithDependencies.Contains(target.Name)) { foreach (var dependency in target.Dependencies.Select(name => buildApp.Targets.FirstOrDefault(x => x.Name == name)).WhereNotNull()) { AddTargetAndDependencies(dependency); } targetNamesWithDependencies.Add(target.Name); } } foreach (var targetName in targetNames) { var target = buildApp.Targets.FirstOrDefault(x => x.Name == targetName); if (target is not null) { AddTargetAndDependencies(target); } } targetNames = targetNamesWithDependencies; skipDependencies = true; } if (helpFlag.Value || (targetNames.Count == 0 && !showTreeFlag.Value)) { commandLineApp.ShowHelp(usePager: false); ShowTargets(buildApp.Targets); } else { var bullseyeArgs = targetNames.ToList(); if (noColorFlag.Value) { bullseyeArgs.Add("--no-color"); } if (skipDependencies) { bullseyeArgs.Add("--skip-dependencies"); } if (showTreeFlag.Value) { bullseyeArgs.Add("--list-tree"); } if (dryRunFlag.Value) { bullseyeArgs.Add("--dry-run"); } bullseyeArgs.Add("--no-extended-chars"); try { await bullseyeTargets.RunWithoutExitingAsync(bullseyeArgs, messageOnly: IsMessageOnlyException).ConfigureAwait(false); } catch (TargetFailedException) { return(1); } catch (Exception exception) when(IsMessageOnlyException(exception)) { Console.Error.WriteLine(exception.Message); return(2); } } return(0); }); try { return(await commandLineApp.ExecuteAsync(args).ConfigureAwait(false)); } catch (Exception exception) when(IsMessageOnlyException(exception)) { Console.Error.WriteLine(exception.Message); return(2); } }
/// <summary> /// Adds the standard .NET targets to the build. /// </summary> /// <param name="build">The build to which to add targets.</param> /// <param name="settings">The build settings.</param> public static void AddDotNetTargets(this BuildApp build, DotNetBuildSettings?settings = null) { settings ??= new DotNetBuildSettings(); var buildOptions = settings.BuildOptions ??= new DotNetBuildOptions(); buildOptions.ConfigurationOption ??= build.AddOption("-c|--configuration <name>", "The configuration to build (default Release)", "Release"); buildOptions.PlatformOption ??= build.AddOption("-p|--platform <name>", "The solution platform to build"); buildOptions.VerbosityOption ??= build.AddOption("-v|--verbosity <level>", "The build verbosity (q[uiet], m[inimal], n[ormal], d[etailed])"); buildOptions.VersionSuffixOption ??= build.AddOption("--version-suffix <suffix>", "Generates a prerelease package"); buildOptions.NuGetOutputOption ??= build.AddOption("--nuget-output <path>", "Directory for generated package (default release)", "release"); buildOptions.TriggerOption ??= build.AddOption("--trigger <name>", "The git branch or tag that triggered the build"); buildOptions.BuildNumberOption ??= build.AddOption("--build-number <number>", "The automated build number"); buildOptions.NoTestFlag ??= build.AddFlag("--no-test", "Skip the unit tests"); var solutionName = settings.SolutionName; var nugetSource = settings.NuGetSource ?? "https://api.nuget.org/v3/index.json"; var msbuildSettings = settings.MSBuildSettings; IReadOnlyList <string>?packagePaths = default; build.Target("clean") .Describe("Deletes all build output") .Does(() => { var findDirectoriesToDelete = settings.CleanSettings?.FindDirectoriesToDelete ?? (() => FindDirectories("{src,tests,tools}/**/{bin,obj}").Except(FindDirectories("tools/bin")).ToList()); foreach (var directoryToDelete in findDirectoriesToDelete()) { DeleteDirectory(directoryToDelete); } var extraProperties = settings.GetExtraPropertyArgs("clean"); if (msbuildSettings is null) { RunDotNet(new[] { "clean", solutionName, "-c", settings.GetConfiguration(), settings.GetPlatformArg(), settings.GetBuildNumberArg(), settings.GetVerbosityArg(), settings.GetMaxCpuCountArg() }.Concat(extraProperties)); } else { MSBuild(new[] { solutionName, "-t:Clean", settings.GetConfigurationArg(), settings.GetPlatformArg(), settings.GetBuildNumberArg(), settings.GetVerbosityArg(), settings.GetMaxCpuCountArg() }.Concat(extraProperties)); } }); build.Target("restore") .Describe("Restores NuGet packages") .Does(() => { var extraProperties = settings.GetExtraPropertyArgs("restore"); if (msbuildSettings is null) { RunDotNet(new[] { "restore", solutionName, settings.GetPlatformArg(), settings.GetBuildNumberArg(), settings.GetVerbosityArg(), settings.GetMaxCpuCountArg() }.Concat(extraProperties)); } else { MSBuild(new[] { solutionName, "-t:Restore", settings.GetConfigurationArg(), settings.GetPlatformArg(), settings.GetBuildNumberArg(), settings.GetVerbosityArg(), settings.GetMaxCpuCountArg() }.Concat(extraProperties)); } if (DotNetLocalTool.Any()) { RunDotNet("tool", "restore"); } }); build.Target("build") .DependsOn("restore") .Describe("Builds the solution") .Does(() => { var extraProperties = settings.GetExtraPropertyArgs("build"); if (msbuildSettings is null) { RunDotNet(new[] { "build", solutionName, "-c", settings.GetConfiguration(), settings.GetPlatformArg(), settings.GetBuildNumberArg(), "--no-restore", settings.GetVerbosityArg(), settings.GetMaxCpuCountArg(), settings.GetBuildSummaryArg() }.Concat(extraProperties)); } else { MSBuild(new[] { solutionName, settings.GetConfigurationArg(), settings.GetPlatformArg(), settings.GetBuildNumberArg(), settings.GetVerbosityArg(), settings.GetMaxCpuCountArg(), settings.GetBuildSummaryArg() }.Concat(extraProperties)); } }); build.Target("test") .DependsOn("build") .Describe("Runs the unit tests") .Does(() => { if (buildOptions.NoTestFlag.Value) { Console.WriteLine("Skipping unit tests due to --no-test."); } else { IReadOnlyList <string?> testPaths; if (settings.TestSettings?.FindAssemblies is var findAssemblies and not null) { testPaths = findAssemblies(settings); }