Skip to content

Adding public API to all manual projects #779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

[*]
charset = utf-8
end_of_line = crlf
end_of_line = lf
trim_trailing_whitespace = false
insert_final_newline = false
insert_final_newline = true
indent_style = space
indent_size = 4

Expand Down Expand Up @@ -35,6 +35,16 @@ dotnet_style_qualification_for_property = false:warning
dotnet_style_require_accessibility_modifiers = for_non_interface_members:hint
csharp_style_inlined_variable_declaration = true:hint
dotnet_sort_system_directives_first = true
dotnet_public_api_analyzer.require_api_files = true
dotnet_diagnostic.RS0016.severity = error
dotnet_diagnostic.RS0017.severity = error

# we should care more about this one day
dotnet_diagnostic.RS0041.severity = warn

# public api warnings silk.net doesn't care about
dotnet_diagnostic.RS0026.severity = none # Don't add multiple public overloads with optional parameters
dotnet_diagnostic.RS0027.severity = none # Overloads w/ optional parameters should have the most parameters of all overloads

# ReSharper properties
resharper_autodetect_indent_settings = true
Expand Down Expand Up @@ -77,4 +87,4 @@ tab_width = 4

# License header + warning generated if missing
file_header_template=Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
dotnet_diagnostic.IDE0073.severity = warning
dotnet_diagnostic.IDE0073.severity = warning
71 changes: 71 additions & 0 deletions .github/workflows/public-api.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Public API
on:
push:
branches:
- "*"
create:
tags:
- "*"
pull_request_target:
permissions:
issues: write
pull-requests: write
jobs:
Check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.PUSHABLE_GITHUB_TOKEN }}
ref: "refs/pull/${{ github.event.number }}/merge"
path: inbound_pr
- name: Add workloads for restore
run: |
dotnet workload install android
- name: Checkout submodules, configure git
run: |
git -c submodule.third_party/git-hooks.update=none submodule update --init --recursive
git config --local user.email "9011267+dotnet-bot@users.noreply.github.com"
git config --local user.name "The Silk.NET Automaton"
- name: Cache .tmp, ~/.nuget/packages
uses: actions/cache@v2
with:
path: |
.tmp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Setup .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- name: Ensure Public API Declared
run: ./build.sh EnsureApiDeclared
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Ship:
if: ${{ github.repository == 'dotnet/Silk.NET' && github.event_name == 'create' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.PUSHABLE_GITHUB_TOKEN }}
- name: Checkout submodules, configure git
run: |
git -c submodule.third_party/git-hooks.update=none submodule update --init --recursive
git config --local user.email "9011267+dotnet-bot@users.noreply.github.com"
git config --local user.name "The Silk.NET Automaton"
- name: Cache .tmp, ~/.nuget/packages
uses: actions/cache@v2
with:
path: |
.tmp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj') }}
- name: Setup .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- name: Ship Public API
run: ./build.sh ShipApi
env:
PUSHABLE_GITHUB_TOKEN: ${{ secrets.PUSHABLE_GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions build/comments/footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

---

<sub>This is an automated comment from Silk.NET's NUKE DevOps solution, created from [a GitHub Actions run](https://github.com/dotnet/Silk.NET/actions/runs/{actionsRun}). If this was created by mistake, just let a maintainer know and they'll happily ignore this. Comment type: `{typeId}`</sub>
3 changes: 3 additions & 0 deletions build/comments/public_api_declared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
![Public API Declared](https://img.shields.io/badge/public%20api-declared-green)

Public API issues have been fixed 🎉
14 changes: 14 additions & 0 deletions build/comments/public_api_not_declared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
![Public API Not Declared](https://img.shields.io/badge/public%20api-not%20declared-red)

Thanks for your contribution adding to the Silk.NET API surface! Note that for manual projects we use the [Public API Analyzer](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) wherein the entirety of the public API (for manual projects) are tracked in text files, in hope that this analyzer will warn us about breaking changes before we make them.

It looks like you either:
- haven't added all of your new APIs into this text file. Don't worry, it's a simple fix, you can do this automatically by running `nuke declareapi` and pushing the changed `PublicAPI.Unshipped.txt` files.
- have made a breaking change 😮 If this is intentional, make sure you talk to a maintainer!

This comment will automatically update once you've fixed this issue! For more information see:
- [Public API Analyzers Help](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
- [Public API Analyzers Diagnostic Descriptions](https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/Microsoft.CodeAnalysis.PublicApiAnalyzers.md)
- [The GitHub Actions Run That Flagged This](https://github.com/dotnet/Silk.NET/actions/runs/{actionsRun})

(Note to maintainers: this comment won't stop you from merging so if you're fine with this just merge anyway)
167 changes: 167 additions & 0 deletions build/nuke/Build.PublicApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using Nuke.Common;
using Nuke.Common.CI.GitHubActions;
using Nuke.Common.IO;
using Nuke.Common.Tooling;
using Octokit;
using Octokit.Internal;
using static Nuke.Common.Tools.Git.GitTasks;
using static Nuke.Common.Tooling.ProcessTasks;
using static Nuke.Common.Tools.DotNet.DotNetTasks;

partial class Build
{
const string FormatDeclCmd =
"format analyzers {0} --diagnostics=RS0016 --severity=error -v=diag --include-generated";

Target ShipApi => CommonTarget
(
x => x.Executes
(
() =>
{
foreach (var unshippedFile in RootDirectory.GlobFiles("**/PublicAPI.Unshipped.txt"))
{
var shippedFile = unshippedFile.Parent / "PublicAPI.Shipped.txt";
if (!File.Exists(shippedFile))
{
// common.props should've made this file, so if it's not here then i'm guessing this isn't a
// public api after all.
continue;
}

var shippedLines = File.ReadAllLines(shippedFile).ToList();
var unshippedLines = File.ReadAllLines(unshippedFile).ToList();
for (var i = 0; i < unshippedLines.Count; i++)
{
var unshippedLine = unshippedLines[i];
if (unshippedLine.StartsWith("//") || unshippedLine.StartsWith("#"))
{
continue;
}

if (!shippedLines.Contains(unshippedLine))
{
shippedLines.Add(unshippedLine);
}

unshippedLines.RemoveAt(i);
i--; // so we don't skip the next element
}

File.WriteAllLines(unshippedFile, unshippedLines);
File.WriteAllLines(shippedFile, shippedLines);
}

MakePr();
}
)
);

Target DeclareApi => CommonTarget(x => x.Executes(() => DotNet(string.Format(FormatDeclCmd, "Silk.NET.sln"))));

Target EnsureApiDeclared => CommonTarget
(
x => x.Executes
(
async () =>
{
try
{
var cmd = string.Format
(
FormatDeclCmd,
GitHubActions.Instance.GitHubRef?.Contains("/pull/") ?? false
? "inbound_pr/Silk.NET.sln"
: "Silk.NET.sln"
);

// I have no trust of incoming code, so let's take the github token away from them before they think
// about adding dodgy MSBuild targets that could swipe it
var githubToken = EnvironmentInfo.GetVariable<string>("GITHUB_TOKEN");
EnvironmentInfo.SetVariable("GITHUB_TOKEN", string.Empty);

// run the format command
DotNet($"{cmd} --verify-no-changes");

// add our github token back
EnvironmentInfo.SetVariable("GITHUB_TOKEN", githubToken);
await AddOrUpdatePrComment("public_api", "public_api_declared", true);
}
catch (ProcessException)
{
await AddOrUpdatePrComment("public_api", "public_api_not_declared");
throw;
}
}
)
);

void MakePr()
{
var pushableToken = EnvironmentInfo.GetVariable<string>("PUSHABLE_GITHUB_TOKEN");
var curBranch = GitCurrentBranch(RootDirectory);
if (GitHubActions.Instance?.GitHubRepository == "dotnet/Silk.NET" &&
!string.IsNullOrWhiteSpace(pushableToken))
{
if (curBranch == "HEAD" || string.IsNullOrWhiteSpace(curBranch))
{
curBranch = "main"; // not a good assumption to make, but fine for now for our purposes
// (tags are created from main usually)
}

// it's assumed that the pushable token was used to checkout the repo
Git("fetch --all", RootDirectory);
Git("pull");
Git("add **/PublicAPI.*.txt", RootDirectory);
var newBranch = $"ci/{curBranch}/ship_apis";
var curCommit = GitCurrentCommit(RootDirectory);
var commitCmd = StartProcess
(
$"git commit -m \"Move unshipped APIs to shipped\""
)
.AssertWaitForExit();
if (!commitCmd.Output.Any(x => x.Text.Contains("nothing to commit", StringComparison.OrdinalIgnoreCase)))
{
commitCmd.AssertZeroExitCode();
}

// ensure there are no other changes
Git("checkout HEAD .nuke/", RootDirectory);
Git("reset --hard", RootDirectory);
if (GitCurrentCommit(RootDirectory) != curCommit) // might get "nothing to commit", you never know...
{
Logger.Info("Checking for existing branch...");
var exists = StartProcess("git", $"checkout \"{newBranch}\"", RootDirectory)
.AssertWaitForExit()
.ExitCode == 0;
if (!exists)
{
Logger.Info("None found, creating a new one...");
Git($"checkout -b \"{newBranch}\"");
}

Git($"merge -X theirs \"{curBranch}\" --allow-unrelated-histories");
Git($"push --set-upstream origin \"{newBranch}\"");
if (!exists)
{
var github = new GitHubClient
(
new ProductHeaderValue("Silk.NET-CI"),
new InMemoryCredentialStore(new Credentials(pushableToken))
);

var pr = github.PullRequest.Create
("dotnet", "Silk.NET", new("Move unshipped APIs to shipped", newBranch, curBranch))
.GetAwaiter()
.GetResult();
}
}
}
}
}
Loading