Skip to content

Commit

Permalink
Rework documentation generation
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyAkinshin committed Jul 7, 2023
1 parent c0cbd25 commit 41f86e5
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 234 deletions.
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -29,7 +29,7 @@ It's no harder than writing unit tests!
Under the hood, it performs a lot of [magic](#automation) that guarantees [reliable and precise](#reliability) results thanks to the [perfolizer](https://github.com/AndreyAkinshin/perfolizer) statistical engine.
BenchmarkDotNet protects you from popular benchmarking mistakes and warns you if something is wrong with your benchmark design or obtained measurements.
The results are presented in a [user-friendly](#friendliness) form that highlights all the important facts about your experiment.
The library is adopted by [16500+ GitHub projects](#who-uses-benchmarkdotnet) including .NET Runtime.
The library is adopted by [16600+ GitHub projects](#who-uses-benchmarkdotnet) including .NET Runtime.

It's [easy](#simplicity) to start writing benchmarks, check out the following example
(copy-pastable version is [here](https://benchmarkdotnet.org/articles/guides/getting-started.html)):
Expand Down Expand Up @@ -231,7 +231,7 @@ If you don't customize the summary view,
## Who uses BenchmarkDotNet?

Everyone!
BenchmarkDotNet is already adopted by more than [16500+](https://github.com/dotnet/BenchmarkDotNet/network/dependents?package_id=UGFja2FnZS0xNTY3MzExMzE%3D) projects including
BenchmarkDotNet is already adopted by more than [16600+](https://github.com/dotnet/BenchmarkDotNet/network/dependents?package_id=UGFja2FnZS0xNTY3MzExMzE%3D) projects including
[dotnet/performance](https://github.com/dotnet/performance) (reference benchmarks for all .NET Runtimes),
[dotnet/runtime](https://github.com/dotnet/runtime/issues?utf8=%E2%9C%93&q=BenchmarkDotNet) (.NET runtime and libraries),
[Roslyn](https://github.com/dotnet/roslyn/search?q=BenchmarkDotNet&type=Issues&utf8=✓) (C# and Visual Basic compiler),
Expand Down
138 changes: 44 additions & 94 deletions build/BenchmarkDotNet.Build/BuildContext.cs
Expand Up @@ -29,14 +29,6 @@ public class BuildContext : FrostingContext
public DirectoryPath RootDirectory { get; }
public DirectoryPath BuildDirectory { 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; }
Expand All @@ -56,9 +48,9 @@ public class BuildContext : FrostingContext

public bool IsLocalBuild => this.BuildSystem().IsLocalBuild;
public bool IsCiBuild => !this.BuildSystem().IsLocalBuild;

public VersionHistory VersionHistory { get; }

public UnitTestRunner UnitTestRunner { get; }
public DocumentationRunner DocumentationRunner { get; }
public BuildRunner BuildRunner { get; }
Expand All @@ -69,17 +61,10 @@ public BuildContext(ICakeContext context)
RootDirectory = new DirectoryPath(new DirectoryInfo(Directory.GetCurrentDirectory()).Parent?.Parent?.FullName);
BuildDirectory = RootDirectory.Combine("build");
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")
Expand Down Expand Up @@ -127,7 +112,7 @@ public BuildContext(ICakeContext context)
}
}
}

// 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"
Expand All @@ -143,92 +128,57 @@ public BuildContext(ICakeContext context)
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 = "")
public void GenerateFile(FilePath filePath, StringBuilder content)
{
EnsureChangelogDetailsExist();
this.Information("DocfxChangelogDownload: " + version);
var path = ChangeLogGenDirectory.Combine("details");
ChangeLogBuilder.Run(path, version, versionPrevious, lastCommit).Wait();
GenerateFile(filePath, content.ToString());
}

public void DocfxChangelogGenerate(string version)
public void GenerateFile(FilePath filePath, string content)
{
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))
var relativePath = RootDirectory.GetRelativePath(filePath);
if (this.FileExists(filePath))
{
content.AppendLine(this.FileReadText(header));
content.AppendLine("");
content.AppendLine("");
}
var oldContent = this.FileReadText(filePath);
if (content == oldContent)
return;

if (this.FileExists(details))
this.FileWriteText(filePath, content);
this.Information("[Updated] " + relativePath);
}
else
{
content.AppendLine(this.FileReadText(details));
content.AppendLine("");
content.AppendLine("");
this.FileWriteText(filePath, content);
this.Information("[Generated] " + relativePath);
}
}

if (this.FileExists(footer))
public void Clone(DirectoryPath path, string repoUrl, string branchName)
{
this.Information($"[GitClone]");
this.Information($" Repo: {repoUrl}");
this.Information($" Branch: {branchName}");
this.Information($" Path: {path}");
var settings = new GitCloneSettings { Checkout = true, BranchName = branchName };
try
{
content.AppendLine("## Additional details");
content.AppendLine("");
content.AppendLine(this.FileReadText(footer));
this.GitClone(repoUrl, path, settings);
this.Information(" Success");
}
catch (Exception e)
{
this.Error($" Failed to clone via API (Exception: {e.GetType().Name})'");
try
{
var gitArgs = $"clone -b {branchName} {repoUrl} {path}";
this.Information($" Trying to clone manually using 'git {gitArgs}'");
this.StartProcess("git", gitArgs);
this.Information(" Success");
}
catch (Exception e2)
{
throw new Exception($"Failed to clone {repoUrl} to {path} (branch: '{branchName})'", e2);
}
}

this.FileWriteText(release, content.ToString());
}


}
44 changes: 22 additions & 22 deletions build/BenchmarkDotNet.Build/ChangeLogBuilder.cs
Expand Up @@ -16,21 +16,21 @@ public static class ChangeLogBuilder
{
private class Config
{
public string CurrentMilestone { get; }
public string PreviousMilestone { get; }
public string CurrentVersion { get; }
public string PreviousVersion { get; }
public string LastCommit { get; }

public void Deconstruct(out string currentMilestone, out string previousMilestone, out string lastCommit)
{
currentMilestone = CurrentMilestone;
previousMilestone = PreviousMilestone;
currentMilestone = CurrentVersion;
previousMilestone = PreviousVersion;
lastCommit = LastCommit;
}

public Config(string currentMilestone, string previousMilestone, string lastCommit)
public Config(string currentVersion, string previousVersion, string lastCommit)
{
CurrentMilestone = currentMilestone;
PreviousMilestone = previousMilestone;
CurrentVersion = currentVersion;
PreviousVersion = previousVersion;
LastCommit = lastCommit;
}
}
Expand All @@ -56,15 +56,15 @@ private MarkdownBuilder(Config config)

private async Task<string> Build()
{
var (milestone, previousMilestone, lastCommit) = config;
var (currentVersion, previousVersion, lastCommit) = config;
if (string.IsNullOrEmpty(lastCommit))
lastCommit = milestone;
lastCommit = currentVersion;

var client = new GitHubClient(new ProductHeaderValue(Repo.ProductHeader));
var tokenAuth = new Credentials(Repo.Token);
var client = new GitHubClient(new ProductHeaderValue(GitHubCredentials.ProductHeader));
var tokenAuth = new Credentials(GitHubCredentials.Token);
client.Credentials = tokenAuth;

if (milestone == "_")
if (currentVersion == "_")
{
var allContributors = await client.Repository.GetAllContributors(Repo.Owner, Repo.Name);
builder.AppendLine("# All contributors");
Expand All @@ -87,11 +87,12 @@ private async Task<string> Build()
{
State = ItemStateFilter.All
};
allMilestones = await client.Issue.Milestone.GetAllForRepository(Repo.Owner, Repo.Name, 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 == $"v{currentVersion}");
if (targetMilestone != null)
{
var issueRequest = new RepositoryIssueRequest
Expand All @@ -112,10 +113,10 @@ private async Task<string> Build()
.OrderBy(issue => issue.Number)
.ToList();

var compare = await client.Repository.Commit.Compare(Repo.Owner, Repo.Name, previousMilestone, lastCommit);
var compare =
await client.Repository.Commit.Compare(Repo.Owner, Repo.Name, $"v{previousVersion}", lastCommit);
var commits = compare.Commits;


foreach (var contributor in commits.Select(commit => commit.Author))
if (contributor != null && !AuthorNames.ContainsKey(contributor.Login))
{
Expand All @@ -137,11 +138,11 @@ string PresentContributor(GitHubCommit commit)
.Distinct()
.ToImmutableList();

var milestoneHtmlUlr = $"https://github.com/{Repo.Owner}/{Repo.Name}/issues?q=milestone:{milestone}";
var milestoneHtmlUlr = $"https://github.com/{Repo.Owner}/{Repo.Name}/issues?q=milestone:{currentVersion}";

builder.AppendLine("## Milestone details");
builder.AppendLine();
builder.AppendLine($"In the [{milestone}]({milestoneHtmlUlr}) scope, ");
builder.AppendLine($"In the [{currentVersion}]({milestoneHtmlUlr}) scope, ");
builder.Append(issues.Count + " issues were resolved and ");
builder.AppendLine(pullRequests.Count + " pull requests were merged.");
builder.AppendLine($"This release includes {commits.Count} commits by {contributors.Count} contributors.");
Expand Down Expand Up @@ -175,14 +176,13 @@ string PresentContributor(GitHubCommit commit)
}
}

public static async Task Run(DirectoryPath path, string currentMilestone, string previousMilestone,
string lastCommit)
public static async Task Run(DirectoryPath path, string currentVersion, string previousVersion, string lastCommit)
{
try
{
var config = new Config(currentMilestone, previousMilestone, lastCommit);
var config = new Config(currentVersion, previousVersion, lastCommit);
var releaseNotes = await MarkdownBuilder.Build(config);
await File.WriteAllTextAsync(path.Combine(config.CurrentMilestone + ".md").FullPath, releaseNotes);
await File.WriteAllTextAsync(path.Combine(config.CurrentVersion + ".md").FullPath, releaseNotes);
}
catch (Exception e)
{
Expand Down
11 changes: 11 additions & 0 deletions build/BenchmarkDotNet.Build/Meta/GitHubCredentials.cs
@@ -0,0 +1,11 @@
using System;

namespace BenchmarkDotNet.Build.Meta;

public static class GitHubCredentials
{
public const string TokenVariableName = "GITHUB_TOKEN";

public const string ProductHeader = "BenchmarkDotNet";
public static string? Token => Environment.GetEnvironmentVariable(TokenVariableName);
}
8 changes: 0 additions & 8 deletions build/BenchmarkDotNet.Build/Meta/Repo.cs
@@ -1,5 +1,3 @@
using System;

namespace BenchmarkDotNet.Build.Meta;

public static class Repo
Expand All @@ -9,10 +7,4 @@ public static class Repo
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 comments on commit 41f86e5

Please sign in to comment.