Esempio n. 1
0
        /// <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>
        /// <param name="workingDirectory">The working directory for the build. (Optional.)</param>
        /// <param name="callerFilePath">The compiler-generated path to the source code of the caller.</param>
        /// <returns>The exit code for the build.</returns>
        /// <remarks>If <paramref name="workingDirectory"/> is omitted, the parent of the parent of the
        /// directory containing the source code of the caller is used.</remarks>
        public static int Execute(string[] args, Action <BuildApp> initialize, string?workingDirectory = null, [CallerFilePath] string?callerFilePath = null)
        {
            if (args == null)
            {
                throw new ArgumentNullException(nameof(args));
            }
            if (initialize == null)
            {
                throw new ArgumentNullException(nameof(initialize));
            }

            if (workingDirectory == null)
            {
                var callerFileDirectory = Path.GetDirectoryName(callerFilePath) ?? throw new ArgumentException("Invalid caller file path.", nameof(callerFilePath));
                workingDirectory = Path.GetFullPath(Path.Combine(callerFileDirectory, "..", ".."));
            }
            Directory.SetCurrentDirectory(workingDirectory);

            var commandLineApp = new CommandLineApplication();

            var buildApp = new BuildApp(commandLineApp);

            initialize(buildApp);

            var noColorFlag     = buildApp.AddFlag("--no-color", "Disable color output");
            var helpFlag        = buildApp.AddFlag("-h|-?|--help", "Show build help");
            var targetsArgument = commandLineApp.Argument("targets", "The targets to build", multipleValues: true);

            foreach (var target in buildApp.Targets)
            {
                Bullseye.Targets.Target(target.Name, target.Dependencies, target.Run);
            }

            commandLineApp.OnExecute(() =>
            {
                var targets = targetsArgument.Values.ToList();

                if (targets.Count == 0 && buildApp.Targets.Any(x => x.Name == c_defaultTarget))
                {
                    targets.Add(c_defaultTarget);
                }

                if (helpFlag.Value || targets.Count == 0)
                {
                    commandLineApp.ShowHelp(usePager: false);
                    ShowTargets(buildApp.Targets);
                }
                else
                {
                    if (noColorFlag.Value)
                    {
                        targets.Add("--no-color");
                    }

                    try
                    {
#pragma warning disable 618
                        Bullseye.Targets.RunTargets(targets);
#pragma warning restore 618
                    }
                    catch (Exception exception) when(exception.GetType().FullName == "Bullseye.Internal.TargetFailedException")
                    {
                        return(1);
                    }
                    catch (Exception exception) when(exception is ApplicationException || exception is CommandParsingException || exception.GetType().FullName == "Bullseye.Internal.InvalidUsageException")
                    {
                        Console.Error.WriteLine(exception.Message);
                        return(2);
                    }
                }

                return(0);
            });

            return(commandLineApp.Execute(args));
        }
Esempio n. 2
0
        /// <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 int Execute(string[] args, Action <BuildApp> initialize)
        {
            if (args == null)
            {
                throw new ArgumentNullException(nameof(args));
            }
            if (initialize == null)
            {
                throw new ArgumentNullException(nameof(initialize));
            }

            var commandLineApp = new CommandLineApplication();

            var buildApp = new BuildApp(commandLineApp);

            initialize(buildApp);

            var noColorFlag          = buildApp.AddFlag("--no-color", "Disable color output");
            var skipDependenciesFlag = buildApp.AddFlag("-s|--skip-dependencies", "Don't run target dependencies");
            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(target.Name, target.Dependencies, target.Run);
            }

            commandLineApp.OnExecute(() =>
            {
                var bullseyeArgs = targetsArgument.Values.ToList();

                if (bullseyeArgs.Count == 0 && buildApp.Targets.Any(x => x.Name == c_defaultTarget))
                {
                    bullseyeArgs.Add(c_defaultTarget);
                }

                if (helpFlag.Value || bullseyeArgs.Count == 0)
                {
                    commandLineApp.ShowHelp(usePager: false);
                    ShowTargets(buildApp.Targets);
                }
                else
                {
                    if (noColorFlag.Value)
                    {
                        bullseyeArgs.Add("--no-color");
                    }
                    if (skipDependenciesFlag.Value)
                    {
                        bullseyeArgs.Add("--skip-dependencies");
                    }
                    bullseyeArgs.Add("--no-extended-chars");

                    try
                    {
                        bullseyeTargets.RunWithoutExiting(bullseyeArgs);
                    }
                    catch (TargetFailedException)
                    {
                        return(1);
                    }
                    catch (Exception exception) when(exception is ApplicationException || exception is CommandParsingException || exception is InvalidUsageException)
                    {
                        Console.Error.WriteLine(exception.Message);
                        return(2);
                    }
                }

                return(0);
            });

            return(commandLineApp.Execute(args));
        }
Esempio n. 3
0
        /// <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 ?? (settings.BuildOptions = new DotNetBuildOptions());
            var configurationOption = buildOptions.ConfigurationOption ?? (buildOptions.ConfigurationOption =
                                                                               build.AddOption("-c|--configuration <name>", "The configuration to build (default Release)", "Release"));
            var platformOption = buildOptions.PlatformOption ?? (buildOptions.PlatformOption =
                                                                     build.AddOption("-p|--platform <name>", "The solution platform to build"));
            var versionSuffixOption = buildOptions.VersionSuffixOption ?? (buildOptions.VersionSuffixOption =
                                                                               build.AddOption("--version-suffix <suffix>", "Generates a prerelease package"));
            var nugetOutputOption = buildOptions.NuGetOutputOption ?? (buildOptions.NuGetOutputOption =
                                                                           build.AddOption("--nuget-output <path>", "Directory for generated package (default release)", "release"));
            var triggerOption = buildOptions.TriggerOption ?? (buildOptions.TriggerOption =
                                                                   build.AddOption("--trigger <name>", "The git branch or tag that triggered the build"));
            var buildNumberOption = buildOptions.BuildNumberOption ?? (buildOptions.BuildNumberOption =
                                                                           build.AddOption("--build-number <number>", "The automated build number"));
            var noTestFlag = buildOptions.NoTestFlag ?? (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;

            var dotNetTools           = settings.DotNetTools ?? new DotNetTools(Path.Combine("tools", "bin"));
            var xmlDocMarkdownVersion = settings.DocsSettings?.ToolVersion ?? "2.0.1";

            var    packagePaths          = new List <string>();
            string?trigger               = null;
            var    ignoreIfAlreadyPushed = false;

            build.Target("clean")
            .Describe("Deletes all build output")
            .Does(() =>
            {
                var findDirectoriesToDelete = settings.CleanSettings?.FindDirectoriesToDelete ?? (() => FindDirectories("{src,tests}/**/{bin,obj}", "tools/XmlDocTarget/{bin,obj}"));
                foreach (var directoryToDelete in findDirectoriesToDelete())
                {
                    deleteDirectory(directoryToDelete);
                }

                var extraProperties = getExtraProperties("clean");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "clean", solutionName, "-c", configurationOption.Value, getPlatformArg(), "--verbosity", "normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    runMSBuild(new[] { solutionName, "-t:Clean", $"-p:Configuration={configurationOption.Value}", getPlatformArg(), "-v:normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
            });

            build.Target("restore")
            .Describe("Restores NuGet packages")
            .Does(() =>
            {
                var extraProperties = getExtraProperties("restore");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "restore", solutionName, getPlatformArg(), "--verbosity", "normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    runMSBuild(new[] { solutionName, "-t:Restore", $"-p:Configuration={configurationOption.Value}", getPlatformArg(), "-v:normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
            });

            build.Target("build")
            .DependsOn("restore")
            .Describe("Builds the solution")
            .Does(() =>
            {
                var buildNumberArg = buildNumberOption.Value == null ? null : $"-p:BuildNumber={buildNumberOption.Value}";

                var extraProperties = getExtraProperties("build");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "build", solutionName, "-c", configurationOption.Value, getPlatformArg(), buildNumberArg, "--no-restore", "--verbosity", "normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    runMSBuild(new[] { solutionName, $"-p:Configuration={configurationOption.Value}", getPlatformArg(), buildNumberArg, "-v:normal", getMaxCpuCountArg() }.Concat(extraProperties));
                }
            });

            build.Target("test")
            .DependsOn("build")
            .Describe("Runs the unit tests")
            .Does(() =>
            {
                if (noTestFlag.Value)
                {
                    Console.WriteLine("Skipping unit tests due to --no-test.");
                }
                else
                {
                    var extraProperties    = getExtraProperties("test").ToList();
                    var findTestAssemblies = settings.TestSettings?.FindTestAssemblies;
                    if (findTestAssemblies != null)
                    {
                        foreach (var testAssembly in findTestAssemblies())
                        {
                            if (settings.TestSettings?.RunTests != null)
                            {
                                settings.TestSettings.RunTests(testAssembly);
                            }
                            else
                            {
                                RunDotNet(new AppRunnerSettings {
                                    Arguments = new[] { "vstest", Path.GetFileName(testAssembly) }.Concat(extraProperties), WorkingDirectory = Path.GetDirectoryName(testAssembly)
                                });
                            }
                        }
                    }
                    else
                    {
                        var testProjects = new List <string?>();

                        var findTestProjects = settings.TestSettings?.FindProjects;
                        if (findTestProjects != null)
                        {
                            testProjects.AddRange(findTestProjects());
                        }
                        else
                        {
                            testProjects.Add(solutionName);
                        }

                        foreach (var testProject in testProjects)
                        {
                            if (settings.TestSettings?.RunTests != null)
                            {
                                settings.TestSettings.RunTests(testProject);
                            }
                            else
                            {
                                RunDotNet(new[] { "test", testProject, "-c", configurationOption.Value, getPlatformArg(), "--no-build", getMaxCpuCountArg() }.Concat(extraProperties));
                            }
                        }
                    }
                }
            });

            build.Target("package")
            .DependsOn("clean", "test")
            .Describe("Builds NuGet packages")
            .Does(() =>
            {
                trigger = triggerOption.Value;

                if (trigger == "detect")
                {
                    using var repository = new Repository(".");
                    var headSha          = repository.Head.Tip.Sha;
                    var autoTrigger      = GetBestTriggerFromTags(repository.Tags.Where(x => x.Target.Sha == headSha).Select(x => x.FriendlyName).ToList());
                    if (autoTrigger != null)
                    {
                        trigger = autoTrigger;
                        ignoreIfAlreadyPushed = true;
                        Console.WriteLine($"Detected trigger: {trigger}");
                    }
                }

                var versionSuffix = versionSuffixOption.Value;
                if (versionSuffix == null && trigger != null)
                {
                    versionSuffix = GetVersionFromTrigger(trigger) is string triggerVersion ? SplitVersion(triggerVersion).Suffix : null;
                }

                var nugetOutputPath = Path.GetFullPath(nugetOutputOption.Value);
                var tempOutputPath  = Path.Combine(nugetOutputPath, $"temp_{Guid.NewGuid():N}");

                var packageProjects = new List <string?>();

                var findPackageProjects = settings.PackageSettings?.FindProjects;
                if (findPackageProjects != null)
                {
                    packageProjects.AddRange(findPackageProjects());
                }
                else
                {
                    packageProjects.Add(solutionName);
                }

                var extraProperties = getExtraProperties("package").ToList();
                foreach (var packageProject in packageProjects)
                {
                    if (msbuildSettings == null)
                    {
                        RunDotNet(new[]
                        {
                            "pack", packageProject,
                            "-c", configurationOption.Value,
                            getPlatformArg(),
                            "--no-build",
                            "--output", tempOutputPath,
                            versionSuffix != null ? "--version-suffix" : null, versionSuffix,
                            getMaxCpuCountArg()
                        }.Concat(extraProperties));
                    }
                    else
                    {
                        runMSBuild(new[]
                        {
                            packageProject, "-t:Pack",
                            $"-p:Configuration={configurationOption.Value}",
                            getPlatformArg(),
                            "-p:NoBuild=true",
                            $"-p:PackageOutputPath={tempOutputPath}",
                            versionSuffix != null ? $"-p:VersionSuffix={versionSuffix}" : null,
                            "-v:normal",
                            getMaxCpuCountArg()
                        }.Concat(extraProperties));
                    }
                }

                var tempPackagePaths = FindFilesFrom(tempOutputPath, "*.nupkg");
                foreach (var tempPackagePath in tempPackagePaths)
                {
                    var packagePath = Path.Combine(nugetOutputPath, Path.GetFileName(tempPackagePath) ?? throw new InvalidOperationException());
                    if (File.Exists(packagePath))
                    {
                        File.Delete(packagePath);
                    }
                    File.Move(tempPackagePath, packagePath);
                    packagePaths.Add(packagePath);
                }
                deleteDirectory(tempOutputPath);

                if (packagePaths.Count == 0)
                {
                    throw new ApplicationException("No NuGet packages created.");
                }
            });

            build.Target("publish")
            .Describe("Publishes NuGet packages and documentation")
            .DependsOn("package")
            .Does(() =>
            {
                if (packagePaths.Count == 0)
                {
                    throw new ApplicationException("No NuGet packages found.");
                }

                if (trigger == null)
                {
                    throw new ApplicationException("--trigger option required.");
                }

                var triggerParts          = trigger.Split('-');
                var publishTrigger        = triggerParts.Length >= 2 && triggerParts[0] == "publish" ? triggerParts[1] : null;
                var shouldPublishPackages = publishTrigger == "package" || publishTrigger == "packages" || publishTrigger == "all";
                var shouldPublishDocs     = publishTrigger == "docs" || publishTrigger == "all";

                var triggerVersion = GetVersionFromTrigger(trigger);
                if (triggerVersion != null)
                {
                    var mismatches = packagePaths.Where(x => GetPackageInfo(x).Version != triggerVersion).ToList();
                    if (mismatches.Count != 0)
                    {
                        throw new ApplicationException($"Trigger '{trigger}' doesn't match package version: {string.Join(", ", mismatches.Select(Path.GetFileName))}");
                    }

                    shouldPublishPackages = true;
                    shouldPublishDocs     = triggerVersion.IndexOf('-') == -1;
                }

                if (shouldPublishPackages || shouldPublishDocs)
                {
                    var docsSettings      = settings.DocsSettings;
                    var shouldPushDocs    = false;
                    string?cloneDirectory = null;
                    string?repoDirectory  = null;
                    string?gitBranchName  = null;

                    Credentials provideCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) =>
                    new UsernamePasswordCredentials
                    {
                        Username = docsSettings?.GitLogin?.Username ?? throw new ApplicationException("GitLogin has a null Username."),
                        Password = docsSettings?.GitLogin?.Password ?? throw new ApplicationException("GitLogin has a null Password."),
                    };

                    if (shouldPublishDocs && docsSettings != null)
                    {
                        if (docsSettings.GitLogin == null || docsSettings.GitAuthor == null)
                        {
                            throw new ApplicationException("GitLogin and GitAuthor must be set on DocsSettings.");
                        }

                        var gitRepositoryUrl = docsSettings.GitRepositoryUrl;
                        gitBranchName        = docsSettings.GitBranchName;

                        if (gitRepositoryUrl != null)
                        {
                            cloneDirectory = "docs_repo_" + Path.GetRandomFileName();
                            Repository.Clone(sourceUrl: gitRepositoryUrl, workdirPath: cloneDirectory,
                                             options: new CloneOptions {
                                BranchName = gitBranchName, CredentialsProvider = provideCredentials
                            });
                            repoDirectory = cloneDirectory;
                        }
                        else
                        {
                            repoDirectory = ".";
                        }

                        using var repository = new Repository(repoDirectory);
                        if (gitRepositoryUrl != null)
                        {
                            if (gitBranchName == null)
                            {
                                gitBranchName = repository.Head.FriendlyName;
                            }
                        }
                        else if (gitBranchName != null)
                        {
                            if (gitBranchName != repository.Head.FriendlyName)
                            {
                                var branch = repository.Branches[gitBranchName] ?? repository.CreateBranch(gitBranchName);
                                Commands.Checkout(repository, branch);
                            }
                        }
                        else
                        {
                            var branch = repository.Branches.FirstOrDefault(x => x.IsCurrentRepositoryHead);
                            if (branch == null)
                            {
                                var autoBranchName = Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH");
                                if (autoBranchName != null)
                                {
                                    branch = repository.Branches[autoBranchName] ?? repository.CreateBranch(autoBranchName);
                                }
                                else
                                {
                                    branch = repository.Branches.FirstOrDefault(x => x.Tip.Sha == repository.Head.Tip.Sha);
                                }
                                if (branch != null)
                                {
                                    Commands.Checkout(repository, branch);
                                }
                            }
                            if (branch == null)
                            {
                                throw new ArgumentException("Could not determine repository branch for publishing docs.");
                            }
                            gitBranchName = branch.FriendlyName;
                        }

                        var projectHasDocs = docsSettings.ProjectHasDocs ?? (_ => true);
                        foreach (var projectName in packagePaths.Select(x => GetPackageInfo(x).Name).Where(projectHasDocs))
                        {
                            string findAssembly(string name) =>
                            FindFiles($"tools/XmlDocTarget/bin/**/{name}.dll").OrderByDescending(File.GetLastWriteTime).FirstOrDefault() ??
                            FindFiles($"src/{name}/bin/**/{name}.dll").OrderByDescending(File.GetLastWriteTime).FirstOrDefault();

                            var assemblyPaths = new List <string>();
                            if (docsSettings.FindAssemblies != null)
                            {
                                assemblyPaths.AddRange(docsSettings.FindAssemblies(projectName));
                            }
                            else
                            {
                                var assemblyPath = (docsSettings.FindAssembly ?? findAssembly)(projectName);
                                if (assemblyPath != null)
                                {
                                    assemblyPaths.Add(assemblyPath);
                                }
                            }

                            if (assemblyPaths.Count != 0)
                            {
                                foreach (var assemblyPath in assemblyPaths)
                                {
                                    RunApp(dotNetTools.GetToolPath($"xmldocmd/{xmlDocMarkdownVersion}"), assemblyPath,
                                           Path.Combine(repoDirectory, docsSettings.TargetDirectory ?? "docs"),
                                           "--source", $"{docsSettings.SourceCodeUrl}/{projectName}", "--newline", "lf", "--clean");
                                }
                            }
                            else
                            {
                                Console.WriteLine($"Documentation not generated for {projectName}; assembly not found.");
                            }
                        }

                        shouldPushDocs = repository.RetrieveStatus().IsDirty;
                    }

                    if (shouldPublishPackages)
                    {
                        var nugetApiKey = settings.NuGetApiKey;
                        if (string.IsNullOrEmpty(nugetApiKey))
                        {
                            throw new ApplicationException("NuGetApiKey required to publish.");
                        }

                        if (ignoreIfAlreadyPushed)
                        {
                            var nugetSettings            = NuGet.Configuration.Settings.LoadDefaultSettings(root: null);
                            var packageSourceProvider    = new PackageSourceProvider(nugetSettings);
                            var sourceRepositoryProvider = new SourceRepositoryProvider(packageSourceProvider, NuGet.Protocol.Core.Types.Repository.Provider.GetCoreV3());
                            using var sourceCacheContext = new SourceCacheContext();
                            var nugetRepositories        = sourceRepositoryProvider.GetRepositories()
                                                           .Select(x => x.GetResourceAsync <DependencyInfoResource>().GetAwaiter().GetResult())
                                                           .ToList();

                            foreach (var packagePath in packagePaths.ToList())
                            {
                                var packageInfo = GetPackageInfo(packagePath);
                                var package     = new PackageIdentity(packageInfo.Name, NuGetVersion.Parse(packageInfo.Version));

                                foreach (var nugetRepository in nugetRepositories)
                                {
                                    var dependencyInfo = nugetRepository.ResolvePackage(package, NuGetFramework.AnyFramework,
                                                                                        sourceCacheContext, NullLogger.Instance, CancellationToken.None).GetAwaiter().GetResult();
                                    if (dependencyInfo != null)
                                    {
                                        Console.WriteLine($"Package already pushed: {packageInfo.Name} {packageInfo.Version}");
                                        packagePaths.Remove(packagePath);
                                        break;
                                    }
                                }
                            }
                        }

                        foreach (var packagePath in packagePaths)
                        {
                            RunDotNet("nuget", "push", packagePath, "--source", nugetSource, "--api-key", nugetApiKey);
                        }
                    }

                    if (shouldPushDocs)
                    {
                        using var repository = new Repository(repoDirectory);
                        Console.WriteLine("Publishing documentation changes.");
                        Commands.Stage(repository, "*");
                        var author = new Signature(docsSettings !.GitAuthor !.Name, docsSettings !.GitAuthor !.Email, DateTimeOffset.Now);
                        repository.Commit("Documentation updated.", author, author, new CommitOptions());
                        repository.Network.Push(repository.Network.Remotes["origin"],
                                                $"refs/heads/{gitBranchName}", new PushOptions {
                            CredentialsProvider = provideCredentials
                        });
                    }

                    if (cloneDirectory != null)
                    {
                        // delete the cloned directory
                        foreach (var fileInfo in FindFiles(cloneDirectory, "**").Select(x => new FileInfo(x)).Where(x => x.IsReadOnly))
                        {
                            fileInfo.IsReadOnly = false;
                        }
                        deleteDirectory(cloneDirectory);
                    }
                }
                else
                {
                    Console.WriteLine("To publish to NuGet, push this tag: v" + GetPackageInfo(packagePaths[0]).Version);
                }
            });

            string?getPlatformArg()
            {
                var platformValue = platformOption?.Value ?? settings?.SolutionPlatform;

                return(platformValue == null ? null : $"-p:Platform={platformValue}");
            }

            string?getMaxCpuCountArg()
            {
                if (settings !.MaxCpuCount != null)
                {
                    return($"-maxcpucount:{settings.MaxCpuCount}");
                }
Esempio n. 4
0
        /// <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 ?? (settings.BuildOptions = new DotNetBuildOptions());
            var configurationOption = buildOptions.ConfigurationOption ?? (buildOptions.ConfigurationOption =
                                                                               build.AddOption("-c|--configuration <name>", "The configuration to build (default Release)", "Release"));
            var platformOption = buildOptions.PlatformOption ?? (buildOptions.PlatformOption =
                                                                     build.AddOption("-p|--platform <name>", "The solution platform to build"));
            var verbosityOption = buildOptions.VerbosityOption ?? (buildOptions.VerbosityOption =
                                                                       build.AddOption("-v|--verbosity <level>", "The build verbosity (q[uiet], m[inimal], n[ormal], d[etailed])"));
            var versionSuffixOption = buildOptions.VersionSuffixOption ?? (buildOptions.VersionSuffixOption =
                                                                               build.AddOption("--version-suffix <suffix>", "Generates a prerelease package"));
            var nugetOutputOption = buildOptions.NuGetOutputOption ?? (buildOptions.NuGetOutputOption =
                                                                           build.AddOption("--nuget-output <path>", "Directory for generated package (default release)", "release"));
            var triggerOption = buildOptions.TriggerOption ?? (buildOptions.TriggerOption =
                                                                   build.AddOption("--trigger <name>", "The git branch or tag that triggered the build"));
            var buildNumberOption = buildOptions.BuildNumberOption ?? (buildOptions.BuildNumberOption =
                                                                           build.AddOption("--build-number <number>", "The automated build number"));
            var noTestFlag = buildOptions.NoTestFlag ?? (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;

            var    packagePaths          = new List <string>();
            string?trigger               = null;
            var    ignoreIfAlreadyPushed = false;

            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 verbosity       = GetVerbosity();
                var extraProperties = GetExtraProperties("clean");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "clean", solutionName, "-c", configurationOption.Value, GetPlatformArg(), "--verbosity", verbosity, GetMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    MSBuild(new[] { solutionName, "-t:Clean", $"-p:Configuration={configurationOption.Value}", GetPlatformArg(), $"-v:{verbosity}", GetMaxCpuCountArg() }.Concat(extraProperties));
                }
            });

            build.Target("restore")
            .Describe("Restores NuGet packages")
            .Does(() =>
            {
                var verbosity       = GetVerbosity();
                var extraProperties = GetExtraProperties("restore");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "restore", solutionName, GetPlatformArg(), "--verbosity", verbosity, GetMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    MSBuild(new[] { solutionName, "-t:Restore", $"-p:Configuration={configurationOption.Value}", GetPlatformArg(), $"-v:{verbosity}", GetMaxCpuCountArg() }.Concat(extraProperties));
                }

                if (DotNetLocalTool.Any())
                {
                    RunDotNet("tool", "restore");
                }
            });

            build.Target("build")
            .DependsOn("restore")
            .Describe("Builds the solution")
            .Does(() =>
            {
                var buildNumberArg = GetBuildNumberArg();

                var verbosity       = GetVerbosity();
                var extraProperties = GetExtraProperties("build");
                if (msbuildSettings == null)
                {
                    RunDotNet(new[] { "build", solutionName, "-c", configurationOption.Value, GetPlatformArg(), buildNumberArg, "--no-restore", "--verbosity", verbosity, GetMaxCpuCountArg() }.Concat(extraProperties));
                }
                else
                {
                    MSBuild(new[] { solutionName, $"-p:Configuration={configurationOption.Value}", GetPlatformArg(), buildNumberArg, $"-v:{verbosity}", GetMaxCpuCountArg() }.Concat(extraProperties));
                }
            });

            build.Target("test")
            .DependsOn("build")
            .Describe("Runs the unit tests")
            .Does(() =>
            {
                if (noTestFlag.Value)
                {
                    Console.WriteLine("Skipping unit tests due to --no-test.");
                }
                else
                {
                    var extraProperties    = GetExtraProperties("test").ToList();
                    var findTestAssemblies = settings.TestSettings?.FindTestAssemblies;
                    if (findTestAssemblies != null)
                    {
                        foreach (var testAssembly in findTestAssemblies())
                        {
                            if (settings.TestSettings?.RunTests != null)
                            {
                                settings.TestSettings.RunTests(testAssembly);
                            }
                            else
                            {
                                RunDotNet(new AppRunnerSettings {
                                    Arguments = new[] { "vstest", Path.GetFileName(testAssembly) }.Concat(extraProperties), WorkingDirectory = Path.GetDirectoryName(testAssembly)
                                });
                            }
                        }
                    }
                    else
                    {
                        var testProjects = new List <string?>();

                        var findTestProjects = settings.TestSettings?.FindProjects;
                        if (findTestProjects != null)
                        {
                            testProjects.AddRange(findTestProjects());
                        }
                        else
                        {
                            testProjects.Add(solutionName);
                        }

                        foreach (var testProject in testProjects)
                        {
                            if (settings.TestSettings?.RunTests != null)
                            {
                                settings.TestSettings.RunTests(testProject);
                            }
                            else
                            {
                                RunDotNet(new[] { "test", testProject, "-c", configurationOption.Value, GetPlatformArg(), "--no-build", GetMaxCpuCountArg() }.Concat(extraProperties));
                            }
                        }
                    }
                }
            });

            build.Target("package")
            .DependsOn("clean", "test")
            .Describe("Builds NuGet packages")
            .Does(() =>
            {
                trigger = triggerOption.Value;

                if (trigger == "detect")
                {
                    using var repository = new Repository(".");
                    var headSha          = repository.Head.Tip.Sha;
                    var autoTrigger      = GetBestTriggerFromTags(repository.Tags.Where(x => x.Target.Sha == headSha).Select(x => x.FriendlyName).ToList());
                    if (autoTrigger != null)
                    {
                        trigger = autoTrigger;
                        ignoreIfAlreadyPushed = true;
                        Console.WriteLine($"Detected trigger: {trigger}");
                    }
                }

                var versionSuffix = versionSuffixOption.Value;
                if (versionSuffix == null && trigger != null)
                {
                    versionSuffix = GetVersionFromTrigger(trigger) is string triggerVersion ? SplitVersion(triggerVersion).Suffix : null;
                }

                var nugetOutputPath = Path.GetFullPath(nugetOutputOption.Value !);
                var tempOutputPath  = Path.Combine(nugetOutputPath, Path.GetRandomFileName());

                var packageProjects = new List <string?>();

                var findPackageProjects = settings.PackageSettings?.FindProjects;
                if (findPackageProjects != null)
                {
                    packageProjects.AddRange(findPackageProjects());
                }
                else
                {
                    packageProjects.Add(solutionName);
                }

                var extraProperties = GetExtraProperties("package").ToList();
                foreach (var packageProject in packageProjects)
                {
                    if (msbuildSettings == null)
                    {
                        RunDotNet(new[]
                        {
                            "pack", packageProject,
                            "-c", configurationOption.Value,
                            GetPlatformArg(),
                            "--no-build",
                            "--output", tempOutputPath,
                            versionSuffix != null ? "--version-suffix" : null, versionSuffix,
                            GetMaxCpuCountArg(),
                        }.Concat(extraProperties));
                    }
                    else
                    {
                        MSBuild(new[]
                        {
                            packageProject, "-t:Pack",
                            $"-p:Configuration={configurationOption.Value}",
                            GetPlatformArg(),
                            "-p:NoBuild=true",
                            $"-p:PackageOutputPath={tempOutputPath}",
                            versionSuffix != null ? $"-p:VersionSuffix={versionSuffix}" : null,
                            $"-v:{GetVerbosity()}",
                            GetMaxCpuCountArg(),
                        }.Concat(extraProperties));
                    }
                }

                var tempPackagePaths = FindFilesFrom(tempOutputPath, "*.nupkg");
                foreach (var tempPackagePath in tempPackagePaths)
                {
                    var packagePath = Path.Combine(nugetOutputPath, Path.GetFileName(tempPackagePath) ?? throw new InvalidOperationException());
                    if (File.Exists(packagePath))
                    {
                        File.Delete(packagePath);
                    }
                    File.Move(tempPackagePath, packagePath);
                    packagePaths.Add(packagePath);
                    Console.WriteLine($"NuGet package: {packagePath}");
                }
                DeleteDirectory(tempOutputPath);

                if (packagePaths.Count == 0)
                {
                    throw new BuildException("No NuGet packages created.");
                }
            });

            build.Target("publish")
            .Describe("Publishes NuGet packages and documentation")
            .DependsOn("package")
            .Does(() =>
            {
                if (packagePaths.Count == 0)
                {
                    throw new BuildException("No NuGet packages found.");
                }

                if (trigger is null)
                {
                    if (packagePaths.Any(x => GetPackageInfo(x).Version == "0.0.0"))
                    {
                        Console.WriteLine("Not publishing package with version 0.0.0. Change package version to publish.");
                        return;
                    }

                    trigger = "publish-all";
                }

                var triggerParts          = trigger.Split('-');
                var publishTrigger        = triggerParts.Length >= 2 && triggerParts[0] == "publish" ? triggerParts[1] : null;
                var shouldPublishPackages = publishTrigger == "package" || publishTrigger == "packages" || publishTrigger == "all";
                var shouldPublishDocs     = publishTrigger == "docs" || publishTrigger == "all";
                var shouldSkipDuplicates  = publishTrigger == "all";

                var triggerVersion = GetVersionFromTrigger(trigger);
                if (triggerVersion != null)
                {
                    var mismatches = packagePaths.Where(x => GetPackageInfo(x).Version != triggerVersion).ToList();
                    if (mismatches.Count != 0)
                    {
                        throw new BuildException($"Trigger '{trigger}' doesn't match package version: {string.Join(", ", mismatches.Select(Path.GetFileName))}");
                    }

                    shouldPublishPackages = true;
                    shouldPublishDocs     = triggerVersion.IndexOf('-') == -1;
                }

                if (shouldPublishPackages || shouldPublishDocs)
                {
                    var docsSettings      = settings.DocsSettings;
                    var shouldPushDocs    = false;
                    string?cloneDirectory = null;
                    string?repoDirectory  = null;
                    string?gitBranchName  = null;

                    if (shouldPublishDocs && docsSettings != null)
                    {
                        if (docsSettings.GitLogin == null || docsSettings.GitAuthor == null)
                        {
                            throw new BuildException("GitLogin and GitAuthor must be set on DocsSettings.");
                        }

                        var gitRepositoryUrl = docsSettings.GitRepositoryUrl;
                        gitBranchName        = docsSettings.GitBranchName;

                        if (gitRepositoryUrl != null)
                        {
                            cloneDirectory = "docs_repo_" + Path.GetRandomFileName();
                            Repository.Clone(sourceUrl: gitRepositoryUrl, workdirPath: cloneDirectory,
                                             options: new CloneOptions {
                                BranchName = gitBranchName, CredentialsProvider = ProvideCredentials
                            });
                            repoDirectory = cloneDirectory;
                        }
                        else
                        {
                            repoDirectory = ".";
                        }

                        using var repository = new Repository(repoDirectory);
                        if (gitRepositoryUrl != null)
                        {
                            gitBranchName ??= repository.Head.FriendlyName;
                        }
                        else if (gitBranchName != null)
                        {
                            if (gitBranchName != repository.Head.FriendlyName)
                            {
                                var branch = repository.Branches[gitBranchName] ?? repository.CreateBranch(gitBranchName);
                                Commands.Checkout(repository, branch);
                            }
                        }
                        else
                        {
                            var branch = repository.Branches.FirstOrDefault(x => x.IsCurrentRepositoryHead);
                            if (branch == null)
                            {
                                var autoBranchName = Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH");

                                if (autoBranchName == null)
                                {
                                    var gitRef          = Environment.GetEnvironmentVariable("GITHUB_REF");
                                    const string prefix = "refs/heads/";
                                    if (gitRef != null && gitRef.StartsWith(prefix, StringComparison.Ordinal))
                                    {
                                        autoBranchName = gitRef.Substring(prefix.Length);
                                    }
                                }

                                if (autoBranchName != null)
                                {
                                    branch = repository.Branches[autoBranchName] ?? repository.CreateBranch(autoBranchName);
                                }
                                else
                                {
                                    branch = repository.Branches.FirstOrDefault(x => x.Tip.Sha == repository.Head.Tip.Sha);
                                }

                                if (branch != null)
                                {
                                    Commands.Checkout(repository, branch);
                                }
                            }
                            if (branch == null)
                            {
                                throw new BuildException("Could not determine repository branch for publishing docs.");
                            }
                            gitBranchName = branch.FriendlyName;
                        }

                        var docsPath = Path.Combine(repoDirectory, docsSettings.TargetDirectory ?? "docs");

                        string?xmlDocGenPath = null;
                        var xmlDocGenProject = FindFiles("tools/XmlDocGen/XmlDocGen.csproj").FirstOrDefault();
                        if (xmlDocGenProject != null)
                        {
                            RunDotNet("publish", xmlDocGenProject, "--output", Path.Combine("tools", "bin", "XmlDocGen"), "--nologo", "--verbosity", "quiet");
                            xmlDocGenPath = Path.Combine("tools", "bin", "XmlDocGen", "XmlDocGen.dll");
                        }

                        var projectHasDocs = docsSettings.ProjectHasDocs ?? (_ => true);
                        foreach (var projectName in packagePaths.Select(x => GetPackageInfo(x).Name).Where(projectHasDocs))
                        {
                            if (xmlDocGenPath != null)
                            {
                                RunDotNet(new[] { xmlDocGenPath }.Concat(GetXmlDocArgs(projectName)));
                            }
                            else
                            {
                                var assemblyPaths = new List <string>();
                                if (docsSettings.FindAssemblies != null)
                                {
                                    assemblyPaths.AddRange(docsSettings.FindAssemblies(projectName));
                                }
                                else
                                {
                                    var assemblyPath = (docsSettings.FindAssembly ?? FindAssembly)(projectName);
                                    if (assemblyPath != null)
                                    {
                                        assemblyPaths.Add(assemblyPath);
                                    }
                                }

                                if (assemblyPaths.Count != 0)
                                {
                                    if (DotNetLocalTool.TryCreate("xmldocmd") is DotNetLocalTool xmldocmd)
                                    {
                                        foreach (var assemblyPath in assemblyPaths)
                                        {
                                            xmldocmd.Run(GetXmlDocArgs(assemblyPath));
                                        }
                                    }
                                    else
                                    {
                                        var dotNetTools           = settings.DotNetTools ?? new DotNetTools(Path.Combine("tools", "bin"));
                                        var xmlDocMarkdownVersion = settings.DocsSettings?.ToolVersion ?? "2.0.1";

                                        foreach (var assemblyPath in assemblyPaths)
                                        {
                                            RunApp(dotNetTools.GetToolPath($"xmldocmd/{xmlDocMarkdownVersion}"), GetXmlDocArgs(assemblyPath));
                                        }
                                    }
                                }
                                else
                                {
                                    Console.WriteLine($"Documentation not generated for {projectName}; assembly not found.");
                                }