Skip to content

Commit 47a6081

Browse files
authored
Merge pull request #352 from DuendeSoftware/dh/kill-workflow-gen
Replace workflow-gen with reusable workflow templates and Bullseye build scripts
2 parents 76550be + 2dd858f commit 47a6081

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1116
-1995
lines changed

.config/dotnet-tools.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": 1,
3+
"isRoot": true,
4+
"tools": {
5+
"NuGetKeyVaultSignTool": {
6+
"version": "3.2.3",
7+
"commands": [
8+
"NuGetKeyVaultSignTool"
9+
]
10+
}
11+
}
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Bullseye" Version="6.1.0" />
12+
<PackageReference Include="SimpleExec" Version="12.1.0" />
13+
</ItemGroup>
14+
15+
</Project>

.github/BuildHelpers/Targets.cs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (c) Duende Software. All rights reserved.
2+
// See LICENSE in the project root for license information.
3+
4+
using static Bullseye.Targets;
5+
using static SimpleExec.Command;
6+
7+
namespace BuildHelpers;
8+
9+
/// <summary>
10+
/// Registers build targets for product build scripts.
11+
/// </summary>
12+
public static class Targets
13+
{
14+
private static readonly Lazy<string> RepoRoot = new(FindRoot);
15+
16+
private const string Restore = "restore";
17+
private const string DebugBuild = "debug-build";
18+
private const string ReleaseBuild = "release-build";
19+
20+
public const string CheckFormatting = "check-formatting";
21+
public const string Clean = "clean";
22+
public const string CheckNoChanges = "check-no-changes";
23+
24+
private const string Default = "default";
25+
26+
/// <summary>
27+
/// Registers all shared targets parameterized by the product's solution filter path.
28+
/// </summary>
29+
/// <param name="slnfPath">
30+
/// Repo-relative path to the product's solution filter
31+
/// (e.g. <c>access-token-management/access-token-management.slnf</c>).
32+
/// </param>
33+
public static void SharedTargets(string slnfPath)
34+
{
35+
ArgumentNullException.ThrowIfNull(slnfPath);
36+
37+
var getChangedCSharpFilesTask = new Lazy<Task<IReadOnlyCollection<string>>>(() =>
38+
GetChangedCSharpFiles(RepoRoot.Value));
39+
40+
Target(Restore, () =>
41+
RunAsync("dotnet", $"restore {slnfPath}", RepoRoot.Value));
42+
43+
Target(DebugBuild, dependsOn: [Restore], () =>
44+
RunAsync("dotnet", $"build {slnfPath} --no-restore -c Debug", RepoRoot.Value));
45+
46+
Target(CheckFormatting, dependsOn: [DebugBuild], async () =>
47+
{
48+
var changedCSharpFiles = await getChangedCSharpFilesTask.Value;
49+
if (changedCSharpFiles.Count == 0)
50+
{
51+
await Console.Out.WriteLineAsync("No changed files found.");
52+
return;
53+
}
54+
55+
var include = string.Join(" ", changedCSharpFiles.Select(file => $"\"{file}\""));
56+
await RunAsync("dotnet", $"format {slnfPath} --verify-no-changes --no-restore --include {include}", RepoRoot.Value);
57+
});
58+
59+
Target(Clean, () =>
60+
RunAsync("dotnet", $"clean {slnfPath}", RepoRoot.Value));
61+
62+
Target(ReleaseBuild, dependsOn: [Restore], () =>
63+
RunAsync("dotnet", $"build {slnfPath} --no-restore -c Release", RepoRoot.Value));
64+
65+
Target(CheckNoChanges, dependsOn: [ReleaseBuild], async () =>
66+
{
67+
var (output, _) = await ReadAsync("git", "status --porcelain", workingDirectory: RepoRoot.Value);
68+
69+
if (!string.IsNullOrWhiteSpace(output))
70+
{
71+
await Console.Error.WriteLineAsync("Unexpected changes detected after build:");
72+
await Console.Error.WriteLineAsync(output);
73+
throw new InvalidOperationException(
74+
"Working tree has uncommitted changes. If these are generated files, commit them before pushing.");
75+
}
76+
});
77+
}
78+
79+
/// <summary>
80+
/// Registers a test target that runs <c>dotnet test</c> on a test project with standard options.
81+
/// </summary>
82+
/// <param name="targetName">The target name (e.g. <c>"test"</c>).</param>
83+
/// <param name="testProjectPath">
84+
/// Repo-relative path to the test project
85+
/// (e.g. <c>"access-token-management/test/AccessTokenManagement.Tests"</c>).
86+
/// </param>
87+
public static void TestTarget(string targetName, string testProjectPath) =>
88+
Target(targetName, dependsOn: [Restore], () =>
89+
RunAsync(
90+
"dotnet",
91+
$"test --project {testProjectPath} -c Release --no-restore /p:TreatWarningsAsErrors=false --coverage " +
92+
$"--report-trx --report-trx-filename {testProjectPath.Replace('/', '-')}-tests.trx",
93+
RepoRoot.Value));
94+
95+
public static void DefaultTarget(IEnumerable<string> dependsOn) =>
96+
Target(Default, dependsOn);
97+
98+
public static Task RunTargetsAndExitAsync(IEnumerable<string> args) =>
99+
Bullseye.Targets.RunTargetsAndExitAsync(args, messageOnly: ex => ex is SimpleExec.ExitCodeException);
100+
101+
private static string FindRoot()
102+
{
103+
var root = Directory.GetCurrentDirectory();
104+
105+
// Repositories have a .git folder, worktrees have a .git file, so check for both.
106+
while (!Directory.Exists(Path.Combine(root, ".git")) && !File.Exists(Path.Combine(root, ".git")))
107+
{
108+
root = Directory.GetParent(root) is { } parent
109+
? parent.FullName
110+
: throw new InvalidOperationException(
111+
"Could not find repository root (no .git directory or file found)");
112+
}
113+
114+
return root;
115+
}
116+
117+
private static async Task<IReadOnlyCollection<string>> GetChangedCSharpFiles(string repoRoot)
118+
{
119+
var mainRef = Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true"
120+
? "origin/main"
121+
: "main";
122+
123+
var (mergeBase, _) = await ReadAsync(
124+
"git", $"merge-base {mainRef} HEAD",
125+
repoRoot);
126+
127+
var (committedInBranchOutput, _) = await ReadAsync(
128+
"git", $"diff --name-only {mergeBase.Trim()}...HEAD",
129+
repoRoot);
130+
131+
var (stagedOutput, _) = await ReadAsync(
132+
"git", "diff --cached --name-only",
133+
repoRoot);
134+
135+
var (unstagedOutput, _) = await ReadAsync(
136+
"git", "diff --name-only",
137+
repoRoot);
138+
139+
var committedInBranch = committedInBranchOutput.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
140+
var staged = stagedOutput.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
141+
var unstaged = unstagedOutput.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
142+
143+
var paths = committedInBranch.Concat(unstaged).Concat(staged)
144+
.Where(name => string.Equals(Path.GetExtension(name), ".cs", StringComparison.OrdinalIgnoreCase) &&
145+
File.Exists(Path.Combine(repoRoot, name)));
146+
147+
return new HashSet<string>(paths);
148+
}
149+
}

0 commit comments

Comments
 (0)