Huge build project refactoring

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

99
.github/workflows/build.yaml поставляемый
Просмотреть файл

@ -4,16 +4,58 @@ on:
pull_request: pull_request:
push: push:
permissions: write-all
jobs: jobs:
build-windows:
build-windows-core:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Disable Windows Defender
run: Set-MpPreference -DisableRealtimeMonitoring $true
shell: powershell
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Run - name: Run task 'Build'
shell: cmd shell: cmd
run: | run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
./build.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: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -25,11 +67,52 @@ jobs:
platform: x64 platform: x64
- name: Set up zlib-static - name: Set up zlib-static
run: sudo apt-get install -y libkrb5-dev run: sudo apt-get install -y libkrb5-dev
- name: Run - name: Run task 'Build'
run: ./build.sh 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: build-macos:
runs-on: macOS-latest runs-on: macos-13
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Run - name: Run task 'Build'
run: ./build.sh 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 ref: master
- name: Download changelog - name: Download changelog
run: ./build.sh --target DocFX_Changelog_Download --VersionCount 1 run: ./build.cmd DocsUpdate /p:Depth=1
env: env:
GITHUB_PRODUCT: ChangelogBuilder GITHUB_PRODUCT: ChangelogBuilder
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

6
.github/workflows/docs-stable.yaml поставляемый
Просмотреть файл

@ -19,16 +19,16 @@ jobs:
ref: docs-stable ref: docs-stable
- name: Build BenchmarkDotNet - name: Build BenchmarkDotNet
run: ./build.bat --target Build run: ./build.cmd Build
- name: Download changelog - name: Download changelog
run: ./build.bat --target DocFX_Changelog_Download --VersionCount 1 run: ./build.cmd DocsUpdate /p:Depth=1
env: env:
GITHUB_PRODUCT: ChangelogBuilder GITHUB_PRODUCT: ChangelogBuilder
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build documentation - name: Build documentation
run: ./build.bat --target DocFX_Build run: ./build.cmd DocsBuild
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v1 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: build_script:
- ps: .\build.ps1 - ps: .\build.cmd CI
test: off test: off
deploy: off deploy: off

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

@ -13,7 +13,7 @@ jobs:
parameters: parameters:
name: Ubuntu name: Ubuntu
vmImage: 'ubuntu-20.04' vmImage: 'ubuntu-20.04'
scriptFileName: ./build.sh scriptFileName: ./build.cmd CI
initialization: initialization:
- bash: | - bash: |
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -

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

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

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

@ -13,4 +13,4 @@ jobs:
parameters: parameters:
name: macOS name: macOS
vmImage: 'macOS-latest' 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> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Cake.Frosting" Version="3.0.0" /> <PackageReference Include="Cake.Frosting" Version="3.0.0" />
<PackageReference Include="Cake.FileHelpers" Version="6.1.3" /> <PackageReference Include="Cake.FileHelpers" Version="6.1.3" />
<PackageReference Include="Cake.Git" Version="3.0.0" /> <PackageReference Include="Cake.Git" Version="3.0.0" />
<PackageReference Include="Microsoft.DocAsCode.App" Version="2.67.5" /> <PackageReference Include="Microsoft.DocAsCode.App" Version="2.67.5" />
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
<PackageReference Include="Octokit" Version="7.0.0" /> <PackageReference Include="Octokit" Version="7.0.0" />
</ItemGroup> </ItemGroup>
</Project> </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.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Build.Helpers;
using BenchmarkDotNet.Build.Meta;
using Cake.Core.IO; using Cake.Core.IO;
using JetBrains.Annotations;
using Octokit; 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 private class Config
? $" ({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) public string CurrentMilestone { get; }
.FirstOrDefault() ?? ""; public string PreviousMilestone { get; }
return message.Length > 80 ? message.Substring(0, 77) + "..." : message; public string LastCommit { get; }
}
public static string ToLink(this GitHubCommit commit) => $"[{commit.Sha.Substring(0, 6)}]({commit.HtmlUrl})"; public void Deconstruct(out string currentMilestone, out string previousMilestone, out string lastCommit)
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)
{
repoOwner = RepoOwner;
repoName = RepoName;
currentMilestone = CurrentMilestone; currentMilestone = CurrentMilestone;
previousMilestone = PreviousMilestone; previousMilestone = PreviousMilestone;
lastCommit = LastCommit; 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) public Config(string currentMilestone, string previousMilestone, string lastCommit)
{ {
CurrentMilestone = currentMilestone; CurrentMilestone = currentMilestone;
@ -85,18 +35,9 @@ public class ChangeLogBuilder
} }
} }
public class AuthorEqualityComparer : IEqualityComparer<Author> private class MarkdownBuilder
{ {
public static readonly IEqualityComparer<Author> Default = new AuthorEqualityComparer(); private static IReadOnlyList<Milestone>? allMilestones;
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 readonly Dictionary<string, string> AuthorNames = new(); private static readonly Dictionary<string, string> AuthorNames = new();
private readonly Config config; private readonly Config config;
@ -115,17 +56,17 @@ public class ChangeLogBuilder
private async Task<string> Build() private async Task<string> Build()
{ {
var (repoOwner, repoName, milestone, previousMilestone, lastCommit) = config; var (milestone, previousMilestone, lastCommit) = config;
if (string.IsNullOrEmpty(lastCommit)) if (string.IsNullOrEmpty(lastCommit))
lastCommit = milestone; lastCommit = milestone;
var client = new GitHubClient(new ProductHeaderValue(config.ProductHeader)); var client = new GitHubClient(new ProductHeaderValue(Repo.ProductHeader));
var tokenAuth = new Credentials(config.Token); var tokenAuth = new Credentials(Repo.Token);
client.Credentials = tokenAuth; client.Credentials = tokenAuth;
if (milestone == "_") 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("# All contributors");
builder.AppendLine(); builder.AppendLine();
foreach (var contributor in allContributors) foreach (var contributor in allContributors)
@ -140,17 +81,17 @@ public class ChangeLogBuilder
return builder.ToString(); return builder.ToString();
} }
if (AllMilestones == null) if (allMilestones == null)
{ {
var milestoneRequest = new MilestoneRequest var milestoneRequest = new MilestoneRequest
{ {
State = ItemStateFilter.All 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>(); 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) if (targetMilestone != null)
{ {
var issueRequest = new RepositoryIssueRequest var issueRequest = new RepositoryIssueRequest
@ -159,7 +100,7 @@ public class ChangeLogBuilder
Milestone = targetMilestone.Number.ToString() 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 var issues = allIssues
@ -171,7 +112,7 @@ public class ChangeLogBuilder
.OrderBy(issue => issue.Number) .OrderBy(issue => issue.Number)
.ToList(); .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; var commits = compare.Commits;
@ -196,7 +137,7 @@ public class ChangeLogBuilder
.Distinct() .Distinct()
.ToImmutableList(); .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("## Milestone details");
builder.AppendLine(); builder.AppendLine();
@ -218,7 +159,7 @@ public class ChangeLogBuilder
} }
private void AppendList<T>(string title, IReadOnlyList<T> items, Func<T, string> format, 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($"## {title} ({items.Count})");
builder.AppendLine(); builder.AppendLine();
@ -234,7 +175,8 @@ public class ChangeLogBuilder
} }
} }
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 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 #!/usr/bin/env pwsh
$DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1'; $DotNetInstallerUri = 'https://dot.net/v1/dotnet-install.ps1';
$DotNetUnixInstallerUri = 'https://dot.net/v1/dotnet-install.sh' $BuildPath = Split-Path $MyInvocation.MyCommand.Path -Parent
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent $PSScriptRoot = Split-Path $PSScriptRoot -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
}
if ($PSVersionTable.PSEdition -ne 'Core') { if ($PSVersionTable.PSEdition -ne 'Core') {
# Attempt to set highest encryption available for SecurityProtocol. # 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_CLI_TELEMETRY_OPTOUT=1
$env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2 $env:DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2
Function Remove-PathVariable([string]$VariableToRemove) Function Remove-PathVariable([string]$VariableToRemove)
{ {
$SplitChar = ';' $SplitChar = ';'
@ -60,20 +51,10 @@ Function Remove-PathVariable([string]$VariableToRemove)
} }
$InstallPath = Join-Path $PSScriptRoot ".dotnet" $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)) { if (!(Test-Path $InstallPath)) {
New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null; 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' $ScriptPath = Join-Path $InstallPath 'dotnet-install.ps1'
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath); (New-Object System.Net.WebClient).DownloadFile($DotNetInstallerUri, $ScriptPath);
& $ScriptPath -JSonFile $GlobalJsonPath -InstallDir $InstallPath; & $ScriptPath -JSonFile $GlobalJsonPath -InstallDir $InstallPath;
@ -81,11 +62,12 @@ else {
Remove-PathVariable "$InstallPath" Remove-PathVariable "$InstallPath"
$env:PATH = "$InstallPath;$env:PATH" $env:PATH = "$InstallPath;$env:PATH"
} }
$env:DOTNET_ROOT=$InstallPath $env:DOTNET_ROOT=$InstallPath
########################################################################### ###########################################################################
# RUN BUILD SCRIPT # RUN BUILD SCRIPT
########################################################################### ###########################################################################
& dotnet run --project build/Build.csproj -- $args & dotnet run --configuration Release --project build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj -- $args
exit $LASTEXITCODE; exit $LASTEXITCODE;

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

@ -1,13 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Define varibles
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
TOOLS_DIR=$SCRIPT_DIR/tools
# Define variables
# Make sure the tools folder exist. SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )
if [ ! -d "$TOOLS_DIR" ]; then
mkdir "$TOOLS_DIR"
fi
########################################################################### ###########################################################################
# INSTALL .NET CORE CLI # INSTALL .NET CORE CLI
@ -20,9 +14,10 @@ export DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX=2
if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then if [ ! -d "$SCRIPT_DIR/.dotnet" ]; then
mkdir "$SCRIPT_DIR/.dotnet" 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 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 PATH="$SCRIPT_DIR/.dotnet":$PATH
export DOTNET_ROOT="$SCRIPT_DIR/.dotnet" export DOTNET_ROOT="$SCRIPT_DIR/.dotnet"
@ -30,4 +25,4 @@ export DOTNET_ROOT="$SCRIPT_DIR/.dotnet"
# RUN BUILD SCRIPT # 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": { "sdk": {
"version": "7.0.304", "version": "7.0.305",
"rollForward": "disable" "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. - [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. 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. [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 [fsharp package](https://fsharp.org/use/mac/)
- Install the latest version of [OpenSSL](https://www.openssl.org/source/). - 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`. 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.
Build has a number of options that you use. Some of the more important options are When executed without arguments, it prints help information with list of all available build tasks.
- **`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.

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

@ -49,31 +49,12 @@ It will be transformed to:
## Building documentation locally ## Building documentation locally
You can build documentation locally with the help of the `DocFX_Build` Cake target. You can build documentation locally with the help of the `DocsBuild` build task:
Use the `DocFX_Serve` Cake target to build and run the documentation.
Windows (PowerShell):
``` ```
.\build.ps1 --target DocFX_Build build.cmd DocsBuild
.\build.ps1 --target DocFX_Serve
``` ```
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 ## See also
* [DocFX User Manual](https://dotnet.github.io/docfx/tutorial/docfx.exe_user_manual.html) * [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`): 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.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. * `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. 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 ## Versioning system and feeds
We have 3 kinds of versions: *stable*, *nightly*, and *develop*. 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`. You can get the current version from the source code via `BenchmarkDotNetInfo.FullVersion` and the full title via `BenchmarkDotNetInfo.FullTitle`.
### Stable ### Stable
These versions are available from the official NuGet feed. These versions are available from the official NuGet feed.
```xml ```xml
@ -28,26 +32,22 @@ These versions are available from the official NuGet feed.
</packageSources> </packageSources>
``` ```
* Example of the main NuGet package: `BenchmarkDotNet.0.10.3.nupkg`.
* Example of `BenchmarkDotNetInfo.FullTitle`: `BenchmarkDotNet v0.10.3`.
### Nightly ### 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 ```xml
<packageSources> <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> </packageSources>
``` ```
Now you can install the packages from the `bdn-nightly` feed. 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 ### 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`. You also can build BenchmarkDotNet from source code:
* Example of `BenchmarkDotNetInfo.FullTitle`: `BenchmarkDotNet v0.10.3.20170304-develop`.
```sh
build.cmd pack
```