Huge build project refactoring

This commit is contained in:
Andrey Akinshin 2023-07-05 19:00:01 +02:00
Родитель 144f5c5552
Коммит 6291a7e724
32 изменённых файлов: 1437 добавлений и 870 удалений

125
.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:
- 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 '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:
- 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
run: ./build.sh
- 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: 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

Просмотреть файл

@ -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 }}

6
.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

24
.github/workflows/publish-nightly.yaml поставляемый Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -37,7 +37,7 @@ before_build:
#---------------------------------#
build_script:
- ps: .\build.ps1
- ps: .\build.cmd CI
test: off
deploy: off

Просмотреть файл

@ -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 -

Просмотреть файл

@ -13,4 +13,4 @@ jobs:
parameters:
name: Windows
vmImage: 'windows-2019'
scriptFileName: .\build.ps1
scriptFileName: .\build.cmd CI

Просмотреть файл

@ -13,4 +13,4 @@ jobs:
parameters:
name: macOS
vmImage: 'macOS-latest'
scriptFileName: ./build.sh
scriptFileName: ./build.cmd CI

5
build.cmd Executable file
Просмотреть файл

@ -0,0 +1,5 @@
:<<"::CMDLITERAL"
@CALL build\build.bat %*
@GOTO :EOF
::CMDLITERAL
"$(cd "$(dirname "$0")"; pwd)/build/build.sh" "$@"

Просмотреть файл

@ -3,13 +3,13 @@
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake.Frosting" Version="3.0.0" />
<PackageReference Include="Cake.FileHelpers" Version="6.1.3" />
<PackageReference Include="Cake.Git" Version="3.0.0" />
<PackageReference Include="Microsoft.DocAsCode.App" Version="2.67.5" />
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="Octokit" Version="7.0.0" />
</ItemGroup>
</Project>

Просмотреть файл

@ -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());
}
}

Просмотреть файл

@ -5,78 +5,28 @@ using System.IO;
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)
private class Config
{
var message = commit.Message.Trim().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.FirstOrDefault() ?? "";
return message.Length > 80 ? message.Substring(0, 77) + "..." : message;
}
public string CurrentMilestone { get; }
public string PreviousMilestone { get; }
public string LastCommit { get; }
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
{
[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; }
[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 class ChangeLogBuilder
}
}
public class AuthorEqualityComparer : IEqualityComparer<Author>
private class MarkdownBuilder
{
public static readonly IEqualityComparer<Author> 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 static IReadOnlyList<Milestone> AllMilestones = null;
private static IReadOnlyList<Milestone>? allMilestones;
private static readonly Dictionary<string, string> AuthorNames = new();
private readonly Config config;
private readonly StringBuilder builder;
@ -115,17 +56,17 @@ public class ChangeLogBuilder
private async Task<string> 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 @@ public class ChangeLogBuilder
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<Issue> allIssues = Array.Empty<Issue>();
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 @@ public class ChangeLogBuilder
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 @@ public class ChangeLogBuilder
.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 @@ public class ChangeLogBuilder
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 @@ public class ChangeLogBuilder
}
private void AppendList<T>(string title, IReadOnlyList<T> items, Func<T, string> format,
string conclusion = null)
string? conclusion = null)
{
builder.AppendLine($"## {title} ({items.Count})");
builder.AppendLine();
@ -233,8 +174,9 @@ public class ChangeLogBuilder
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
{

Просмотреть файл

@ -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",
"<LEVEL>",
"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("<TASK> ");
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("<KEY>");
WriteOption("=");
WriteArg("<VALUE>");
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<string> 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<BuildContext>)) && !type.IsAbstract)
.Select(type => (
Name: type.GetCustomAttribute<TaskNameAttribute>()?.Name ?? "",
Description: type.GetCustomAttribute<TaskDescriptionAttribute>()?.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<BuildContext>().Run(new[] { "--help" });
return null;
}
}
var argsToProcess = new Queue<string>(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<string>
{
"--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);
}
}

Просмотреть файл

@ -0,0 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nupkg/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Просмотреть файл

@ -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") : "";
}
}

Просмотреть файл

@ -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<string, DotNetVerbosity>(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;
}
}

Просмотреть файл

@ -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";
}

Просмотреть файл

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

Просмотреть файл

@ -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<BuildContext>().Run(cakeArgs);
}
}
[TaskName("Restore")]
[TaskDescription("Restore NuGet packages")]
public class RestoreTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context) => context.BuildRunner.Restore();
}
[TaskName("Build")]
[TaskDescription("Build BenchmarkDotNet.sln solution")]
[IsDependentOn(typeof(RestoreTask))]
public class BuildTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context) => context.BuildRunner.Build();
}
[TaskName("UnitTests")]
[TaskDescription("Run unit tests (fast)")]
[IsDependentOn(typeof(BuildTask))]
public class UnitTestsTask : FrostingTask<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
}
[TaskName("Pack")]
[TaskDescription("Pack Nupkg packages")]
[IsDependentOn(typeof(BuildTask))]
public class PackTask : FrostingTask<BuildContext>
{
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<BuildContext>
{
}
[TaskName("DocsUpdate")]
[TaskDescription("Update generated documentation files")]
public class DocsUpdateTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context) => context.DocumentationRunner.Update();
}
[TaskName("DocsPrepare")]
[TaskDescription("Prepare auxiliary documentation files")]
public class DocsPrepareTask : FrostingTask<BuildContext>
{
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<BuildContext>
{
public override void Run(BuildContext context) => context.DocumentationRunner.Build();
}
[TaskName("FastTests")]
[TaskDescription("OBSOLETE: use 'UnitTests'")]
[IsDependentOn(typeof(UnitTestsTask))]
public class FastTestsTask : FrostingTask<BuildContext>
{
}
[TaskName("SlowFullFrameworkTests")]
[TaskDescription("OBSOLETE: use 'InTestsFull'")]
[IsDependentOn(typeof(InTestsFullTask))]
public class SlowFullFrameworkTestsTask : FrostingTask<BuildContext>
{
}
[TaskName("SlowTestsNetCore")]
[TaskDescription("OBSOLETE: use 'InTestsCore'")]
[IsDependentOn(typeof(InTestsCoreTask))]
public class SlowTestsNetCoreTask : FrostingTask<BuildContext>
{
}
[TaskName("DocFX_Changelog_Download")]
[TaskDescription("OBSOLETE: use 'DocsUpdate'")]
[IsDependentOn(typeof(DocsUpdateTask))]
public class DocFxChangelogDownloadTask : FrostingTask<BuildContext>
{
}
[TaskName("DocFX_Changelog_Generate")]
[TaskDescription("OBSOLETE: use 'DocsPrepare'")]
[IsDependentOn(typeof(DocsPrepareTask))]
public class DocfxChangelogGenerateTask : FrostingTask<BuildContext>
{
}
[TaskName("DocFX_Generate_Redirects")]
[TaskDescription("OBSOLETE: use 'DocsBuild'")]
[IsDependentOn(typeof(DocsBuildTask))]
public class DocfxGenerateRedirectsTask : FrostingTask<BuildContext>
{
}
[TaskName("DocFX_Build")]
[TaskDescription("OBSOLETE: use 'DocsBuild'")]
[IsDependentOn(typeof(DocsBuildTask))]
public class DocfxBuildTask : FrostingTask<BuildContext>
{
}

Просмотреть файл

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

Просмотреть файл

@ -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 =
$"<!doctype html>" +
$"<html lang=en-us>" +
$"<head>" +
$"<title>{target}</title>" +
$"<link rel=canonical href='{target}'>" +
$"<meta name=robots content=\"noindex\">" +
$"<meta charset=utf-8><meta http-equiv=refresh content=\"0; url={target}\">" +
$"</head>" +
$"</html>";
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();
}
}

Просмотреть файл

@ -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<int> 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());
}
}
}

Просмотреть файл

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

Просмотреть файл

@ -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<BuildContext>()
.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 =
$"<!doctype html>" +
$"<html lang=en-us>" +
$"<head>" +
$"<title>{target}</title>" +
$"<link rel=canonical href='{target}'>" +
$"<meta name=robots content=\"noindex\">" +
$"<meta charset=utf-8><meta http-equiv=refresh content=\"0; url={target}\">" +
$"</head>" +
$"</html>";
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<BuildContext>
{
public override void Run(BuildContext context)
{
context.CleanDirectory(context.ArtifactsDirectory);
}
}
[TaskName("Restore")]
[IsDependentOn(typeof(CleanTask))]
public class RestoreTask : FrostingTask<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
}
[TaskName("Pack")]
[IsDependentOn(typeof(BuildTask))]
public class PackTask : FrostingTask<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<BuildContext>
{
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<int> 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);
}
}
}

0
build/azure-pipelines.job.template.yml Executable file → Normal file
Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -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
}
$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;

Просмотреть файл

@ -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 -- "$@"

Просмотреть файл

@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.304",
"version": "7.0.305",
"rollForward": "disable"
}
}

Просмотреть файл

@ -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.

Просмотреть файл

@ -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):
You can build documentation locally with the help of the `DocsBuild` build task:
```
.\build.ps1 --target DocFX_Build
.\build.ps1 --target DocFX_Serve
build.cmd DocsBuild
```
Windows (Batch):
```
.\build.bat --target DocFX_Build
.\build.bat --target DocFX_Serve
```
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)

Просмотреть файл

@ -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.
</packageSources>
```
* 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 `<packageSources>` 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 `<packageSources>` section of your `NuGet.config`:
```xml
<packageSources>
<add key="bdn-nightly" value="https://ci.appveyor.com/nuget/benchmarkdotnet" />
<add key="bdn-nightly" value="https://www.myget.org/F/benchmarkdotnet/api/v3/index.json" />
</packageSources>
```
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
```