/// <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);
                }