ci: Gitea pipeline — gate on both self-hosted runners, publish alpha releases
Every push to main now runs the gate on the self-hosted runners and, when
green, publishes a Gitea Release carrying the client, launcher+bake, and
manifest.
Pipeline (.gitea/workflows/ci.yml):
- windows-gate runs tools/run-release-gate.ps1, the project's own bounded
gate. A bare `dotnet test AcDream.slnx` is NOT usable as a gate: it fails
~36 tests by design, because the InstalledDat/Live/Manual/OS lanes assert
their own preconditions. The gate script's trait filter is what excludes
them.
- linux-portable runs the portable closure, where the Linux-lane tests
actually execute instead of failing on Windows.
- release depends on both, so a red gate cannot publish. It is a job in the
same workflow rather than a workflow_run trigger, whose Forgejo support is
unreliable; `needs` is guaranteed.
No actions/setup-dotnet: data.forgejo.org does not mirror it at all (404),
and both runners carry the pinned SDK band already. actions/checkout IS
mirrored and is used normally.
Release payloads become release ATTACHMENTS, outside git history, so ~120 MB
per build never enters a branch. Only the ~500-byte manifest.json is
committed, to the payload-free dist branch, because Forgejo has no
/releases/latest/download/ route (verified 404) for the launcher to poll.
publish-bin.ps1 takes -BaseUrl so the manifest points at the release tag.
Two real gate failures fixed:
- LauncherProjectBoundaryTests asserted four `**` path filters belonging to
the push triggers that 8be14d39 removed when workflows went manual-only.
The assertions about what the workflow DOES are untouched.
- MainWindowViewTests failed in Test Case Cleanup with "calling thread cannot
access this object" while passing in isolation: Avalonia's headless session
is thread-affine and xUnit ran collections in parallel. Serialized via
xunit.runner.json, the same settings AcDream.Core.Tests already uses.
Local gate: 12 projects, 14,346 tests, 0 failures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5256a39fb3
commit
b746d3d61b
5 changed files with 174 additions and 7 deletions
140
.gitea/workflows/ci.yml
Normal file
140
.gitea/workflows/ci.yml
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Gitea Actions CI gate for the self-hosted runners.
|
||||||
|
#
|
||||||
|
# Deliberately does NOT use actions/setup-dotnet: data.forgejo.org (the mirror
|
||||||
|
# Gitea resolves actions from) does not host that action at all, and the
|
||||||
|
# self-hosted runners carry the pinned SDK band from global.json already.
|
||||||
|
# actions/checkout IS mirrored, so it is used normally.
|
||||||
|
#
|
||||||
|
# The suite runs through tools/run-release-gate.ps1 rather than a bare
|
||||||
|
# `dotnet test`: that script owns the xUnit trait-lane filter which excludes
|
||||||
|
# the InstalledDat / Live / Manual / OS-specific lanes. A bare `dotnet test`
|
||||||
|
# fails ~36 tests by design because those lanes assert their own preconditions.
|
||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows-gate:
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Verify the pinned SDK band resolves
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
dotnet --version
|
||||||
|
dotnet --list-sdks
|
||||||
|
|
||||||
|
- name: Complete Release gate
|
||||||
|
shell: pwsh
|
||||||
|
run: ./tools/run-release-gate.ps1
|
||||||
|
|
||||||
|
- name: Upload gate evidence
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-gate-windows
|
||||||
|
path: artifacts/release-gate/
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
|
linux-portable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Portable closure (Linux lanes run here, not on Windows)
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
dotnet --version
|
||||||
|
for p in \
|
||||||
|
tests/AcDream.Platform.Tests \
|
||||||
|
tests/AcDream.Core.Tests \
|
||||||
|
tests/AcDream.Core.Net.Tests \
|
||||||
|
tests/AcDream.Content.Tests \
|
||||||
|
tests/AcDream.Runtime.Tests \
|
||||||
|
tests/AcDream.Headless.Tests \
|
||||||
|
tests/AcDream.Launcher.Core.Tests \
|
||||||
|
tests/AcDream.UI.Abstractions.Tests ; do
|
||||||
|
echo "::group::$p"
|
||||||
|
dotnet test "$p" -c Release --nologo \
|
||||||
|
--filter 'Lane!=InstalledDat&Lane!=PreparedPackage&Lane!=Live&Lane!=Manual&Lane!=Windows&Lane!=SystemFont&Purpose!=Diagnostic&Status!=KnownFailure'
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
|
|
||||||
|
release:
|
||||||
|
# Same workflow rather than a workflow_run trigger: workflow_run is a
|
||||||
|
# GitHub feature whose Forgejo support is unreliable, while `needs` is
|
||||||
|
# guaranteed. A red gate therefore cannot publish.
|
||||||
|
needs: [windows-gate, linux-portable]
|
||||||
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Compute release version
|
||||||
|
id: ver
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$v = '0.1.0-build.{0}' -f ([DateTime]::UtcNow.ToString('yyyyMMddHHmm'))
|
||||||
|
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||||
|
Write-Host "release version: $v"
|
||||||
|
|
||||||
|
- name: Build payloads with release-attachment URLs
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.ver.outputs.version }}
|
||||||
|
run: |
|
||||||
|
./tools/publish-bin.ps1 -Version $env:TAG -BaseUrl "${{ github.server_url }}/${{ github.repository }}/releases/download/$env:TAG"
|
||||||
|
|
||||||
|
- name: Create the release and upload payloads
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.ver.outputs.version }}
|
||||||
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$api = "${{ github.server_url }}/api/v1/repos/${{ github.repository }}"
|
||||||
|
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||||
|
$body = @{
|
||||||
|
tag_name = $env:TAG
|
||||||
|
name = "acdream alpha $env:TAG"
|
||||||
|
body = "Automated alpha build from ${{ github.sha }}."
|
||||||
|
draft = $false
|
||||||
|
prerelease = $true
|
||||||
|
target_commitish = 'main'
|
||||||
|
} | ConvertTo-Json
|
||||||
|
$release = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $headers -ContentType 'application/json' -Body $body
|
||||||
|
Write-Host "created release id=$($release.id)"
|
||||||
|
foreach ($f in Get-ChildItem bin -File) {
|
||||||
|
Write-Host ("uploading {0} ({1:N1} MB)" -f $f.Name, ($f.Length/1MB))
|
||||||
|
Invoke-RestMethod -Method Post -Headers $headers -Uri "$api/releases/$($release.id)/assets?name=$($f.Name)" -Form @{ attachment = Get-Item $f.FullName } | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Point the launcher manifest at the new release
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
TAG: ${{ steps.ver.outputs.version }}
|
||||||
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
# dist carries ONLY manifest.json (~500 bytes), force-replaced each
|
||||||
|
# publish, never pushed to GitHub. Payloads stay in release
|
||||||
|
# attachments, outside git history entirely.
|
||||||
|
$url = "${{ github.server_url }}/${{ github.repository }}.git" -replace '^https://', "https://x:$env:TOKEN@"
|
||||||
|
git config --global user.email 'ci@acdream.local'
|
||||||
|
git config --global user.name 'acdream CI'
|
||||||
|
New-Item -ItemType Directory -Force dist-branch/bin | Out-Null
|
||||||
|
Copy-Item bin/manifest.json dist-branch/bin/manifest.json
|
||||||
|
Push-Location dist-branch
|
||||||
|
git init -q
|
||||||
|
git checkout -q -b dist
|
||||||
|
git add -f bin/manifest.json
|
||||||
|
git commit -q -m "release: manifest for $env:TAG"
|
||||||
|
git push -q --force $url dist:dist
|
||||||
|
Pop-Location
|
||||||
|
Write-Host "manifest published for $env:TAG"
|
||||||
|
|
@ -23,4 +23,14 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\AcDream.Launcher\AcDream.Launcher.csproj" />
|
<ProjectReference Include="..\..\src\AcDream.Launcher\AcDream.Launcher.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Avalonia's headless session is thread-affine, and its per-test cleanup
|
||||||
|
runs on whichever thread xUnit hands it. Under the default parallel
|
||||||
|
collections MainWindowViewTests failed in Test Case Cleanup with
|
||||||
|
"The calling thread cannot access this object because a different
|
||||||
|
thread owns it" while passing in isolation. Same serialization
|
||||||
|
settings AcDream.Core.Tests already uses. -->
|
||||||
|
<None Update="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -140,10 +140,13 @@ public sealed class LauncherProjectBoundaryTests
|
||||||
"workflows",
|
"workflows",
|
||||||
"headless-portability.yml"));
|
"headless-portability.yml"));
|
||||||
|
|
||||||
Assert.Contains("src/AcDream.Launcher/**", workflow, StringComparison.Ordinal);
|
// The four `**` path filters this used to assert belonged to the
|
||||||
Assert.Contains("tests/AcDream.Launcher.Tests/**", workflow, StringComparison.Ordinal);
|
// push/pull_request triggers that `8be14d39 ci: make GitHub workflows
|
||||||
Assert.Contains("src/AcDream.Bake/**", workflow, StringComparison.Ordinal);
|
// manual only` deliberately removed; the workflow is workflow_dispatch
|
||||||
Assert.Contains("tests/AcDream.Bake.Tests/**", workflow, StringComparison.Ordinal);
|
// now. What this test is actually for — that the portability workflow
|
||||||
|
// BUILDS, TESTS, PUBLISHES and EXECUTES the launcher and bake CLI — is
|
||||||
|
// covered by the content assertions below, which are unaffected by how
|
||||||
|
// the workflow is triggered.
|
||||||
Assert.Contains("portable-launcher:", workflow, StringComparison.Ordinal);
|
Assert.Contains("portable-launcher:", workflow, StringComparison.Ordinal);
|
||||||
Assert.Contains("tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj", workflow, StringComparison.Ordinal);
|
Assert.Contains("tests/AcDream.Launcher.Tests/AcDream.Launcher.Tests.csproj", workflow, StringComparison.Ordinal);
|
||||||
Assert.Contains("tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj", workflow, StringComparison.Ordinal);
|
Assert.Contains("tests/AcDream.Bake.Tests/AcDream.Bake.Tests.csproj", workflow, StringComparison.Ordinal);
|
||||||
|
|
|
||||||
6
tests/AcDream.Launcher.Tests/xunit.runner.json
Normal file
6
tests/AcDream.Launcher.Tests/xunit.runner.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
|
||||||
|
"parallelizeAssembly": false,
|
||||||
|
"parallelizeTestCollections": false,
|
||||||
|
"maxParallelThreads": 1
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[string]$Version,
|
[string]$Version,
|
||||||
|
[string]$BaseUrl,
|
||||||
[switch]$IncludeLinux,
|
[switch]$IncludeLinux,
|
||||||
[string]$MinimumLauncherVersion = '0.0.1'
|
[string]$MinimumLauncherVersion = '0.0.1'
|
||||||
)
|
)
|
||||||
|
|
@ -61,9 +62,16 @@ foreach ($candidate in @($Version, $MinimumLauncherVersion)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Raw-file base for the PUBLIC Gitea repo's dist branch. Must agree with
|
# Where the manifest says the payloads live. The CI pipeline passes the Gitea
|
||||||
# ReleaseManifestClient.ProductionManifestUri.
|
# RELEASE asset base for the tag it is publishing, so payloads live outside git
|
||||||
$RawBase = 'https://git.snakedesert.se/erik/acdream/raw/branch/dist/bin'
|
# entirely; the default keeps the older dist-branch layout working for a manual
|
||||||
|
# local publish. The manifest itself always stays at the stable dist raw URL
|
||||||
|
# that ReleaseManifestClient.ProductionManifestUri points at.
|
||||||
|
$RawBase = if ([string]::IsNullOrWhiteSpace($BaseUrl)) {
|
||||||
|
'https://git.snakedesert.se/erik/acdream/raw/branch/dist/bin'
|
||||||
|
} else {
|
||||||
|
$BaseUrl.TrimEnd('/')
|
||||||
|
}
|
||||||
|
|
||||||
$BinRoot = Join-Path $RepoRoot 'bin'
|
$BinRoot = Join-Path $RepoRoot 'bin'
|
||||||
$Staging = Join-Path $BinRoot 'payload'
|
$Staging = Join-Path $BinRoot 'payload'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue