Skip to content
Draft
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
11 changes: 11 additions & 0 deletions docs/website/05-cicd/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,14 @@ CheckoutAction = "releases/v1" // error: ambiguous, names neither form
:::note
`actions/cache@v5` and later run on the node24 runtime and require a self-hosted runner of at least `2.327.1`. Set `CacheAction = "v4"` if your runners are older.
:::

### Pinning the .NET SDK

By default the setup step reads the SDK from `global.json` at the repo root (`global-json-file: global.json`). Set `SetupDotNetVersions` to emit `dotnet-version` instead. That covers a `global.json` kept in a subfolder, and pinning an exact SDK stops self-hosted runners that share one dotnet directory from all installing each new patch at the same time. One value is written inline, several as a `|` block; `global-json-file` is dropped. Left empty, the output is unchanged.

```csharp title="Build.cs"
[GitHubActions(
// ...
SetupDotNetVersions = new[] { "8.0.x", "10.0.103" })]
class Build : FalloutBuild { /* ... */ }
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Fallout.Common.Utilities;
using Fallout.Common.Utilities.Collections;

Expand All @@ -19,6 +20,13 @@ public string SetupDotNetAction
GitHubActionsDefaults.SetupDotNetAction, value, $"{nameof(GitHubActionsRunStep)}.{nameof(SetupDotNetAction)}");
} = GitHubActionsDefaults.SetupDotNetAction;

/// <summary>
/// SDK versions to install, emitted as <c>dotnet-version</c> in place of <c>global-json-file</c>. One value
/// is written inline, several as a <c>|</c> block. Null, empty, or only blank entries keep
/// <c>global-json-file: global.json</c>.
/// </summary>
public string[] SetupDotNetVersions { get; set; }

public string[] InvokedTargets { get; set; }

public Dictionary<string, string> Imports { get; set; }
Expand All @@ -32,7 +40,21 @@ public override void Write(CustomFileWriter writer)
writer.WriteLine("with:");
using (writer.Indent())
{
writer.WriteLine("global-json-file: global.json");
var versions = (SetupDotNetVersions ?? [])
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x.Trim())
.ToArray();

if (versions.Length == 0)
writer.WriteLine("global-json-file: global.json");
else if (versions.Length == 1)
writer.WriteLine($"dotnet-version: {versions[0]}");
else
{
writer.WriteLine("dotnet-version: |");
using (writer.Indent())
versions.ForEach(x => writer.WriteLine(x));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,14 @@ public string CheckoutRef
/// </summary>
public string SetupDotNetAction { get; set; } = GitHubActionsDefaults.SetupDotNetAction;

/// <summary>
/// SDK versions the generated <c>actions/setup-dotnet</c> step installs, emitted as <c>dotnet-version</c>
/// (one value inline, several as a <c>|</c> block). Use it when <c>global.json</c> isn't at the repo root,
/// or to pin an exact SDK so runners sharing one dotnet directory don't all install each new patch at once.
/// Empty (the default) keeps <c>global-json-file: global.json</c>.
/// </summary>
public string[] SetupDotNetVersions { get; set; } = [];

/// <summary>
/// The <c>actions/upload-artifact</c> reference the generated workflow uses; same forms as
/// <see cref="CheckoutAction"/>.
Expand Down Expand Up @@ -354,6 +362,7 @@ private GitHubActionsStep[] GetSteps(IReadOnlyCollection<ExecutableTarget> relev
var run = new GitHubActionsRunStep
{
SetupDotNetAction = SetupDotNetAction,
SetupDotNetVersions = SetupDotNetVersions,
InvokedTargets = InvokedTargets,
Imports = GetImports().ToDictionary(x => x.Key, x => x.Value)
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [TestGitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# fallout --generate-configuration GitHubActions_test --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------

name: test

on: [push]

jobs:
ubuntu-latest:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: 'Cache: .fallout/temp, ~/.nuget/packages'
uses: actions/cache@v6
with:
path: |
.fallout/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: 'Setup: .NET SDK'
uses: actions/setup-dotnet@v6
with:
dotnet-version: 10.0.103
- name: 'Restore: dotnet tools'
run: dotnet tool restore
- name: 'Run: Test'
run: dotnet fallout Test
- name: 'Publish: src'
uses: actions/upload-artifact@v7
with:
name: src
path: src
- name: 'Publish: test-results'
uses: actions/upload-artifact@v7
with:
name: test-results
path: output/test-results
- name: 'Publish: coverage-report.zip'
uses: actions/upload-artifact@v7
with:
name: coverage-report.zip
path: output/coverage-report.zip
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# ------------------------------------------------------------------------------
# <auto-generated>
#
# This code was generated.
#
# - To turn off auto-generation set:
#
# [TestGitHubActions (AutoGenerate = false)]
#
# - To trigger manual generation invoke:
#
# fallout --generate-configuration GitHubActions_test --host GitHubActions
#
# </auto-generated>
# ------------------------------------------------------------------------------

name: test

on: [push]

jobs:
ubuntu-latest:
name: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: 'Cache: .fallout/temp, ~/.nuget/packages'
uses: actions/cache@v6
with:
path: |
.fallout/temp
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: 'Setup: .NET SDK'
uses: actions/setup-dotnet@v6
with:
dotnet-version: |
8.0.x
10.0.103
- name: 'Restore: dotnet tools'
run: dotnet tool restore
- name: 'Run: Test'
run: dotnet fallout Test
- name: 'Publish: src'
uses: actions/upload-artifact@v7
with:
name: src
path: src
- name: 'Publish: test-results'
uses: actions/upload-artifact@v7
with:
name: test-results
path: output/test-results
- name: 'Publish: coverage-report.zip'
uses: actions/upload-artifact@v7
with:
name: coverage-report.zip
path: output/coverage-report.zip
23 changes: 23 additions & 0 deletions tests/Fallout.Common.Specs/CI/ConfigurationGenerationSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,29 @@ public class TestBuild : FalloutBuild
}
);

// Pinned SDK versions replace global-json-file; one is written inline, several as a block.
yield return
(
"setup-dotnet-version",
new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest)
{
On = new[] { GitHubActionsTrigger.Push },
InvokedTargets = new[] { nameof(Test) },
SetupDotNetVersions = new[] { "10.0.103" }
}
);

yield return
(
"setup-dotnet-versions",
new TestGitHubActionsAttribute(GitHubActionsImage.UbuntuLatest)
{
On = new[] { GitHubActionsTrigger.Push },
InvokedTargets = new[] { nameof(Test) },
SetupDotNetVersions = new[] { "8.0.x", "10.0.103" }
}
);

// Every emitted action is overridable, in each accepted form: a bare ref appended to the default
// action name, a complete reference, a SHA pin carrying its version as a trailing comment, and a
// fork. Pack is invoked because it produces artifacts, so the upload step actually renders.
Expand Down
Loading