diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3e68fba968..ae19f9b0e6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,32 +4,115 @@ on: pull_request: push: +permissions: write-all + jobs: - build-windows: + + build-windows-core: + runs-on: windows-latest + steps: + - name: Disable Windows Defender + run: Set-MpPreference -DisableRealtimeMonitoring $true + shell: powershell + - uses: actions/checkout@v3 + - name: Run task 'Build' + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + ./build.cmd Build + - name: Run task 'InTestsCore' + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + ./build.cmd InTestsCore -e + - name: Upload test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: build-windows-core-trx + path: "**/*.trx" + + build-windows-full: runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - name: Run - shell: cmd - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - ./build.bat + - name: Disable Windows Defender + run: Set-MpPreference -DisableRealtimeMonitoring $true + shell: powershell + - uses: actions/checkout@v3 + - name: Run task 'Build' + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + ./build.cmd Build + - name: Run task 'InTestsFull' + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + ./build.cmd InTestsFull -e + - name: Upload test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: build-windows-full-trx + path: "**/*.trx" + build-linux: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Clang - uses: egor-tensin/setup-clang@v1 - with: - version: latest - platform: x64 - - name: Set up zlib-static - run: sudo apt-get install -y libkrb5-dev - - name: Run - run: ./build.sh + - uses: actions/checkout@v3 + - name: Set up Clang + uses: egor-tensin/setup-clang@v1 + with: + version: latest + platform: x64 + - name: Set up zlib-static + run: sudo apt-get install -y libkrb5-dev + - name: Run task 'Build' + run: ./build.cmd Build + - name: Run task 'UnitTests' + run: ./build.cmd UnitTests -e + - name: Run task 'InTestsCore' + run: ./build.cmd InTestsCore -e + - name: Upload test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: build-linux-trx + path: "**/*.trx" + build-macos: - runs-on: macOS-latest + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + - name: Run task 'Build' + run: ./build.cmd Build + - name: Run task 'UnitTests' + run: ./build.cmd UnitTests -e + - name: Run task 'InTestsCore' + run: ./build.cmd InTestsCore -e + - name: Upload test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: build-macos-trx + path: "**/*.trx" + + report: + concurrency: ci-${{ github.ref }} + needs: [build-windows-full, build-windows-core, build-linux, build-macos] + runs-on: ubuntu-latest + if: always() steps: - - uses: actions/checkout@v3 - - name: Run - run: ./build.sh \ No newline at end of file + - uses: actions/checkout@v3 + - name: Download Artifacts + uses: actions/download-artifact@v3 + - name: Display structure of downloaded files + run: ls -R + - name: Report tests results + uses: dorny/test-reporter@v1 + if: always() + with: + name: test-results + path: "**/*.trx" + reporter: dotnet-trx + fail-on-error: true diff --git a/.github/workflows/docs-changelog-generate.yaml b/.github/workflows/docs-changelog-generate.yaml index f55816e706..9afad1f814 100644 --- a/.github/workflows/docs-changelog-generate.yaml +++ b/.github/workflows/docs-changelog-generate.yaml @@ -19,7 +19,7 @@ jobs: ref: master - name: Download changelog - run: ./build.sh --target DocFX_Changelog_Download --VersionCount 1 + run: ./build.cmd DocsUpdate /p:Depth=1 env: GITHUB_PRODUCT: ChangelogBuilder GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs-stable.yaml b/.github/workflows/docs-stable.yaml index 83e50d7e97..089f6ed7b4 100644 --- a/.github/workflows/docs-stable.yaml +++ b/.github/workflows/docs-stable.yaml @@ -19,16 +19,16 @@ jobs: ref: docs-stable - name: Build BenchmarkDotNet - run: ./build.bat --target Build + run: ./build.cmd Build - name: Download changelog - run: ./build.bat --target DocFX_Changelog_Download --VersionCount 1 + run: ./build.cmd DocsUpdate /p:Depth=1 env: GITHUB_PRODUCT: ChangelogBuilder GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build documentation - run: ./build.bat --target DocFX_Build + run: ./build.cmd DocsBuild - name: Upload Artifacts uses: actions/upload-artifact@v1 diff --git a/.github/workflows/publish-nightly.yaml b/.github/workflows/publish-nightly.yaml new file mode 100644 index 0000000000..8cdf355f61 --- /dev/null +++ b/.github/workflows/publish-nightly.yaml @@ -0,0 +1,24 @@ +name: publish-nightly + +on: + push: + branches: + - master + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set date + run: echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV + - name: Pack + run: ./build.cmd pack /p:VersionSuffix=nightly.$DATE.$GITHUB_RUN_NUMBER + - name: Publish nupkg + env: + MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} + run: .dotnet/dotnet nuget push **/*.nupkg --source https://www.myget.org/F/benchmarkdotnet/api/v3/index.json --api-key $MYGET_API_KEY --timeout 600 + - name: Publish snupkg + env: + MYGET_API_KEY: ${{ secrets.MYGET_API_KEY }} + run: .dotnet/dotnet nuget push **/*.snupkg --source https://www.myget.org/F/benchmarkdotnet/api/v3/index.json --api-key $MYGET_API_KEY --timeout 600 diff --git a/appveyor.yml b/appveyor.yml index a4c11ce797..2960c99409 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ before_build: #---------------------------------# build_script: -- ps: .\build.ps1 +- ps: .\build.cmd CI test: off deploy: off diff --git a/azure-pipelines.Ubuntu.yml b/azure-pipelines.Ubuntu.yml index 4c8bf86311..a16a5446d5 100755 --- a/azure-pipelines.Ubuntu.yml +++ b/azure-pipelines.Ubuntu.yml @@ -13,7 +13,7 @@ jobs: parameters: name: Ubuntu vmImage: 'ubuntu-20.04' - scriptFileName: ./build.sh + scriptFileName: ./build.cmd CI initialization: - bash: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - diff --git a/azure-pipelines.Windows.yml b/azure-pipelines.Windows.yml index 809d92e62e..34e23807f0 100755 --- a/azure-pipelines.Windows.yml +++ b/azure-pipelines.Windows.yml @@ -13,4 +13,4 @@ jobs: parameters: name: Windows vmImage: 'windows-2019' - scriptFileName: .\build.ps1 \ No newline at end of file + scriptFileName: .\build.cmd CI \ No newline at end of file diff --git a/azure-pipelines.macOS.yml b/azure-pipelines.macOS.yml index 6543a8eb9b..19a34556f4 100755 --- a/azure-pipelines.macOS.yml +++ b/azure-pipelines.macOS.yml @@ -13,4 +13,4 @@ jobs: parameters: name: macOS vmImage: 'macOS-latest' - scriptFileName: ./build.sh + scriptFileName: ./build.cmd CI diff --git a/build.cmd b/build.cmd new file mode 100755 index 0000000000..2a8c5273ba --- /dev/null +++ b/build.cmd @@ -0,0 +1,5 @@ +:<<"::CMDLITERAL" +@CALL build\build.bat %* +@GOTO :EOF +::CMDLITERAL +"$(cd "$(dirname "$0")"; pwd)/build/build.sh" "$@" \ No newline at end of file diff --git a/build/Build.csproj b/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj similarity index 88% rename from build/Build.csproj rename to build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj index 6c3dc8db6a..3b5596f3b5 100644 --- a/build/Build.csproj +++ b/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj @@ -3,13 +3,13 @@ Exe net7.0 $(MSBuildProjectDirectory) + enable - \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/BuildContext.cs b/build/BenchmarkDotNet.Build/BuildContext.cs new file mode 100644 index 0000000000..416220424e --- /dev/null +++ b/build/BenchmarkDotNet.Build/BuildContext.cs @@ -0,0 +1,228 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using BenchmarkDotNet.Build.Helpers; +using BenchmarkDotNet.Build.Meta; +using BenchmarkDotNet.Build.Runners; +using Cake.Common; +using Cake.Common.Build; +using Cake.Common.Build.AppVeyor; +using Cake.Common.Diagnostics; +using Cake.Common.IO; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.MSBuild; +using Cake.Core; +using Cake.Core.IO; +using Cake.FileHelpers; +using Cake.Frosting; +using Cake.Git; + +namespace BenchmarkDotNet.Build; + +public class BuildContext : FrostingContext +{ + public string BuildConfiguration { get; set; } = "Release"; + public DotNetVerbosity BuildVerbosity { get; set; } = DotNetVerbosity.Minimal; + public int Depth { get; set; } + + public DirectoryPath RootDirectory { get; } + public DirectoryPath ArtifactsDirectory { get; } + public DirectoryPath DocsDirectory { get; } + public FilePath DocfxJsonFile { get; } + + public DirectoryPath ChangeLogDirectory { get; } + public DirectoryPath ChangeLogGenDirectory { get; } + + public DirectoryPath RedirectRootDirectory { get; } + public DirectoryPath RedirectTargetDirectory { get; } + + public FilePath SolutionFile { get; } + public FilePath TemplatesTestsProjectFile { get; } + public FilePathCollection AllPackableSrcProjects { get; } + + public DotNetMSBuildSettings MsBuildSettingsRestore { get; } + public DotNetMSBuildSettings MsBuildSettingsBuild { get; } + public DotNetMSBuildSettings MsBuildSettingsPack { get; } + + private IAppVeyorProvider AppVeyor => this.BuildSystem().AppVeyor; + public bool IsRunningOnAppVeyor => AppVeyor.IsRunningOnAppVeyor; + public bool IsOnAppVeyorAndNotPr => IsRunningOnAppVeyor && !AppVeyor.Environment.PullRequest.IsPullRequest; + + public bool IsOnAppVeyorAndBdnNightlyCiCd => IsOnAppVeyorAndNotPr && + AppVeyor.Environment.Repository.Branch == "master" && + this.IsRunningOnWindows(); + + public bool IsLocalBuild => this.BuildSystem().IsLocalBuild; + public bool IsCiBuild => !this.BuildSystem().IsLocalBuild; + + public UnitTestRunner UnitTestRunner { get; } + public DocumentationRunner DocumentationRunner { get; } + public BuildRunner BuildRunner { get; } + + public BuildContext(ICakeContext context) + : base(context) + { + RootDirectory = new DirectoryPath(new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.FullName); + ArtifactsDirectory = RootDirectory.Combine("artifacts"); + DocsDirectory = RootDirectory.Combine("docs"); + DocfxJsonFile = DocsDirectory.CombineWithFilePath("docfx.json"); + + ChangeLogDirectory = RootDirectory.Combine("docs").Combine("changelog"); + ChangeLogGenDirectory = RootDirectory.Combine("docs").Combine("_changelog"); + + RedirectRootDirectory = RootDirectory.Combine("docs").Combine("_redirects"); + RedirectTargetDirectory = RootDirectory.Combine("docs").Combine("_site"); + + SolutionFile = RootDirectory.CombineWithFilePath("BenchmarkDotNet.sln"); + + TemplatesTestsProjectFile = RootDirectory.Combine("templates") + .CombineWithFilePath("BenchmarkDotNet.Templates.csproj"); + AllPackableSrcProjects = new FilePathCollection(context.GetFiles(RootDirectory.FullPath + "/src/**/*.csproj") + .Where(p => !p.FullPath.Contains("Disassembler"))); + + MsBuildSettingsRestore = new DotNetMSBuildSettings(); + MsBuildSettingsBuild = new DotNetMSBuildSettings(); + MsBuildSettingsPack = new DotNetMSBuildSettings(); + + if (IsCiBuild) + { + System.Environment.SetEnvironmentVariable("BDN_CI_BUILD", "true"); + + MsBuildSettingsBuild.MaxCpuCount = 1; + MsBuildSettingsBuild.WithProperty("UseSharedCompilation", "false"); + } + + Depth = -1; + if (context.Arguments.HasArgument("msbuild")) + { + var msBuildParameters = context.Arguments.GetArguments().First(it => it.Key == "msbuild").Value; + foreach (var msBuildParameter in msBuildParameters) + { + var split = msBuildParameter.Split(new[] { '=' }, 2); + if (split.Length == 2) + { + var name = split[0]; + var value = split[1]; + + MsBuildSettingsRestore.WithProperty(name, value); + MsBuildSettingsBuild.WithProperty(name, value); + MsBuildSettingsPack.WithProperty(name, value); + + if (name.Equals("configuration", StringComparison.OrdinalIgnoreCase)) BuildConfiguration = value; + + if (name.Equals("verbosity", StringComparison.OrdinalIgnoreCase)) + { + var parsedVerbosity = Utils.ParseVerbosity(value); + if (parsedVerbosity != null) + BuildVerbosity = parsedVerbosity.Value; + } + + if (name.Equals("depth", StringComparison.OrdinalIgnoreCase)) + Depth = int.Parse(value); + } + } + } + + // NativeAOT build requires VS C++ tools to be added to $path via vcvars64.bat + // but once we do that, dotnet restore fails with: + // "Please specify a valid solution configuration using the Configuration and Platform properties" + if (context.IsRunningOnWindows()) + { + MsBuildSettingsRestore.WithProperty("Platform", "Any CPU"); + MsBuildSettingsBuild.WithProperty("Platform", "Any CPU"); + } + + UnitTestRunner = new UnitTestRunner(this); + DocumentationRunner = new DocumentationRunner(this); + BuildRunner = new BuildRunner(this); + } + + public void EnsureChangelogDetailsExist(bool forceClean = false) + { + var path = ChangeLogGenDirectory.Combine("details"); + if (this.DirectoryExists(path) && forceClean) + this.DeleteDirectory(path, new DeleteDirectorySettings() { Force = true, Recursive = true }); + + if (!this.DirectoryExists(path)) + { + var repo = Repo.HttpsGitUrl; + var branchName = Repo.ChangelogDetailsBranch; + var settings = new GitCloneSettings { Checkout = true, BranchName = branchName }; + this.Information($"Trying to clone {repo} to {path} (branch: '{branchName})"); + try + { + this.GitClone(repo, path, settings); + } + catch (Exception e) + { + this.Error($"Failed to clone {repo} to {path} (branch: '{branchName}), Exception: {e.GetType().Name}'"); + try + { + var gitArgs = $"clone -b {branchName} {repo} {path}"; + this.Information($"Trying to clone manually: 'git {gitArgs}'"); + this.StartProcess("git", gitArgs); + } + catch (Exception e2) + { + throw new Exception($"Failed to clone {repo} to {path} (branch: '{branchName})'", e2); + } + } + + this.Information("Clone is successfully finished"); + this.Information(""); + } + } + + public void DocfxChangelogDownload(string version, string versionPrevious, string lastCommit = "") + { + EnsureChangelogDetailsExist(); + this.Information("DocfxChangelogDownload: " + version); + var path = ChangeLogGenDirectory.Combine("details"); + ChangeLogBuilder.Run(path, version, versionPrevious, lastCommit).Wait(); + } + + public void DocfxChangelogGenerate(string version) + { + EnsureChangelogDetailsExist(); + this.Information("DocfxChangelogGenerate: " + version); + var header = ChangeLogGenDirectory.Combine("header").CombineWithFilePath(version + ".md"); + var footer = ChangeLogGenDirectory.Combine("footer").CombineWithFilePath(version + ".md"); + var details = ChangeLogGenDirectory.Combine("details").CombineWithFilePath(version + ".md"); + var release = ChangeLogDirectory.CombineWithFilePath(version + ".md"); + + var content = new StringBuilder(); + content.AppendLine("---"); + content.AppendLine("uid: changelog." + version); + content.AppendLine("---"); + content.AppendLine(""); + content.AppendLine("# BenchmarkDotNet " + version); + content.AppendLine(""); + content.AppendLine(""); + + if (this.FileExists(header)) + { + content.AppendLine(this.FileReadText(header)); + content.AppendLine(""); + content.AppendLine(""); + } + + if (this.FileExists(details)) + { + content.AppendLine(this.FileReadText(details)); + content.AppendLine(""); + content.AppendLine(""); + } + + if (this.FileExists(footer)) + { + content.AppendLine("## Additional details"); + content.AppendLine(""); + content.AppendLine(this.FileReadText(footer)); + } + + this.FileWriteText(release, content.ToString()); + } + + +} \ No newline at end of file diff --git a/build/ChangeLogBuilder.cs b/build/BenchmarkDotNet.Build/ChangeLogBuilder.cs similarity index 62% rename from build/ChangeLogBuilder.cs rename to build/BenchmarkDotNet.Build/ChangeLogBuilder.cs index 561ece19b7..caf20629fc 100644 --- a/build/ChangeLogBuilder.cs +++ b/build/BenchmarkDotNet.Build/ChangeLogBuilder.cs @@ -5,78 +5,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using BenchmarkDotNet.Build.Helpers; +using BenchmarkDotNet.Build.Meta; using Cake.Core.IO; -using JetBrains.Annotations; using Octokit; -namespace Build; +namespace BenchmarkDotNet.Build; -public static class OctokitExtensions +public static class ChangeLogBuilder { - public static string ToStr(this User user, string prefix) => user != null - ? $" ({prefix} [@{user.Login}]({user.HtmlUrl}))" - : ""; - - private static string ToStr(this Author user, string prefix) => user != null - ? $" ({prefix} {user.ToLink()})" - : ""; - - private static string ToStr(this Committer user, string prefix) => user != null - ? $" ({prefix} {user.Name})" - : ""; - - public static string ToLink(this Author user) => $"[@{user.Login}]({user.HtmlUrl})"; - - public static string ToLinkWithName(this Author user, string name) => $"[@{user.Login} ({name})]({user.HtmlUrl})"; - - public static string ToCommitMessage(this Commit commit) - { - var message = commit.Message.Trim().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) - .FirstOrDefault() ?? ""; - return message.Length > 80 ? message.Substring(0, 77) + "..." : message; - } - - public static string ToLink(this GitHubCommit commit) => $"[{commit.Sha.Substring(0, 6)}]({commit.HtmlUrl})"; - - public static string ToByStr(this GitHubCommit commit) - { - if (commit.Author != null) - return commit.Author.ToStr("by"); - return commit.Commit.Author != null ? commit.Commit.Author.ToStr("by") : ""; - } -} - -public class ChangeLogBuilder -{ - public class Config + private class Config { - [PublicAPI] public string ProductHeader => Environment.GetEnvironmentVariable("GITHUB_PRODUCT"); - [PublicAPI] public string Token => Environment.GetEnvironmentVariable("GITHUB_TOKEN"); - - [PublicAPI] public string RepoOwner => "dotnet"; - [PublicAPI] public string RepoName => "BenchmarkDotNet"; - [PublicAPI] public string CurrentMilestone { get; } + public string CurrentMilestone { get; } + public string PreviousMilestone { get; } + public string LastCommit { get; } - [PublicAPI] public string PreviousMilestone { get; } - [PublicAPI] public string LastCommit { get; } - - public void Deconstruct(out string repoOwner, out string repoName, out string currentMilestone, - out string previousMilestone, out string lastCommit) + public void Deconstruct(out string currentMilestone, out string previousMilestone, out string lastCommit) { - repoOwner = RepoOwner; - repoName = RepoName; currentMilestone = CurrentMilestone; previousMilestone = PreviousMilestone; lastCommit = LastCommit; } - public Config(string[] args) - { - CurrentMilestone = args[0]; - PreviousMilestone = args[1]; - LastCommit = args.Length <= 2 ? CurrentMilestone : args[2]; - } - public Config(string currentMilestone, string previousMilestone, string lastCommit) { CurrentMilestone = currentMilestone; @@ -85,20 +35,11 @@ public Config(string currentMilestone, string previousMilestone, string lastComm } } - public class AuthorEqualityComparer : IEqualityComparer - { - public static readonly IEqualityComparer Default = new AuthorEqualityComparer(); - - public bool Equals(Author x, Author y) => x.Login == y.Login; - - public int GetHashCode(Author author) => author.Login.GetHashCode(); - } - - public class MarkdownBuilder + private class MarkdownBuilder { - private static IReadOnlyList AllMilestones = null; + private static IReadOnlyList? allMilestones; private static readonly Dictionary AuthorNames = new(); - + private readonly Config config; private readonly StringBuilder builder; @@ -115,17 +56,17 @@ private MarkdownBuilder(Config config) private async Task Build() { - var (repoOwner, repoName, milestone, previousMilestone, lastCommit) = config; + var (milestone, previousMilestone, lastCommit) = config; if (string.IsNullOrEmpty(lastCommit)) lastCommit = milestone; - var client = new GitHubClient(new ProductHeaderValue(config.ProductHeader)); - var tokenAuth = new Credentials(config.Token); + var client = new GitHubClient(new ProductHeaderValue(Repo.ProductHeader)); + var tokenAuth = new Credentials(Repo.Token); client.Credentials = tokenAuth; - + if (milestone == "_") { - var allContributors = await client.Repository.GetAllContributors(repoOwner, repoName); + var allContributors = await client.Repository.GetAllContributors(Repo.Owner, Repo.Name); builder.AppendLine("# All contributors"); builder.AppendLine(); foreach (var contributor in allContributors) @@ -140,17 +81,17 @@ private async Task Build() return builder.ToString(); } - if (AllMilestones == null) + if (allMilestones == null) { var milestoneRequest = new MilestoneRequest { State = ItemStateFilter.All }; - AllMilestones = await client.Issue.Milestone.GetAllForRepository(repoOwner, repoName, milestoneRequest); + allMilestones = await client.Issue.Milestone.GetAllForRepository(Repo.Owner, Repo.Name, milestoneRequest); } IReadOnlyList allIssues = Array.Empty(); - var targetMilestone = AllMilestones.FirstOrDefault(m => m.Title == milestone); + var targetMilestone = allMilestones.FirstOrDefault(m => m.Title == milestone); if (targetMilestone != null) { var issueRequest = new RepositoryIssueRequest @@ -159,7 +100,7 @@ private async Task Build() Milestone = targetMilestone.Number.ToString() }; - allIssues = await client.Issue.GetAllForRepository(repoOwner, repoName, issueRequest); + allIssues = await client.Issue.GetAllForRepository(Repo.Owner, Repo.Name, issueRequest); } var issues = allIssues @@ -170,11 +111,11 @@ private async Task Build() .Where(issue => issue.PullRequest != null) .OrderBy(issue => issue.Number) .ToList(); - - var compare = await client.Repository.Commit.Compare(repoOwner, repoName, previousMilestone, lastCommit); + + var compare = await client.Repository.Commit.Compare(Repo.Owner, Repo.Name, previousMilestone, lastCommit); var commits = compare.Commits; - - + + foreach (var contributor in commits.Select(commit => commit.Author)) if (contributor != null && !AuthorNames.ContainsKey(contributor.Login)) { @@ -189,14 +130,14 @@ string PresentContributor(GitHubCommit commit) return $"{AuthorNames[commit.Author.Login]} ({commit.Author.ToLink()})".Trim(); return commit.Commit.Author.Name; } - + var contributors = compare.Commits .Select(PresentContributor) .OrderBy(it => it) .Distinct() .ToImmutableList(); - - var milestoneHtmlUlr = $"https://github.com/{repoOwner}/{repoName}/issues?q=milestone:{milestone}"; + + var milestoneHtmlUlr = $"https://github.com/{Repo.Owner}/{Repo.Name}/issues?q=milestone:{milestone}"; builder.AppendLine("## Milestone details"); builder.AppendLine(); @@ -218,7 +159,7 @@ string PresentContributor(GitHubCommit commit) } private void AppendList(string title, IReadOnlyList items, Func format, - string conclusion = null) + string? conclusion = null) { builder.AppendLine($"## {title} ({items.Count})"); builder.AppendLine(); @@ -233,8 +174,9 @@ private void AppendList(string title, IReadOnlyList items, Func builder.AppendLine(); } } - - public static async Task Run(DirectoryPath path, string currentMilestone, string previousMilestone, string lastCommit) + + public static async Task Run(DirectoryPath path, string currentMilestone, string previousMilestone, + string lastCommit) { try { diff --git a/build/BenchmarkDotNet.Build/CommandLineParser.cs b/build/BenchmarkDotNet.Build/CommandLineParser.cs new file mode 100644 index 0000000000..6f8af08a8c --- /dev/null +++ b/build/BenchmarkDotNet.Build/CommandLineParser.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Cake.Frosting; + +namespace BenchmarkDotNet.Build; + +public class CommandLineParser +{ + public static readonly CommandLineParser Instance = new(); + + private record Option(string ShortName, string FullName, string Arg, string Description, string CakeOption); + + private readonly Option[] options = + { + new("-v", + "--verbosity", + "", + "Specifies the amount of information to be displayed\n(Quiet, Minimal, Normal, Verbose, Diagnostic)", + "--verbosity"), + new("-e", + "--exclusive", + "", + "Executes the target task without any dependencies", + "--exclusive") + }; + + private void PrintHelp(bool skipWelcome = false) + { + const string scriptName = "build.cmd"; + if (!skipWelcome) + { + WriteHeader("Welcome to the BenchmarkDotNet build script!"); + WriteLine(); + } + + WriteHeader("USAGE:"); + + WritePrefix(); + Write(scriptName + " "); + WriteTask(" "); + WriteOption("[OPTIONS]"); + WriteLine(); + + WriteLine(); + + WriteHeader("EXAMPLES:"); + + WritePrefix(); + Write(scriptName + " "); + WriteTask("restore"); + WriteLine(); + + WritePrefix(); + Write(scriptName + " "); + WriteTask("build "); + WriteOption("/p:"); + WriteArg("Configuration"); + WriteOption("="); + WriteArg("Debug"); + WriteLine(); + + WritePrefix(); + Write(scriptName + " "); + WriteTask("pack "); + WriteOption("/p:"); + WriteArg("Version"); + WriteOption("="); + WriteArg("0.1.1729-preview"); + WriteLine(); + + WritePrefix(); + Write(scriptName + " "); + WriteTask("unittests "); + WriteOption("--exclusive --verbosity "); + WriteArg("Diagnostic"); + WriteLine(); + + WritePrefix(); + Write(scriptName + " "); + WriteTask("docsupdate "); + WriteOption("/p:"); + WriteArg("Depth"); + WriteOption("="); + WriteArg("3"); + WriteLine(); + + WriteLine(); + + WriteLine("OPTIONS:", ConsoleColor.DarkCyan); + + var shortNameWidth = options.Max(it => it.ShortName.Length); + var targetWidth = options.Max(it => it.FullName.Length + it.Arg.Length); + + foreach (var (shortName, fullName, arg, description, _) in options) + { + WritePrefix(); + WriteOption(shortName.PadRight(shortNameWidth)); + WriteOption(shortName != "" ? "," : " "); + WriteOption(fullName); + Write(" "); + WriteArg(arg); + Write(new string(' ', targetWidth - fullName.Length - arg.Length + 3)); + var descriptionLines = description.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + Write(descriptionLines.FirstOrDefault() ?? ""); + for (int i = 1; i < descriptionLines.Length; i++) + { + WriteLine(); + WritePrefix(); + Write(new string(' ', shortNameWidth + 2 + targetWidth + 3)); + Write(descriptionLines[i]); + } + + WriteLine(); + } + + WritePrefix(); + WriteOption("/p:"); + WriteArg(""); + WriteOption("="); + WriteArg(""); + Write(new string(' ', targetWidth + shortNameWidth - 11)); + Write("Passes custom properties to MSBuild"); + WriteLine(); + + WriteLine(); + + WriteHeader("TASKS:"); + var taskWidth = GetTaskNames().Max(name => name.Length) + 3; + foreach (var (taskName, taskDescription) in GetTasks()) + { + if (taskName.Equals("Default", StringComparison.OrdinalIgnoreCase)) + continue; + + if (taskDescription.StartsWith("OBSOLETE", StringComparison.OrdinalIgnoreCase)) + { + WriteObsolete(" " + taskName.PadRight(taskWidth)); + WriteObsolete(taskDescription); + } + else + { + WriteTask(" " + taskName.PadRight(taskWidth)); + Write(taskDescription); + } + + WriteLine(); + } + + return; + + void WritePrefix() => Write(" "); + void WriteTask(string message) => Write(message, ConsoleColor.Green); + void WriteOption(string message) => Write(message, ConsoleColor.Blue); + void WriteArg(string message) => Write(message, ConsoleColor.DarkYellow); + void WriteObsolete(string message) => Write(message, ConsoleColor.Gray); + + void WriteHeader(string message) + { + WriteLine(message, ConsoleColor.DarkCyan); + } + + void Write(string message, ConsoleColor? color = null) + { + if (color != null) + Console.ForegroundColor = color.Value; + Console.Write(message); + if (color != null) + Console.ResetColor(); + } + + void WriteLine(string message = "", ConsoleColor? color = null) + { + Write(message, color); + Console.WriteLine(); + } + } + + private static HashSet GetTaskNames() + { + return GetTasks().Select(task => task.Name).ToHashSet(StringComparer.OrdinalIgnoreCase); + } + + private static List<(string Name, string Description)> GetTasks() + { + return typeof(BuildContext).Assembly + .GetTypes() + .Where(type => type.IsSubclassOf(typeof(FrostingTask)) && !type.IsAbstract) + .Select(type => ( + Name: type.GetCustomAttribute()?.Name ?? "", + Description: type.GetCustomAttribute()?.Description ?? "" + )) + .Where(task => task.Name != "") + .ToList(); + } + + + public string[]? Parse(string[]? args) + { + if (args == null || args.Length == 0) + { + PrintHelp(); + return null; + } + + if (args.Length == 1) + { + if (IsOneOf(args[0], "help")) + { + PrintHelp(); + return null; + } + + if (IsOneOf(args[0], "help-cake")) + { + new CakeHost().UseContext().Run(new[] { "--help" }); + return null; + } + } + + var argsToProcess = new Queue(args); + + var taskName = argsToProcess.Dequeue(); + if (IsOneOf(taskName, "-t", "--target") && argsToProcess.Any()) + taskName = argsToProcess.Dequeue(); + + var taskNames = GetTaskNames(); + if (!taskNames.Contains(taskName)) + { + PrintError($"'{taskName}' is not a task"); + return null; + } + + var cakeArgs = new List + { + "--target", + taskName + }; + while (argsToProcess.Any()) + { + var arg = argsToProcess.Dequeue(); + + var matched = false; + foreach (var option in options) + { + if (IsOneOf(arg, option.ShortName, option.FullName)) + { + matched = true; + cakeArgs.Add(option.CakeOption); + if (option.Arg != "") + { + if (!argsToProcess.Any()) + { + PrintError(option.FullName + " is not specified"); + return null; + } + + cakeArgs.Add(argsToProcess.Dequeue()); + } + } + } + + if (arg.StartsWith("/p:")) + { + matched = true; + cakeArgs.Add("--msbuild"); + cakeArgs.Add(arg[3..]); + } + + if (!matched) + { + PrintError("Unknown option: " + arg); + return null; + } + } + + return cakeArgs.ToArray(); + } + + bool IsOneOf(string arg, params string[] values) => + values.Any(value => value.Equals(arg, StringComparison.OrdinalIgnoreCase)); + + void PrintError(string text) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Error.WriteLine("ERROR: " + text); + Console.WriteLine(); + Console.ResetColor(); + PrintHelp(true); + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Folder.DotSettings b/build/BenchmarkDotNet.Build/Folder.DotSettings new file mode 100644 index 0000000000..53109cf04e --- /dev/null +++ b/build/BenchmarkDotNet.Build/Folder.DotSettings @@ -0,0 +1,4 @@ + + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + True \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Helpers/OctokitExtensions.cs b/build/BenchmarkDotNet.Build/Helpers/OctokitExtensions.cs new file mode 100644 index 0000000000..f981d6a12c --- /dev/null +++ b/build/BenchmarkDotNet.Build/Helpers/OctokitExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Linq; +using Octokit; + +namespace BenchmarkDotNet.Build.Helpers; + +public static class OctokitExtensions +{ + public static string ToStr(this User? user, string prefix) => user != null + ? $" ({prefix} [@{user.Login}]({user.HtmlUrl}))" + : ""; + + private static string ToStr(this Author? user, string prefix) => user != null + ? $" ({prefix} {user.ToLink()})" + : ""; + + private static string ToStr(this Committer? user, string prefix) => user != null + ? $" ({prefix} {user.Name})" + : ""; + + public static string ToLink(this Author user) => $"[@{user.Login}]({user.HtmlUrl})"; + + public static string ToLinkWithName(this Author user, string name) => $"[@{user.Login} ({name})]({user.HtmlUrl})"; + + public static string ToCommitMessage(this Commit commit) + { + var message = commit.Message.Trim().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .FirstOrDefault() ?? ""; + return message.Length > 80 ? message.Substring(0, 77) + "..." : message; + } + + public static string ToLink(this GitHubCommit commit) => $"[{commit.Sha.Substring(0, 6)}]({commit.HtmlUrl})"; + + public static string ToByStr(this GitHubCommit commit) + { + if (commit.Author != null) + return commit.Author.ToStr("by"); + return commit.Commit.Author != null ? commit.Commit.Author.ToStr("by") : ""; + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Helpers/Utils.cs b/build/BenchmarkDotNet.Build/Helpers/Utils.cs new file mode 100644 index 0000000000..017e092419 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Helpers/Utils.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Cake.Common.Tools.DotNet; + +namespace BenchmarkDotNet.Build.Helpers; + +public static class Utils +{ + public static string GetOs() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "linux"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "windows"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "macos"; + return "unknown"; + } + + public static DotNetVerbosity? ParseVerbosity(string verbosity) + { + var lookup = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "q", DotNetVerbosity.Quiet }, + { "quiet", DotNetVerbosity.Quiet }, + { "m", DotNetVerbosity.Minimal }, + { "minimal", DotNetVerbosity.Minimal }, + { "n", DotNetVerbosity.Normal }, + { "normal", DotNetVerbosity.Normal }, + { "d", DotNetVerbosity.Detailed }, + { "detailed", DotNetVerbosity.Detailed }, + { "diag", DotNetVerbosity.Diagnostic }, + { "diagnostic", DotNetVerbosity.Diagnostic } + }; + return lookup.TryGetValue(verbosity, out var value) ? value : null; + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Meta/DocumentationHelper.cs b/build/BenchmarkDotNet.Build/Meta/DocumentationHelper.cs new file mode 100644 index 0000000000..103a6e5368 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Meta/DocumentationHelper.cs @@ -0,0 +1,62 @@ +namespace BenchmarkDotNet.Build.Meta; + +public static class DocumentationHelper +{ + public static readonly string[] BdnAllVersions = + { + "v0.7.0", + "v0.7.1", + "v0.7.2", + "v0.7.3", + "v0.7.4", + "v0.7.5", + "v0.7.6", + "v0.7.7", + "v0.7.8", + "v0.8.0", + "v0.8.1", + "v0.8.2", + "v0.9.0", + "v0.9.1", + "v0.9.2", + "v0.9.3", + "v0.9.4", + "v0.9.5", + "v0.9.6", + "v0.9.7", + "v0.9.8", + "v0.9.9", + "v0.10.0", + "v0.10.1", + "v0.10.2", + "v0.10.3", + "v0.10.4", + "v0.10.5", + "v0.10.6", + "v0.10.7", + "v0.10.8", + "v0.10.9", + "v0.10.10", + "v0.10.11", + "v0.10.12", + "v0.10.13", + "v0.10.14", + "v0.11.0", + "v0.11.1", + "v0.11.2", + "v0.11.3", + "v0.11.4", + "v0.11.5", + "v0.12.0", + "v0.12.1", + "v0.13.0", + "v0.13.1", + "v0.13.2", + "v0.13.3", + "v0.13.4", + "v0.13.5" + }; + + public const string BdnNextVersion = "v0.13.6"; + public const string BdnFirstCommit = "6eda98ab1e83a0d185d09ff8b24c795711af8db1"; +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Meta/Repo.cs b/build/BenchmarkDotNet.Build/Meta/Repo.cs new file mode 100644 index 0000000000..06e6137b55 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Meta/Repo.cs @@ -0,0 +1,18 @@ +using System; + +namespace BenchmarkDotNet.Build.Meta; + +public static class Repo +{ + public const string Owner = "dotnet"; + public const string Name = "BenchmarkDotNet"; + public const string HttpsUrlBase = $"https://github.com/{Owner}/{Name}"; + public const string HttpsGitUrl = $"{HttpsUrlBase}.git"; + public const string ChangelogDetailsBranch = "docs-changelog-details"; + + public const string ProductHeaderVar = "GITHUB_PRODUCT"; + public const string TokenVar = "GITHUB_TOKEN"; + + public static string? ProductHeader => Environment.GetEnvironmentVariable(ProductHeaderVar); + public static string? Token => Environment.GetEnvironmentVariable(TokenVar); +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Program.cs b/build/BenchmarkDotNet.Build/Program.cs new file mode 100644 index 0000000000..4f9138bf08 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Program.cs @@ -0,0 +1,158 @@ +using Cake.Common; +using Cake.Frosting; + +namespace BenchmarkDotNet.Build; + +public static class Program +{ + public static int Main(string[] args) + { + var cakeArgs = CommandLineParser.Instance.Parse(args); + return cakeArgs == null + ? 0 + : new CakeHost().UseContext().Run(cakeArgs); + } +} + +[TaskName("Restore")] +[TaskDescription("Restore NuGet packages")] +public class RestoreTask : FrostingTask +{ + public override void Run(BuildContext context) => context.BuildRunner.Restore(); +} + +[TaskName("Build")] +[TaskDescription("Build BenchmarkDotNet.sln solution")] +[IsDependentOn(typeof(RestoreTask))] +public class BuildTask : FrostingTask +{ + public override void Run(BuildContext context) => context.BuildRunner.Build(); +} + +[TaskName("UnitTests")] +[TaskDescription("Run unit tests (fast)")] +[IsDependentOn(typeof(BuildTask))] +public class UnitTestsTask : FrostingTask +{ + public override void Run(BuildContext context) => context.UnitTestRunner.RunUnitTests(); +} + +[TaskName("InTestsFull")] +[TaskDescription("Run integration tests using .NET Framework 4.6.2+ (slow)")] +[IsDependentOn(typeof(BuildTask))] +public class InTestsFullTask : FrostingTask +{ + public override bool ShouldRun(BuildContext context) => + context.IsRunningOnWindows() && !context.IsRunningOnAppVeyor; + + public override void Run(BuildContext context) => context.UnitTestRunner.RunInTests("net462"); +} + +[TaskName("InTestsCore")] +[TaskDescription("Run integration tests using .NET 7 (slow)")] +[IsDependentOn(typeof(BuildTask))] +public class InTestsCoreTask : FrostingTask +{ + public override void Run(BuildContext context) => context.UnitTestRunner.RunInTests("net7.0"); +} + +[TaskName("AllTests")] +[TaskDescription("Run all unit and integration tests (slow)")] +[IsDependentOn(typeof(UnitTestsTask))] +[IsDependentOn(typeof(InTestsFullTask))] +[IsDependentOn(typeof(InTestsCoreTask))] +public class AllTestsTask : FrostingTask +{ +} + +[TaskName("Pack")] +[TaskDescription("Pack Nupkg packages")] +[IsDependentOn(typeof(BuildTask))] +public class PackTask : FrostingTask +{ + public override void Run(BuildContext context) => context.BuildRunner.Pack(); +} + +[TaskName("CI")] +[TaskDescription("Perform all CI-related tasks: Restore, Build, AllTests, Pack")] +[IsDependentOn(typeof(BuildTask))] +[IsDependentOn(typeof(AllTestsTask))] +[IsDependentOn(typeof(PackTask))] +public class CiTask : FrostingTask +{ +} + +[TaskName("DocsUpdate")] +[TaskDescription("Update generated documentation files")] +public class DocsUpdateTask : FrostingTask +{ + public override void Run(BuildContext context) => context.DocumentationRunner.Update(); +} + +[TaskName("DocsPrepare")] +[TaskDescription("Prepare auxiliary documentation files")] +public class DocsPrepareTask : FrostingTask +{ + public override void Run(BuildContext context) => context.DocumentationRunner.Prepare(); +} + +// In order to work around xref issues in DocFx, BenchmarkDotNet and BenchmarkDotNet.Annotations must be build +// before running the DocFX_Build target. However, including a dependency on BuildTask here may have unwanted +// side effects (CleanTask). +// TODO: Define dependencies when a CI workflow scenario for using the "DocFX_Build" target exists. +[TaskName("DocsBuild")] +[TaskDescription("Build final documentation")] +[IsDependentOn(typeof(DocsPrepareTask))] +public class DocsBuildTask : FrostingTask +{ + public override void Run(BuildContext context) => context.DocumentationRunner.Build(); +} + +[TaskName("FastTests")] +[TaskDescription("OBSOLETE: use 'UnitTests'")] +[IsDependentOn(typeof(UnitTestsTask))] +public class FastTestsTask : FrostingTask +{ +} + +[TaskName("SlowFullFrameworkTests")] +[TaskDescription("OBSOLETE: use 'InTestsFull'")] +[IsDependentOn(typeof(InTestsFullTask))] +public class SlowFullFrameworkTestsTask : FrostingTask +{ +} + +[TaskName("SlowTestsNetCore")] +[TaskDescription("OBSOLETE: use 'InTestsCore'")] +[IsDependentOn(typeof(InTestsCoreTask))] +public class SlowTestsNetCoreTask : FrostingTask +{ +} + +[TaskName("DocFX_Changelog_Download")] +[TaskDescription("OBSOLETE: use 'DocsUpdate'")] +[IsDependentOn(typeof(DocsUpdateTask))] +public class DocFxChangelogDownloadTask : FrostingTask +{ +} + +[TaskName("DocFX_Changelog_Generate")] +[TaskDescription("OBSOLETE: use 'DocsPrepare'")] +[IsDependentOn(typeof(DocsPrepareTask))] +public class DocfxChangelogGenerateTask : FrostingTask +{ +} + +[TaskName("DocFX_Generate_Redirects")] +[TaskDescription("OBSOLETE: use 'DocsBuild'")] +[IsDependentOn(typeof(DocsBuildTask))] +public class DocfxGenerateRedirectsTask : FrostingTask +{ +} + +[TaskName("DocFX_Build")] +[TaskDescription("OBSOLETE: use 'DocsBuild'")] +[IsDependentOn(typeof(DocsBuildTask))] +public class DocfxBuildTask : FrostingTask +{ +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs b/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs new file mode 100644 index 0000000000..8ef40475d0 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Runners/BuildRunner.cs @@ -0,0 +1,66 @@ +using Cake.Common.Build; +using Cake.Common.Diagnostics; +using Cake.Common.IO; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Build; +using Cake.Common.Tools.DotNet.Pack; +using Cake.Common.Tools.DotNet.Restore; +using Cake.Core; + +namespace BenchmarkDotNet.Build.Runners; + +public class BuildRunner +{ + private readonly BuildContext context; + + public BuildRunner(BuildContext context) + { + this.context = context; + } + + public void Restore() + { + context.DotNetRestore(context.SolutionFile.FullPath, + new DotNetRestoreSettings + { + MSBuildSettings = context.MsBuildSettingsRestore + }); + } + + public void Build() + { + context.Information("BuildSystemProvider: " + context.BuildSystem().Provider); + context.DotNetBuild(context.SolutionFile.FullPath, new DotNetBuildSettings + { + NoRestore = true, + DiagnosticOutput = true, + MSBuildSettings = context.MsBuildSettingsBuild, + Configuration = context.BuildConfiguration, + Verbosity = context.BuildVerbosity + }); + } + + public void Pack() + { + context.CleanDirectory(context.ArtifactsDirectory); + + var settingsSrc = new DotNetPackSettings + { + OutputDirectory = context.ArtifactsDirectory, + ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg"), + MSBuildSettings = context.MsBuildSettingsPack, + Configuration = context.BuildConfiguration + }; + + foreach (var project in context.AllPackableSrcProjects) + context.DotNetPack(project.FullPath, settingsSrc); + + var settingsTemplate = new DotNetPackSettings + { + OutputDirectory = context.ArtifactsDirectory, + MSBuildSettings = context.MsBuildSettingsPack, + Configuration = context.BuildConfiguration + }; + context.DotNetPack(context.TemplatesTestsProjectFile.FullPath, settingsTemplate); + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs b/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs new file mode 100644 index 0000000000..bc7e68dfa0 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs @@ -0,0 +1,168 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using BenchmarkDotNet.Build.Meta; +using Cake.Common.Diagnostics; +using Cake.Common.IO; +using Cake.Core.IO; +using Cake.FileHelpers; + +namespace BenchmarkDotNet.Build.Runners; + +public class DocumentationRunner +{ + private readonly BuildContext context; + + public DocumentationRunner(BuildContext context) + { + this.context = context; + } + + private void GenerateRedirects() + { + var redirectFile = context.RedirectRootDirectory.CombineWithFilePath("_redirects"); + if (!context.FileExists(redirectFile)) + { + context.Error($"Redirect file '{redirectFile}' does not exist"); + return; + } + + context.EnsureDirectoryExists(context.RedirectTargetDirectory); + + var redirects = context.FileReadLines(redirectFile) + .Select(line => line.Split(' ')) + .Select(parts => (source: parts[0], target: parts[1])) + .ToList(); + + foreach (var (source, target) in redirects) + { + var fileName = source.StartsWith("/") || source.StartsWith("\\") ? source[1..] : source; + var fullFileName = context.RedirectTargetDirectory.CombineWithFilePath(fileName); + var content = + $"" + + $"" + + $"" + + $"{target}" + + $"" + + $"" + + $"" + + $"" + + $""; + context.EnsureDirectoryExists(fullFileName.GetDirectory()); + context.FileWriteText(fullFileName, content); + } + } + + private void RunDocfx(FilePath docfxJson) + { + context.Information($"Running docfx for '{docfxJson}'"); + + var currentDirectory = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(docfxJson.GetDirectory().FullPath); + Microsoft.DocAsCode.Dotnet.DotnetApiCatalog.GenerateManagedReferenceYamlFiles(docfxJson.FullPath).Wait(); + Microsoft.DocAsCode.Docset.Build(docfxJson.FullPath).Wait(); + Directory.SetCurrentDirectory(currentDirectory); + } + + private void GenerateIndexMd() + { + context.Information("DocsBuild: Generate index.md"); + var content = new StringBuilder(); + content.AppendLine("---"); + content.AppendLine("title: Home"); + content.AppendLine("---"); + content.Append(context.FileReadText(context.RootDirectory.CombineWithFilePath("README.md"))); + context.FileWriteText(context.DocsDirectory.CombineWithFilePath("index.md"), content.ToString()); + } + + public void Update() + { + context.EnsureChangelogDetailsExist(); + + ReadmeUpdater.Run(context); + + if (string.IsNullOrEmpty(Repo.ProductHeader)) + throw new Exception($"Environment variable '{Repo.ProductHeaderVar}' is not specified!"); + if (string.IsNullOrEmpty(Repo.Token)) + throw new Exception($"Environment variable '{Repo.TokenVar}' is not specified!"); + + var count = context.Depth; + var total = DocumentationHelper.BdnAllVersions.Length; + + if (count == 0) + { + context.DocfxChangelogDownload( + DocumentationHelper.BdnAllVersions.First(), + DocumentationHelper.BdnFirstCommit); + + for (int i = 1; i < total; i++) + context.DocfxChangelogDownload( + DocumentationHelper.BdnAllVersions[i], + DocumentationHelper.BdnAllVersions[i - 1]); + } + else if (count > 0) + { + for (int i = Math.Max(total - count, 1); i < total; i++) + context.DocfxChangelogDownload( + DocumentationHelper.BdnAllVersions[i], + DocumentationHelper.BdnAllVersions[i - 1]); + } + + context.DocfxChangelogDownload( + DocumentationHelper.BdnNextVersion, + DocumentationHelper.BdnAllVersions.Last(), + "HEAD"); + } + + public void Prepare() + { + foreach (var version in DocumentationHelper.BdnAllVersions) + context.DocfxChangelogGenerate(version); + context.DocfxChangelogGenerate(DocumentationHelper.BdnNextVersion); + + context.Information("DocfxChangelogGenerate: index.md"); + var indexContent = new StringBuilder(); + indexContent.AppendLine("---"); + indexContent.AppendLine("uid: changelog"); + indexContent.AppendLine("---"); + indexContent.AppendLine(""); + indexContent.AppendLine("# ChangeLog"); + indexContent.AppendLine(""); + foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) + indexContent.AppendLine($"* @changelog.{version}"); + indexContent.AppendLine("* @changelog.full"); + context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("index.md"), indexContent.ToString()); + + context.Information("DocfxChangelogGenerate: full.md"); + var fullContent = new StringBuilder(); + fullContent.AppendLine("---"); + fullContent.AppendLine("uid: changelog.full"); + fullContent.AppendLine("---"); + fullContent.AppendLine(""); + fullContent.AppendLine("# Full ChangeLog"); + fullContent.AppendLine(""); + foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) + fullContent.AppendLine($"[!include[{version}]({version}.md)]"); + context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("full.md"), fullContent.ToString()); + + context.Information("DocfxChangelogGenerate: toc.yml"); + var tocContent = new StringBuilder(); + foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) + { + tocContent.AppendLine($"- name: {version}"); + tocContent.AppendLine($" href: {version}.md"); + } + + tocContent.AppendLine("- name: Full ChangeLog"); + tocContent.AppendLine(" href: full.md"); + context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("toc.yml"), tocContent.ToString()); + } + + public void Build() + { + GenerateIndexMd(); + RunDocfx(context.DocfxJsonFile); + GenerateRedirects(); + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Runners/ReadmeUpdater.cs b/build/BenchmarkDotNet.Build/Runners/ReadmeUpdater.cs new file mode 100644 index 0000000000..c040beada3 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Runners/ReadmeUpdater.cs @@ -0,0 +1,82 @@ +using System; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using BenchmarkDotNet.Build.Meta; +using Cake.FileHelpers; + +namespace BenchmarkDotNet.Build.Runners; + +public class ReadmeUpdater +{ + public static void Run(BuildContext context) => new ReadmeUpdater().RunInternal(context); + + private void RunInternal(BuildContext context) + { + var dependentProjectsNumber = GetDependentProjectsNumber().Result; + var updaters = new LineUpdater[] + { + new( + "The library is adopted by", + @"\[(\d+)\+ GitHub projects\]", + dependentProjectsNumber + ), + new( + "BenchmarkDotNet is already adopted by more than ", + @"\[(\d+)\+\]", + dependentProjectsNumber + ), + }; + + var file = context.RootDirectory.CombineWithFilePath("README.md"); + var lines = context.FileReadLines(file); + for (var i = 0; i < lines.Length; i++) + { + foreach (var updater in updaters) + lines[i] = updater.Apply(lines[i]); + } + + context.FileWriteLines(file, lines); + } + + private static async Task GetDependentProjectsNumber() + { + using var httpClient = new HttpClient(); + const string url = $"{Repo.HttpsUrlBase}/network/dependents"; + var response = await httpClient.GetAsync(new Uri(url)); + var dependentsPage = await response.Content.ReadAsStringAsync(); + var match = new Regex(@"([0-9\,]+)[\n\r\s]+Repositories").Match(dependentsPage); + var number = int.Parse(match.Groups[1].Value.Replace(",", "")); + number = number / 100 * 100; + return number; + } + + private class LineUpdater + { + public string Prefix { get; } + public Regex Regex { get; } + public int Value { get; } + + public LineUpdater(string prefix, string regex, int value) + { + Prefix = prefix; + Regex = new Regex(regex); + Value = value; + } + + public string Apply(string line) + { + if (!line.StartsWith(Prefix)) + return line; + + var match = Regex.Match(line); + if (!match.Success) + return line; + + // Groups[1] refers to the first group (\d+) + var numberString = match.Groups[1].Value; + var number = int.Parse(numberString); + return line.Replace(number.ToString(), Value.ToString()); + } + } +} \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs new file mode 100644 index 0000000000..48d5183e14 --- /dev/null +++ b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs @@ -0,0 +1,74 @@ +using BenchmarkDotNet.Build.Helpers; +using Cake.Common; +using Cake.Common.Diagnostics; +using Cake.Common.Tools.DotNet; +using Cake.Common.Tools.DotNet.Test; +using Cake.Core.IO; + +namespace BenchmarkDotNet.Build.Runners; + +public class UnitTestRunner +{ + private readonly BuildContext context; + + private FilePath UnitTestsProjectFile { get; } + private FilePath IntegrationTestsProjectFile { get; } + private DirectoryPath TestOutputDirectory { get; } + + public UnitTestRunner(BuildContext context) + { + this.context = context; + UnitTestsProjectFile = context.RootDirectory + .Combine("tests") + .Combine("BenchmarkDotNet.Tests") + .CombineWithFilePath("BenchmarkDotNet.Tests.csproj"); + IntegrationTestsProjectFile = context.RootDirectory + .Combine("tests") + .Combine("BenchmarkDotNet.IntegrationTests") + .CombineWithFilePath("BenchmarkDotNet.IntegrationTests.csproj"); + TestOutputDirectory = context.RootDirectory + .Combine("TestResults"); + } + + private DotNetTestSettings GetTestSettingsParameters(FilePath logFile, string tfm) + { + var settings = new DotNetTestSettings + { + Configuration = context.BuildConfiguration, + Framework = tfm, + NoBuild = true, + NoRestore = true, + Loggers = new[] { "trx", $"trx;LogFileName={logFile.FullPath}", "console;verbosity=detailed" }, + EnvironmentVariables = + { + ["Platform"] = "" // force the tool to not look for the .dll in platform-specific directory + } + }; + return settings; + } + + private void RunTests(FilePath projectFile, string alias, string tfm) + { + var os = Utils.GetOs(); + var trxFileName = $"{os}-{alias}-{tfm}.trx"; + var trxFile = TestOutputDirectory.CombineWithFilePath(trxFileName); + var settings = GetTestSettingsParameters(trxFile, tfm); + + context.Information($"Run tests for {projectFile} ({tfm}), result file: '{trxFile}'"); + context.DotNetTest(projectFile.FullPath, settings); + } + + private void RunUnitTests(string tfm) => RunTests(UnitTestsProjectFile, "unit", tfm); + + public void RunUnitTests() + { + var targetFrameworks = context.IsRunningOnWindows() + ? new[] { "net462", "net7.0" } + : new[] { "net7.0" }; + + foreach (var targetFramework in targetFrameworks) + RunUnitTests(targetFramework); + } + + public void RunInTests(string tfm) => RunTests(IntegrationTestsProjectFile, "integration", tfm); +} \ No newline at end of file diff --git a/build/Program.cs b/build/Program.cs deleted file mode 100644 index e816a1c49e..0000000000 --- a/build/Program.cs +++ /dev/null @@ -1,661 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Build; -using Cake.Common; -using Cake.Common.Build; -using Cake.Common.Build.AppVeyor; -using Cake.Common.Diagnostics; -using Cake.Common.IO; -using Cake.Common.Tools.DotNet; -using Cake.Common.Tools.DotNet.Build; -using Cake.Common.Tools.DotNet.MSBuild; -using Cake.Common.Tools.DotNet.Pack; -using Cake.Common.Tools.DotNet.Restore; -using Cake.Common.Tools.DotNet.Test; -using Cake.Core; -using Cake.Core.IO; -using Cake.FileHelpers; -using Cake.Frosting; -using Cake.Git; - -public static class Program -{ - public static int Main(string[] args) - { - return new CakeHost() - .UseContext() - .Run(args); - } -} - -public class BuildContext : FrostingContext -{ - public string BuildConfiguration { get; set; } - public bool SkipTests { get; set; } - public bool SkipSlowTests { get; set; } - public string TargetVersion { get; set; } - - public DirectoryPath RootDirectory { get; } - public DirectoryPath ArtifactsDirectory { get; } - public DirectoryPath ToolsDirectory { get; } - public DirectoryPath DocsDirectory { get; } - public FilePath DocfxJsonFile { get; } - public DirectoryPath TestOutputDirectory { get; } - - public DirectoryPath ChangeLogDirectory { get; } - public DirectoryPath ChangeLogGenDirectory { get; } - - public DirectoryPath RedirectRootDirectory { get; } - public DirectoryPath RedirectTargetDirectory { get; } - - public FilePath SolutionFile { get; } - public FilePath UnitTestsProjectFile { get; } - public FilePath IntegrationTestsProjectFile { get; } - public FilePath TemplatesTestsProjectFile { get; } - public FilePathCollection AllPackableSrcProjects { get; } - - public DotNetMSBuildSettings MsBuildSettingsRestore { get; } - public DotNetMSBuildSettings MsBuildSettingsBuild { get; } - public DotNetMSBuildSettings MsBuildSettingsPack { get; } - - private IAppVeyorProvider AppVeyor => this.BuildSystem().AppVeyor; - public bool IsRunningOnAppVeyor => AppVeyor.IsRunningOnAppVeyor; - public bool IsOnAppVeyorAndNotPr => IsRunningOnAppVeyor && !AppVeyor.Environment.PullRequest.IsPullRequest; - - public bool IsOnAppVeyorAndBdnNightlyCiCd => IsOnAppVeyorAndNotPr && - AppVeyor.Environment.Repository.Branch == "master" && - this.IsRunningOnWindows(); - - public bool IsLocalBuild => this.BuildSystem().IsLocalBuild; - public bool IsCiBuild => !this.BuildSystem().IsLocalBuild; - - public BuildContext(ICakeContext context) - : base(context) - { - BuildConfiguration = context.Argument("Configuration", "Release"); - SkipTests = context.Argument("SkipTests", false); - SkipSlowTests = context.Argument("SkipSlowTests", false); - TargetVersion = context.Argument("Version", ""); - - RootDirectory = new DirectoryPath(new DirectoryInfo(Directory.GetCurrentDirectory()).Parent.FullName); - ArtifactsDirectory = RootDirectory.Combine("artifacts"); - ToolsDirectory = RootDirectory.Combine("tools"); - DocsDirectory = RootDirectory.Combine("docs"); - DocfxJsonFile = DocsDirectory.CombineWithFilePath("docfx.json"); - TestOutputDirectory = RootDirectory.Combine("TestResults"); - - ChangeLogDirectory = RootDirectory.Combine("docs").Combine("changelog"); - ChangeLogGenDirectory = RootDirectory.Combine("docs").Combine("_changelog"); - - RedirectRootDirectory = RootDirectory.Combine("docs").Combine("_redirects"); - RedirectTargetDirectory = RootDirectory.Combine("docs").Combine("_site"); - - SolutionFile = RootDirectory.CombineWithFilePath("BenchmarkDotNet.sln"); - UnitTestsProjectFile = RootDirectory.Combine("tests").Combine("BenchmarkDotNet.Tests") - .CombineWithFilePath("BenchmarkDotNet.Tests.csproj"); - IntegrationTestsProjectFile = RootDirectory.Combine("tests").Combine("BenchmarkDotNet.IntegrationTests") - .CombineWithFilePath("BenchmarkDotNet.IntegrationTests.csproj"); - TemplatesTestsProjectFile = RootDirectory.Combine("templates") - .CombineWithFilePath("BenchmarkDotNet.Templates.csproj"); - AllPackableSrcProjects = new FilePathCollection(context.GetFiles(RootDirectory.FullPath + "/src/**/*.csproj") - .Where(p => !p.FullPath.Contains("Disassembler"))); - - MsBuildSettingsRestore = new DotNetMSBuildSettings(); - MsBuildSettingsBuild = new DotNetMSBuildSettings(); - MsBuildSettingsPack = new DotNetMSBuildSettings(); - - if (IsCiBuild) - { - System.Environment.SetEnvironmentVariable("BDN_CI_BUILD", "true"); - - MsBuildSettingsBuild.MaxCpuCount = 1; - MsBuildSettingsBuild.WithProperty("UseSharedCompilation", "false"); - } - - if (!string.IsNullOrEmpty(TargetVersion)) - { - MsBuildSettingsRestore.WithProperty("Version", TargetVersion); - MsBuildSettingsBuild.WithProperty("Version", TargetVersion); - MsBuildSettingsPack.WithProperty("Version", TargetVersion); - } - - // NativeAOT build requires VS C++ tools to be added to $path via vcvars64.bat - // but once we do that, dotnet restore fails with: - // "Please specify a valid solution configuration using the Configuration and Platform properties" - if (context.IsRunningOnWindows()) - { - MsBuildSettingsRestore.WithProperty("Platform", "Any CPU"); - MsBuildSettingsBuild.WithProperty("Platform", "Any CPU"); - } - } - - private DotNetTestSettings GetTestSettingsParameters(FilePath logFile, string tfm) - { - var settings = new DotNetTestSettings - { - Configuration = BuildConfiguration, - Framework = tfm, - NoBuild = true, - NoRestore = true, - Loggers = new[] { "trx", $"trx;LogFileName={logFile.FullPath}", "console;verbosity=detailed" } - }; - // force the tool to not look for the .dll in platform-specific directory - settings.EnvironmentVariables["Platform"] = ""; - return settings; - } - - public void RunTests(FilePath projectFile, string alias, string tfm) - { - var xUnitXmlFile = TestOutputDirectory.CombineWithFilePath(alias + "-" + tfm + ".trx"); - this.Information($"Run tests for {projectFile} ({tfm}), result file: '{xUnitXmlFile}'"); - var settings = GetTestSettingsParameters(xUnitXmlFile, tfm); - this.DotNetTest(projectFile.FullPath, settings); - } - - public void EnsureChangelogDetailsExist(bool forceClean = false) - { - var path = ChangeLogGenDirectory.Combine("details"); - if (this.DirectoryExists(path) && forceClean) - this.DeleteDirectory(path, new DeleteDirectorySettings() { Force = true, Recursive = true }); - - if (!this.DirectoryExists(path)) - { - var settings = new GitCloneSettings { Checkout = true, BranchName = "docs-changelog-details" }; - this.GitClone("https://github.com/dotnet/BenchmarkDotNet.git", path, settings); - } - } - - public void DocfxChangelogDownload(string version, string versionPrevious, string lastCommit = "") - { - EnsureChangelogDetailsExist(true); - this.Information("DocfxChangelogDownload: " + version); - // Required environment variables: GITHUB_PRODUCT, GITHUB_TOKEN - var path = ChangeLogGenDirectory.Combine("details"); - ChangeLogBuilder.Run(path, version, versionPrevious, lastCommit).Wait(); - } - - public void DocfxChangelogGenerate(string version) - { - EnsureChangelogDetailsExist(); - this.Information("DocfxChangelogGenerate: " + version); - var header = ChangeLogGenDirectory.Combine("header").CombineWithFilePath(version + ".md"); - var footer = ChangeLogGenDirectory.Combine("footer").CombineWithFilePath(version + ".md"); - var details = ChangeLogGenDirectory.Combine("details").CombineWithFilePath(version + ".md"); - var release = ChangeLogDirectory.CombineWithFilePath(version + ".md"); - - var content = new StringBuilder(); - content.AppendLine("---"); - content.AppendLine("uid: changelog." + version); - content.AppendLine("---"); - content.AppendLine(""); - content.AppendLine("# BenchmarkDotNet " + version); - content.AppendLine(""); - content.AppendLine(""); - - if (this.FileExists(header)) - { - content.AppendLine(this.FileReadText(header)); - content.AppendLine(""); - content.AppendLine(""); - } - - if (this.FileExists(details)) - { - content.AppendLine(this.FileReadText(details)); - content.AppendLine(""); - content.AppendLine(""); - } - - if (this.FileExists(footer)) - { - content.AppendLine("## Additional details"); - content.AppendLine(""); - content.AppendLine(this.FileReadText(footer)); - } - - this.FileWriteText(release, content.ToString()); - } - - public void RunDocfx(FilePath docfxJson) - { - this.Information($"Running docfx for '{docfxJson}'"); - - var currentDirectory = Directory.GetCurrentDirectory(); - Directory.SetCurrentDirectory(docfxJson.GetDirectory().FullPath); - Microsoft.DocAsCode.Dotnet.DotnetApiCatalog.GenerateManagedReferenceYamlFiles(docfxJson.FullPath).Wait(); - Microsoft.DocAsCode.Docset.Build(docfxJson.FullPath).Wait(); - Directory.SetCurrentDirectory(currentDirectory); - } - - public void GenerateRedirects() - { - var redirectFile = RedirectRootDirectory.CombineWithFilePath("_redirects"); - if (!this.FileExists(redirectFile)) - { - this.Error($"Redirect file '{redirectFile}' does not exist"); - return; - } - - this.EnsureDirectoryExists(RedirectTargetDirectory); - - var redirects = this.FileReadLines(redirectFile) - .Select(line => line.Split(' ')) - .Select(parts => (source: parts[0], target: parts[1])) - .ToList(); - - foreach (var (source, target) in redirects) - { - var fileName = source.StartsWith("/") || source.StartsWith("\\") ? source[1..] : source; - var fullFileName = RedirectTargetDirectory.CombineWithFilePath(fileName); - var content = - $"" + - $"" + - $"" + - $"{target}" + - $"" + - $"" + - $"" + - $"" + - $""; - this.EnsureDirectoryExists(fullFileName.GetDirectory()); - this.FileWriteText(fullFileName, content); - } - } -} - -public static class DocumentationHelper -{ - public static readonly string[] BdnAllVersions = - { - "v0.7.0", - "v0.7.1", - "v0.7.2", - "v0.7.3", - "v0.7.4", - "v0.7.5", - "v0.7.6", - "v0.7.7", - "v0.7.8", - "v0.8.0", - "v0.8.1", - "v0.8.2", - "v0.9.0", - "v0.9.1", - "v0.9.2", - "v0.9.3", - "v0.9.4", - "v0.9.5", - "v0.9.6", - "v0.9.7", - "v0.9.8", - "v0.9.9", - "v0.10.0", - "v0.10.1", - "v0.10.2", - "v0.10.3", - "v0.10.4", - "v0.10.5", - "v0.10.6", - "v0.10.7", - "v0.10.8", - "v0.10.9", - "v0.10.10", - "v0.10.11", - "v0.10.12", - "v0.10.13", - "v0.10.14", - "v0.11.0", - "v0.11.1", - "v0.11.2", - "v0.11.3", - "v0.11.4", - "v0.11.5", - "v0.12.0", - "v0.12.1", - "v0.13.0", - "v0.13.1", - "v0.13.2", - "v0.13.3", - "v0.13.4", - "v0.13.5" - }; - - public const string BdnNextVersion = "v0.13.6"; - public const string BdnFirstCommit = "6eda98ab1e83a0d185d09ff8b24c795711af8db1"; -} - -[TaskName("Clean")] -public class CleanTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.CleanDirectory(context.ArtifactsDirectory); - } -} - -[TaskName("Restore")] -[IsDependentOn(typeof(CleanTask))] -public class RestoreTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.DotNetRestore(context.SolutionFile.FullPath, - new DotNetRestoreSettings - { - MSBuildSettings = context.MsBuildSettingsRestore - }); - } -} - -[TaskName("Build")] -[IsDependentOn(typeof(RestoreTask))] -public class BuildTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.Information("BuildSystemProvider: " + context.BuildSystem().Provider); - context.DotNetBuild(context.SolutionFile.FullPath, new DotNetBuildSettings - { - Configuration = context.BuildConfiguration, - NoRestore = true, - DiagnosticOutput = true, - MSBuildSettings = context.MsBuildSettingsBuild, - Verbosity = DotNetVerbosity.Minimal - }); - } -} - -[TaskName("FastTests")] -[IsDependentOn(typeof(BuildTask))] -public class FastTestsTask : FrostingTask -{ - public override bool ShouldRun(BuildContext context) - { - return !context.SkipTests; - } - - public override void Run(BuildContext context) - { - var targetFrameworks = context.IsRunningOnWindows() - ? new[] { "net462", "net7.0" } - : new[] { "net7.0" }; - - foreach (var targetFramework in targetFrameworks) - context.RunTests(context.UnitTestsProjectFile, "UnitTests", targetFramework); - } -} - -[TaskName("SlowFullFrameworkTests")] -[IsDependentOn(typeof(BuildTask))] -public class SlowFullFrameworkTestsTask : FrostingTask -{ - public override bool ShouldRun(BuildContext context) - { - return !context.SkipTests && !context.SkipSlowTests && context.IsRunningOnWindows() && - !context.IsRunningOnAppVeyor; - } - - public override void Run(BuildContext context) - { - context.RunTests(context.IntegrationTestsProjectFile, "IntegrationTests", "net462"); - } -} - -[TaskName("SlowTestsNetCore")] -[IsDependentOn(typeof(BuildTask))] -public class SlowTestsNetCoreTask : FrostingTask -{ - public override bool ShouldRun(BuildContext context) - { - return !context.SkipTests && !context.SkipSlowTests; - } - - public override void Run(BuildContext context) - { - context.RunTests(context.IntegrationTestsProjectFile, "IntegrationTests", "net7.0"); - } -} - -[TaskName("AllTests")] -[IsDependentOn(typeof(FastTestsTask))] -[IsDependentOn(typeof(SlowFullFrameworkTestsTask))] -[IsDependentOn(typeof(SlowTestsNetCoreTask))] -public class AllTestsTask : FrostingTask -{ -} - -[TaskName("Pack")] -[IsDependentOn(typeof(BuildTask))] -public class PackTask : FrostingTask -{ - public override bool ShouldRun(BuildContext context) - { - return context.IsOnAppVeyorAndBdnNightlyCiCd || context.IsLocalBuild; - } - - public override void Run(BuildContext context) - { - var settingsSrc = new DotNetPackSettings - { - Configuration = context.BuildConfiguration, - OutputDirectory = context.ArtifactsDirectory.FullPath, - ArgumentCustomization = args => args.Append("--include-symbols").Append("-p:SymbolPackageFormat=snupkg"), - MSBuildSettings = context.MsBuildSettingsPack - }; - - foreach (var project in context.AllPackableSrcProjects) - context.DotNetPack(project.FullPath, settingsSrc); - - var settingsTemplate = new DotNetPackSettings - { - Configuration = context.BuildConfiguration, - OutputDirectory = context.ArtifactsDirectory.FullPath - }; - context.DotNetPack(context.TemplatesTestsProjectFile.FullPath, settingsTemplate); - } -} - -[TaskName("Default")] -[IsDependentOn(typeof(AllTestsTask))] -[IsDependentOn(typeof(PackTask))] -public class DefaultTask : FrostingTask -{ -} - - -[TaskName("DocFX_Changelog_Download")] -public class DocFxChangelogDownloadTask : FrostingTask -{ - public override void Run(BuildContext context) - { - var count = context.Argument("VersionCount", -1); - var total = DocumentationHelper.BdnAllVersions.Length; - - if (count == 0) - { - context.DocfxChangelogDownload( - DocumentationHelper.BdnAllVersions.First(), - DocumentationHelper.BdnFirstCommit); - - for (int i = 1; i < total; i++) - context.DocfxChangelogDownload( - DocumentationHelper.BdnAllVersions[i], - DocumentationHelper.BdnAllVersions[i - 1]); - } - else if (count > 0) - { - for (int i = Math.Max(total - count, 1); i < total; i++) - context.DocfxChangelogDownload( - DocumentationHelper.BdnAllVersions[i], - DocumentationHelper.BdnAllVersions[i - 1]); - } - - context.DocfxChangelogDownload( - DocumentationHelper.BdnNextVersion, - DocumentationHelper.BdnAllVersions.Last(), - "HEAD"); - } -} - -[TaskName("DocFX_Changelog_Generate")] -public class DocfxChangelogGenerateTask : FrostingTask -{ - public override void Run(BuildContext context) - { - foreach (var version in DocumentationHelper.BdnAllVersions) - context.DocfxChangelogGenerate(version); - context.DocfxChangelogGenerate(DocumentationHelper.BdnNextVersion); - - context.Information("DocfxChangelogGenerate: index.md"); - var indexContent = new StringBuilder(); - indexContent.AppendLine("---"); - indexContent.AppendLine("uid: changelog"); - indexContent.AppendLine("---"); - indexContent.AppendLine(""); - indexContent.AppendLine("# ChangeLog"); - indexContent.AppendLine(""); - foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) - indexContent.AppendLine($"* @changelog.{version}"); - indexContent.AppendLine("* @changelog.full"); - context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("index.md"), indexContent.ToString()); - - context.Information("DocfxChangelogGenerate: full.md"); - var fullContent = new StringBuilder(); - fullContent.AppendLine("---"); - fullContent.AppendLine("uid: changelog.full"); - fullContent.AppendLine("---"); - fullContent.AppendLine(""); - fullContent.AppendLine("# Full ChangeLog"); - fullContent.AppendLine(""); - foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) - fullContent.AppendLine($"[!include[{version}]({version}.md)]"); - context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("full.md"), fullContent.ToString()); - - context.Information("DocfxChangelogGenerate: toc.yml"); - var tocContent = new StringBuilder(); - foreach (var version in DocumentationHelper.BdnAllVersions.Reverse()) - { - tocContent.AppendLine($"- name: {version}"); - tocContent.AppendLine($" href: {version}.md"); - } - - tocContent.AppendLine("- name: Full ChangeLog"); - tocContent.AppendLine(" href: full.md"); - context.FileWriteText(context.ChangeLogDirectory.CombineWithFilePath("toc.yml"), tocContent.ToString()); - } -} - -[TaskName("DocFX_Generate_Redirects")] -public class DocfxGenerateRedirectsTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.GenerateRedirects(); - } -} - -// In order to work around xref issues in DocFx, BenchmarkDotNet and BenchmarkDotNet.Annotations must be build -// before running the DocFX_Build target. However, including a dependency on BuildTask here may have unwanted -// side effects (CleanTask). -// TODO: Define dependencies when a CI workflow scenario for using the "DocFX_Build" target exists. -[TaskName("DocFX_Build")] -[IsDependentOn(typeof(DocfxChangelogGenerateTask))] -public class DocfxBuildTask : FrostingTask -{ - public override void Run(BuildContext context) - { - context.Information("DocfxBuild: Generate index.md"); - var content = new StringBuilder(); - content.AppendLine("---"); - content.AppendLine("title: Home"); - content.AppendLine("---"); - content.Append(context.FileReadText(context.RootDirectory.CombineWithFilePath("README.md"))); - context.FileWriteText(context.DocsDirectory.CombineWithFilePath("index.md"), content.ToString()); - - context.RunDocfx(context.DocfxJsonFile); - context.GenerateRedirects(); - } -} - -[TaskName("UpdateStats")] -public class UpdateStatsTask : FrostingTask -{ - public class Updater - { - public string Prefix { get; } - public Regex Regex { get; } - public int Value { get; } - - public Updater(string prefix, string regex, int value) - { - Prefix = prefix; - Regex = new Regex(regex); - Value = value; - } - - public string Apply(string line) - { - if (!line.StartsWith(Prefix)) - return line; - - var match = Regex.Match(line); - if (!match.Success) - return line; - - // Groups[1] refers to the first group (\d+) - var numberString = match.Groups[1].Value; - var number = int.Parse(numberString); - return line.Replace(number.ToString(), Value.ToString()); - } - } - - private static async Task GetDependentProjectsNumber() - { - using var httpClient = new HttpClient(); - const string url = "https://github.com/dotnet/BenchmarkDotNet/network/dependents"; - var response = await httpClient.GetAsync(new Uri(url)); - var dependentsPage = await response.Content.ReadAsStringAsync(); - var match = new Regex(@"([0-9\,]+)[\n\r\s]+Repositories").Match(dependentsPage); - var number = int.Parse(match.Groups[1].Value.Replace(",", "")); - number = number / 100 * 100; - return number; - } - - public override void Run(BuildContext context) - { - var dependentProjectsNumber = GetDependentProjectsNumber().Result; - var updaters = new Updater[] - { - new( - "The library is adopted by", - @"\[(\d+)\+ GitHub projects\]", - dependentProjectsNumber - ), - new( - "BenchmarkDotNet is already adopted by more than ", - @"\[(\d+)\+\]", - dependentProjectsNumber - ), - }; - var files = new[] - { - context.RootDirectory.CombineWithFilePath("README.md") - }; - foreach (var file in files) - { - var lines = context.FileReadLines(file); - for (var i = 0; i < lines.Length; i++) - { - foreach (var updater in updaters) - lines[i] = updater.Apply(lines[i]); - } - - context.FileWriteLines(file, lines); - } - } -} \ No newline at end of file diff --git a/build/azure-pipelines.job.template.yml b/build/azure-pipelines.job.template.yml deleted file mode 100755 index d2f11f46f2..0000000000 --- a/build/azure-pipelines.job.template.yml +++ /dev/null @@ -1,32 +0,0 @@ -parameters: - name: '' - vmImage: '' - scriptFileName: '' - timeoutInMinutes: 120 - initialization: [] - -jobs: -- job: ${{ parameters.name }} - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - pool: - vmImage: ${{ parameters.vmImage }} - steps: - - ${{ parameters.initialization }} - # Linux or macOS - - bash: ${{ parameters.scriptFileName }} - continueOnError: true - condition: in( variables['Agent.OS'], 'Linux', 'Darwin' ) - # Windows - - powershell: ${{ parameters.scriptFileName }} - continueOnError: true - condition: eq( variables['Agent.OS'], 'Windows_NT' ) - - task: PublishTestResults@2 - inputs: - testRunner: VSTest - testResultsFiles: '**/*.trx' - testRunTitle: ${{ parameters.name }} - mergeTestResults: true - - script: 'echo 1>&2' - failOnStderr: true - displayName: 'If above is partially succeeded, then fail' - condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues') \ No newline at end of file diff --git a/build.bat b/build/build.bat similarity index 100% rename from build.bat rename to build/build.bat diff --git a/build.ps1 b/build/build.ps1 similarity index 76% rename from build.ps1 rename to build/build.ps1 index d33f302d65..2f6d5a5d11 100755 --- a/build.ps1 +++ b/build/build.ps1 @@ -1,16 +1,8 @@ #!/usr/bin/env pwsh -$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; -$DotNetUnixInstallerUri = 'https://dot.net/v1/dotnet-install.sh' -$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -$BuildPath = Join-Path $PSScriptRoot "build" - -# Make sure tools folder exists -$ToolPath = Join-Path $PSScriptRoot "tools" -if (!(Test-Path $ToolPath)) { - Write-Verbose "Creating tools directory..." - New-Item -Path $ToolPath -Type Directory -Force | out-null -} +$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; +$BuildPath = Split-Path $MyInvocation.MyCommand.Path -Parent +$PSScriptRoot = Split-Path $PSScriptRoot -Parent if ($PSVersionTable.PSEdition -ne 'Core') { # Attempt to set highest encryption available for SecurityProtocol. @@ -36,7 +28,6 @@ $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 $env:DOTNET_CLI_TELEMETRY_OPTOUT=1 $env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 - Function Remove-PathVariable([string]$VariableToRemove) { $SplitChar = ';' @@ -60,20 +51,10 @@ Function Remove-PathVariable([string]$VariableToRemove) } $InstallPath = Join-Path $PSScriptRoot ".dotnet" -$GlobalJsonPath = Join-Path $BuildPath "global.json" +$SdkPath = Join-Path $BuildPath "sdk" +$GlobalJsonPath = Join-Path $SdkPath "global.json" if (!(Test-Path $InstallPath)) { New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null; -} - -if ($IsMacOS -or $IsLinux) { - $ScriptPath = Join-Path $InstallPath 'dotnet-install.sh' - (New-Object System.Net.WebClient).DownloadFile($DotNetUnixInstallerUri, $ScriptPath); - & bash $ScriptPath --jsonfile "$GlobalJsonPath" --install-dir "$InstallPath" --no-path - - Remove-PathVariable "$InstallPath" - $env:PATH = "$($InstallPath):$env:PATH" -} -else { $ScriptPath = Join-Path $InstallPath 'dotnet-install.ps1' (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath); & $ScriptPath -JSonFile $GlobalJsonPath -InstallDir $InstallPath; @@ -81,11 +62,12 @@ else { Remove-PathVariable "$InstallPath" $env:PATH = "$InstallPath;$env:PATH" } + $env:DOTNET_ROOT=$InstallPath ########################################################################### # RUN BUILD SCRIPT ########################################################################### -& dotnet run --project build/Build.csproj -- $args +& dotnet run --configuration Release --project build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj -- $args exit $LASTEXITCODE; \ No newline at end of file diff --git a/build.sh b/build/build.sh similarity index 61% rename from build.sh rename to build/build.sh index cf86d75757..ebf8ef04bd 100755 --- a/build.sh +++ b/build/build.sh @@ -1,13 +1,7 @@ #!/usr/bin/env bash -# Define varibles -SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -TOOLS_DIR=$SCRIPT_DIR/tools - -# Make sure the tools folder exist. -if [ ! -d "$TOOLS_DIR" ]; then - mkdir "$TOOLS_DIR" -fi +# Define variables +SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd ) ########################################################################### # INSTALL .NET CORE CLI @@ -20,9 +14,10 @@ export DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then mkdir "$SCRIPT_DIR/.dotnet" + curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh + bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --jsonfile ./build/sdk/global.json --install-dir .dotnet --no-path fi -curl -Lsfo "$SCRIPT_DIR/.dotnet/dotnet-install.sh" https://dot.net/v1/dotnet-install.sh -bash "$SCRIPT_DIR/.dotnet/dotnet-install.sh" --jsonfile ./build/global.json --install-dir .dotnet --no-path + export PATH="$SCRIPT_DIR/.dotnet":$PATH export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" @@ -30,4 +25,4 @@ export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" # RUN BUILD SCRIPT ########################################################################### -dotnet run --project ./build/Build.csproj -- "$@" +dotnet run --configuration Release --project ./build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj -- "$@" diff --git a/build/global.json b/build/sdk/global.json similarity index 64% rename from build/global.json rename to build/sdk/global.json index b44053f07a..5e4624ef91 100644 --- a/build/global.json +++ b/build/sdk/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.304", + "version": "7.0.305", "rollForward": "disable" } } diff --git a/docs/articles/contributing/building.md b/docs/articles/contributing/building.md index 71487cdaa0..b8c84ca942 100644 --- a/docs/articles/contributing/building.md +++ b/docs/articles/contributing/building.md @@ -6,11 +6,11 @@ There are two recommended options to build BenchmarkDotNet from source: - [Visual Studio](https://www.visualstudio.com/downloads/) (Community, Professional, Enterprise) with .NET 4.6.2 SDK and F# support. -- [.NET 5 SDK](https://dotnet.microsoft.com/download). +- [.NET 7 SDK](https://dotnet.microsoft.com/download). Once all the necessary tools are in place, building is trivial. Simply open solution file **BenchmarkDotNet.sln** that lives at the base of the repository and run Build action. -## Cake (C# Make) +## Command-line [Cake (C# Make)](https://cakebuild.net/) is a cross platform build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. @@ -36,19 +36,6 @@ The build currently depends on the following prerequisites: - Install [fsharp package](https://fsharp.org/use/mac/) - Install the latest version of [OpenSSL](https://www.openssl.org/source/). -After you have installed these pre-requisites, you can build the BenchmarkDotNet by invoking the build script (`build.ps1` on Windows, or `build.sh` on Linux and macOS) at the base of the BenchmarkDotNet repository. By default the build process also run all the tests. There are quite a few tests, taking a significant amount of time that is not necessary if you just want to experiment with changes. You can skip the tests phase by adding the `skiptests` argument to the build script, e.g. `.\build.ps1 --SkipTests=True` or `./build.sh --skiptests=true`. - -Build has a number of options that you use. Some of the more important options are - -- **`skiptests`** - do not run the tests. This can shorten build times quite a bit. On Windows: `.\build.ps1 --SkipTests=True` or `./build.sh --skiptests=true` on Linux/macOS. - -- **`configuration`** - build the 'Release' or 'Debug' build type. Default value is 'Release'. On Windows: `.\build.ps1 -Configuration Debug` or `./build.sh --configuration debug` on Linux/macOS. - -- **`target`** - with this parameter you can run a specific target from build pipeline. Default value is 'Default' target. On Windows: `.\build.ps1 -Target Default` or `./build.sh --target default` on Linux/macOS. Available targets: - - **`Default`** - run all actions one by one. - - **`Clean`** - clean all `obj`, `bin` and `artifacts` directories. - - **`Restore`** - automatically execute `Clean` action and after that restore all NuGet dependencies. - - **`Build`** - automatically execute `Restore` action, then run MSBuild for the solution file. - - **`FastTests`** - automatically execute `Build` action, then run all tests from the BenchmarkDotNet.Tests project. - - **`SlowTests`** - automatically execute `Build` action, then run all tests from the BenchmarkDotNet.IntegrationTests project. - - **`Pack`** - automatically execute `Build` action and after that creates local NuGet packages. +In order to run various build tasks from terminal, use `build.cmd` file in the repository root. +`build.cmd` is a cross-platform script that can be used the same way on Windows, Linux, and macOS. +When executed without arguments, it prints help information with list of all available build tasks. \ No newline at end of file diff --git a/docs/articles/contributing/documentation.md b/docs/articles/contributing/documentation.md index 6bdbc944ff..a0f23dd2d2 100644 --- a/docs/articles/contributing/documentation.md +++ b/docs/articles/contributing/documentation.md @@ -49,31 +49,12 @@ It will be transformed to: ## Building documentation locally -You can build documentation locally with the help of the `DocFX_Build` Cake target. -Use the `DocFX_Serve` Cake target to build and run the documentation. - -Windows (PowerShell): - -``` -.\build.ps1 --target DocFX_Build -.\build.ps1 --target DocFX_Serve -``` - -Windows (Batch): +You can build documentation locally with the help of the `DocsBuild` build task: ``` -.\build.bat --target DocFX_Build -.\build.bat --target DocFX_Serve +build.cmd DocsBuild ``` -Linux/macOS (Bash): - -``` -./build.sh --target DocFX_Build -./build.sh --target DocFX_Serve -``` - - ## See also * [DocFX User Manual](https://dotnet.github.io/docfx/tutorial/docfx.exe_user_manual.html) diff --git a/docs/articles/guides/nuget.md b/docs/articles/guides/nuget.md index 0d903802ea..0e2de62a79 100644 --- a/docs/articles/guides/nuget.md +++ b/docs/articles/guides/nuget.md @@ -9,17 +9,21 @@ name: Installing NuGet packages We have the following set of NuGet packages (you can install it directly from `nuget.org`): -* `BenchmarkDotNet`: Basic BenchmarkDotNet infrastructure and logic. This is all you need to run benchmarks. +* `BenchmarkDotNet`: BenchmarkDotNet infrastructure and logic. This is all you need to run benchmarks. +* `BenchmarkDotNet.Annotations`: Basic BenchmarkDotNet annotations for your benchmarks. * `BenchmarkDotNet.Diagnostics.Windows`: an additional optional package that provides a set of Windows diagnosers. +* `BenchmarkDotNet.Diagnostics.dotTrace`: an additional optional package that provides DotTraceDiagnoser. * `BenchmarkDotNet.Templates`: Templates for BenchmarkDotNet. You might find other NuGet packages that start with `BenchmarkDotNet` name, but they are internal BDN packages that should not be installed manually. All that matters are the three packages mentioned above. ## Versioning system and feeds + We have 3 kinds of versions: *stable*, *nightly*, and *develop*. You can get the current version from the source code via `BenchmarkDotNetInfo.FullVersion` and the full title via `BenchmarkDotNetInfo.FullTitle`. ### Stable + These versions are available from the official NuGet feed. ```xml @@ -28,26 +32,22 @@ These versions are available from the official NuGet feed. ``` -* Example of the main NuGet package: `BenchmarkDotNet.0.10.3.nupkg`. -* Example of `BenchmarkDotNetInfo.FullTitle`: `BenchmarkDotNet v0.10.3`. - ### Nightly -If you want to use a nightly version of the BenchmarkDotNet, add the `https://ci.appveyor.com/nuget/benchmarkdotnet` feed in the `` section of your `NuGet.config`: + +If you want to use a nightly version of the BenchmarkDotNet, add the `https://www.myget.org/F/benchmarkdotnet/api/v3/index.json` feed in the `` section of your `NuGet.config`: ```xml - + ``` Now you can install the packages from the `bdn-nightly` feed. -* Example of the main NuGet package: `BenchmarkDotNet.0.10.3.13.nupkg`. -* Example of `BenchmarkDotNetInfo.FullTitle`: `BenchmarkDotNet v0.10.3.13-nightly`. - ### Develop -You also can build BenchmarkDotNet from source code. -The `.nupkg` files could be build with the help of `.\build\build-and-pack.cmd`. -* Example of the main NuGet package: `BenchmarkDotNet.0.10.3-develop.nupkg`. -* Example of `BenchmarkDotNetInfo.FullTitle`: `BenchmarkDotNet v0.10.3.20170304-develop`. +You also can build BenchmarkDotNet from source code: + +```sh +build.cmd pack +``` \ No newline at end of file