Skip to content

Commit e5c0595

Browse files
PublicApiAnalyzer refinements (#3677)
1 parent 16c10d4 commit e5c0595

38 files changed

+1659
-258
lines changed

.build/Build.PublicApiAnalyzer.cs

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
using System.IO;
2+
using System.Linq;
3+
using Colorful;
4+
using Nuke.Common;
5+
using Nuke.Common.IO;
6+
using Nuke.Common.Tooling;
7+
using Nuke.Common.Tools.DotNet;
8+
using static Nuke.Common.Tools.DotNet.DotNetTasks;
9+
using static Nuke.Common.Tools.Git.GitTasks;
10+
using static Helpers;
11+
using System.Collections.Generic;
12+
using System.Threading.Tasks;
13+
using System.Text;
14+
using System;
15+
using System.Drawing;
16+
17+
partial class Build : NukeBuild
18+
{
19+
private readonly string _shippedApiFile = "PublicAPI.Shipped.txt";
20+
private readonly string _unshippedApiFile = "PublicAPI.Unshipped.txt";
21+
private readonly string _removedApiPrefix = "*REMOVED*";
22+
23+
[Parameter] readonly string From;
24+
[Parameter] readonly string To;
25+
[Parameter] readonly bool Breaking;
26+
27+
Target CheckPublicApi => _ => _
28+
.DependsOn(Restore)
29+
.Executes(() =>
30+
{
31+
if (!InvokedTargets.Contains(Restore))
32+
{
33+
DotNetBuildSonarSolution(AllSolutionFile);
34+
}
35+
36+
DotNetBuild(c => c
37+
.SetProjectFile(AllSolutionFile)
38+
.SetNoRestore(InvokedTargets.Contains(Restore))
39+
.SetConfiguration(Configuration)
40+
.SetProperty("RequireDocumentationOfPublicApiChanges", true));
41+
});
42+
43+
Target AddUnshipped => _ => _
44+
.DependsOn(Restore)
45+
.Executes(() =>
46+
{
47+
// first we ensure that the All.sln exists.
48+
if (!InvokedTargets.Contains(Restore))
49+
{
50+
DotNetBuildSonarSolution(AllSolutionFile);
51+
}
52+
53+
// new we restore our local dotnet tools including dotnet-format
54+
DotNetToolRestore(c => c.SetProcessWorkingDirectory(RootDirectory));
55+
56+
// last we run the actual dotnet format command.
57+
DotNet($@"format ""{AllSolutionFile}"" --fix-analyzers warn --diagnostics RS0016", workingDirectory: RootDirectory);
58+
});
59+
60+
Target DiffShippedApi => _ => _
61+
.Executes(() =>
62+
{
63+
var from = string.IsNullOrEmpty(From) ? GitRepository.Branch : From;
64+
var to = string.IsNullOrEmpty(To) ? "main" : To;
65+
66+
if (from == to)
67+
{
68+
Colorful.Console.WriteLine("Nothing to diff here.", Color.Yellow);
69+
return;
70+
}
71+
72+
AbsolutePath shippedPath = SourceDirectory / "**" / _shippedApiFile;
73+
74+
Git($@" --no-pager diff --minimal -U0 --word-diff ""{from}"" ""{to}"" -- ""{shippedPath}""", RootDirectory);
75+
});
76+
77+
Target DisplayUnshippedApi => _ => _
78+
.Executes(async () =>
79+
{
80+
var unshippedFiles = Directory.GetFiles(SourceDirectory, _unshippedApiFile, SearchOption.AllDirectories);
81+
82+
if (Breaking)
83+
{
84+
Colorful.Console.WriteLine("Unshipped breaking changes:", Color.Red);
85+
}
86+
else
87+
{
88+
Colorful.Console.WriteLine("Unshipped changes:");
89+
}
90+
91+
Colorful.Console.WriteLine();
92+
93+
foreach (var unshippedFile in unshippedFiles)
94+
{
95+
IEnumerable<string> unshippedApis = await GetNonEmptyLinesAsync(unshippedFile);
96+
97+
if (Breaking)
98+
{
99+
unshippedApis = unshippedApis.Where(u => u.StartsWith(_removedApiPrefix)).ToList();
100+
}
101+
102+
if (!unshippedApis.Any())
103+
{
104+
continue;
105+
}
106+
107+
foreach (var unshippedApi in unshippedApis)
108+
{
109+
if (unshippedApi.StartsWith(_removedApiPrefix))
110+
{
111+
var value = unshippedApi[_removedApiPrefix.Length..];
112+
Colorful.Console.WriteLine(value);
113+
}
114+
else
115+
{
116+
Colorful.Console.WriteLine(unshippedApi);
117+
}
118+
}
119+
}
120+
});
121+
122+
Target MarkApiShipped => _ => _
123+
.Executes(async () =>
124+
{
125+
var shippedFiles = Directory.GetFiles(SourceDirectory, _shippedApiFile, SearchOption.AllDirectories);
126+
127+
foreach (var shippedFile in shippedFiles)
128+
{
129+
var projectDir = Path.GetDirectoryName(shippedFile);
130+
var unshippedFile = Path.Join(projectDir, _unshippedApiFile);
131+
132+
if (!File.Exists(unshippedFile))
133+
{
134+
continue;
135+
}
136+
137+
List<string> unshippedApis = await GetNonEmptyLinesAsync(unshippedFile);
138+
139+
if (!unshippedApis.Any())
140+
{
141+
continue;
142+
}
143+
144+
List<string> shippedApis = await GetNonEmptyLinesAsync(shippedFile);
145+
146+
List<string> removedApis = new();
147+
148+
foreach (var unshippedApi in unshippedApis)
149+
{
150+
if (unshippedApi.StartsWith(_removedApiPrefix))
151+
{
152+
var value = unshippedApi[_removedApiPrefix.Length..];
153+
removedApis.Add(value);
154+
}
155+
else
156+
{
157+
shippedApis.Add(unshippedApi);
158+
}
159+
}
160+
161+
IOrderedEnumerable<string> newShippedApis = shippedApis
162+
.Where(s => !removedApis.Contains(s))
163+
.Distinct()
164+
.OrderBy(s => s);
165+
166+
await File.WriteAllLinesAsync(shippedFile, newShippedApis, Encoding.ASCII);
167+
await File.WriteAllTextAsync(unshippedFile, "", Encoding.ASCII);
168+
}
169+
});
170+
171+
private static async Task<List<string>> GetNonEmptyLinesAsync(string filepath)
172+
{
173+
var lines = await File.ReadAllLinesAsync(filepath);
174+
175+
return lines.Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
176+
}
177+
}

.build/Build.cs

-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,5 @@ partial class Build : NukeBuild
7070
.SetFileVersion(GitVersion.AssemblySemFileVer)
7171
.SetInformationalVersion(GitVersion.InformationalVersion)
7272
.SetVersion(GitVersion.SemVer));
73-
// .SetProperty("RequireDocumentationOfPublicApiChanges", true));
7473
});
7574
}

.build/Build.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="Nuke.Common" Version="5.0.2" />
13+
<PackageReference Include="Nuke.Common" Version="5.1.2" />
1414
<PackageDownload Include="GitVersion.Tool" Version="[5.6.3]" />
1515
<PackageDownload Include="NuGet.CommandLine" Version="[5.5.1]" />
1616
<PackageDownload Include="dotnet-sonarscanner" Version="[5.0.4]" />

.config/dotnet-tools.json

+12-6
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88
"dotnet-sonarscanner"
99
]
1010
},
11-
"nuke.globaltool": {
12-
"version": "0.24.11",
13-
"commands": [
14-
"nuke"
15-
]
16-
},
1711
"boost.tool": {
1812
"version": "0.2.2",
1913
"commands": [
2014
"boo"
2115
]
16+
},
17+
"dotnet-format": {
18+
"version": "5.1.225507",
19+
"commands": [
20+
"dotnet-format"
21+
]
22+
},
23+
"nuke.globaltool": {
24+
"version": "5.1.2",
25+
"commands": [
26+
"nuke"
27+
]
2228
}
2329
}
2430
}

.devops/azure-pipelines.release-hotchocolate.yml

+30
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ stages:
1414
strategy:
1515
parallel: 5
1616
steps:
17+
- task: UseDotNet@2
18+
displayName: "Install .NET Core 2.1"
19+
inputs:
20+
packageType: 'sdk'
21+
version: '2.1.816'
22+
- task: UseDotNet@2
23+
displayName: "Install .NET Core 3.1"
24+
inputs:
25+
packageType: 'sdk'
26+
version: '3.1.409'
1727
- task: UseDotNet@2
1828
displayName: "Install .NET Core"
1929
inputs:
@@ -32,6 +42,16 @@ stages:
3242
displayName: "Pack"
3343
dependsOn: []
3444
steps:
45+
- task: UseDotNet@2
46+
displayName: "Install .NET Core 2.1"
47+
inputs:
48+
packageType: 'sdk'
49+
version: '2.1.816'
50+
- task: UseDotNet@2
51+
displayName: "Install .NET Core 3.1"
52+
inputs:
53+
packageType: 'sdk'
54+
version: '3.1.409'
3555
- task: UseDotNet@2
3656
displayName: "Install .NET Core"
3757
inputs:
@@ -50,6 +70,16 @@ stages:
5070
displayName: "Publish"
5171
dependsOn: [Test, Pack]
5272
steps:
73+
- task: UseDotNet@2
74+
displayName: "Install .NET Core 2.1"
75+
inputs:
76+
packageType: 'sdk'
77+
version: '2.1.816'
78+
- task: UseDotNet@2
79+
displayName: "Install .NET Core 3.1"
80+
inputs:
81+
packageType: 'sdk'
82+
version: '3.1.409'
5383
- task: UseDotNet@2
5484
displayName: "Install .NET Core"
5585
inputs:

.devops/azure-pipelines.sonar-hotchocolate.yml

+30
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ stages:
1919
strategy:
2020
parallel: 5
2121
steps:
22+
- task: UseDotNet@2
23+
displayName: "Install .NET Core 2.1"
24+
inputs:
25+
packageType: 'sdk'
26+
version: '2.1.816'
27+
- task: UseDotNet@2
28+
displayName: "Install .NET Core 3.1"
29+
inputs:
30+
packageType: 'sdk'
31+
version: '3.1.409'
2232
- task: UseDotNet@2
2333
displayName: "Install .NET Core"
2434
inputs:
@@ -37,6 +47,16 @@ stages:
3747
displayName: "ReportCoverage"
3848
dependsOn: [Cover]
3949
steps:
50+
- task: UseDotNet@2
51+
displayName: "Install .NET Core 2.1"
52+
inputs:
53+
packageType: 'sdk'
54+
version: '2.1.816'
55+
- task: UseDotNet@2
56+
displayName: "Install .NET Core 3.1"
57+
inputs:
58+
packageType: 'sdk'
59+
version: '3.1.409'
4060
- task: UseDotNet@2
4161
displayName: "Install .NET Core"
4262
inputs:
@@ -55,6 +75,16 @@ stages:
5575
displayName: "Sonar"
5676
dependsOn: [Cover]
5777
steps:
78+
- task: UseDotNet@2
79+
displayName: "Install .NET Core 2.1"
80+
inputs:
81+
packageType: 'sdk'
82+
version: '2.1.816'
83+
- task: UseDotNet@2
84+
displayName: "Install .NET Core 3.1"
85+
inputs:
86+
packageType: 'sdk'
87+
version: '3.1.409'
5888
- task: UseDotNet@2
5989
displayName: "Install .NET Core"
6090
inputs:

.devops/azure-pipelines.sonar-pr-hotchocolate.yml

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ stages:
1717
displayName: "Sonar Pull-Request"
1818
dependsOn: []
1919
steps:
20+
- task: UseDotNet@2
21+
displayName: "Install .NET Core 2.1"
22+
inputs:
23+
packageType: 'sdk'
24+
version: '2.1.816'
25+
- task: UseDotNet@2
26+
displayName: "Install .NET Core 3.1"
27+
inputs:
28+
packageType: 'sdk'
29+
version: '3.1.409'
2030
- task: UseDotNet@2
2131
displayName: "Install .NET Core"
2232
inputs:

.devops/azure-pipelines.test-pr-hotchocolate.yml

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ stages:
2020
strategy:
2121
parallel: 5
2222
steps:
23+
- task: UseDotNet@2
24+
displayName: "Install .NET Core 2.1"
25+
inputs:
26+
packageType: 'sdk'
27+
version: '2.1.816'
28+
- task: UseDotNet@2
29+
displayName: "Install .NET Core 3.1"
30+
inputs:
31+
packageType: 'sdk'
32+
version: '3.1.409'
2333
- task: UseDotNet@2
2434
displayName: "Install .NET Core"
2535
inputs:
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: Check Public API
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
check-public-api:
10+
name: "Check Public API"
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v2
14+
- uses: actions/setup-dotnet@v1
15+
- run: ./build.sh CheckPublicApi
16+
name: "Check for undocumented public API changes"

.nuke

Whitespace-only changes.

0 commit comments

Comments
 (0)