Skip to content

ci: adopt the consolidated .NET workflow - #43

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/happy-rubin-w67sx1
Sep 13, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
claude/happy-rubin-w67sx1

Conversation

@matt-edmondson

@matt-edmondson matt-edmondson commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Every ktsu .NET repository carries its own copy of .github/workflows/dotnet.yml, and they had drifted into five different files. This replaces the local copy with one canonical workflow that is byte-identical in every repository — folding the drift in rather than flattening it.

The drift that was there

variant repositories what was different
ed23a97 (18,495 b) 43, including this one the baseline
f59a33a (18,653 b) AppDataStorage, Extensions, KtsuTools stale comments — one repository's measurements presented as the shared rule
c47b195 (18,775 b) KtsuBuild half of the above
30fbfeb (19,681 b) Schema extra test runtimes; an extra coverage exclusion
cb1dbdc (25,807 b) ImGuiApp macOS matrix, Linux-only UI tests, coverage-path rewrite, Sonar outage probe

Three repositories have no dotnet.yml at all (Sdk, VST, ByteSizeDotnet) and are untouched.

What this repository gains

  • macOS is back in the test matrix. It had been excluded org-wide because a macOS runner widened one repository's target frameworks to include a net10.0-ios head needing a workload this job does not install, so every macOS cell failed with NETSDK1147. That widening is now opt-in, so every other repository stops paying for a problem it never had. The old comment said the platform was "excluded until the underlying problem is fixed rather than papered over" — it is fixed.
  • The UI-test rule names Linux rather than Windows. Behaviour on the existing two platforms is unchanged; the point is that a platform added later gets the cheap treatment by default instead of silently inheriting the expensive one.
  • The Linux coverage report's paths are rewritten to this job's workspace before analysis. Sonar matches coverage to source by path, so coverage produced on the Linux runner was being dropped without a word. The step no-ops where there is no Linux report.
  • The test and release jobs install the 8.0 and 9.0 runtimes alongside the pinned SDK. Test projects commonly multi-target every framework their library publishes, and the test host needs those runtimes present. global.json still pins the SDK, so this adds runtimes without changing what builds.
  • A SonarQube Cloud outage skips analysis instead of failing the build. The scanner fails in pre-processing, before anything is compiled, so an outage turned every pull request red while saying nothing about the change. The probe runs before the Sonar caches and the scanner install, and gates all of them, so an outage does not pay for a dotnet tool install it is about to throw away.
  • Coverage exclusions are extensible per repository through a new SONAR_COVERAGE_EXCLUSIONS_EXTRA variable, so a repository with a file that cannot be executed — a windowed entry point, say — can say so without every other repository carrying its paths. Unset here, so nothing changes.

Nothing in the pipeline's shape changed: the same four jobs, the same steps, the same order.

5dc8128: a correction the outage itself produced

The first commit forgave an outage everywhere except where SONAR_BLOCKING_GATE is set, reasoning that the Release step is implicitly gated on the steps before it succeeding, so skipping would release past the very gate the repository opted into.

Then ktsu-dev/ImGuiApp#395 shipped it into a repository where that variable is set, and a live SonarCloud 503 failed the pull request on all three probe attempts. The probe did exactly what it was written to do; what it was written to do was wrong. That is the false signal the probe exists to remove.

A pull request cannot publish, so failing it buys nothing — a required SonarCloud check still holds the merge, because a skipped analysis reports no gate at all. So an outage is now forgiven on a pull request whatever the gate setting, and only a run that could publish is held to a blocking gate.

What must not happen is a release going out ungated, and that was previously prevented only as a side effect of the whole job failing — which does not cover a gate that never happened, since a skipped step is not a failed one. The Release step now names the two Sonar outputs explicitly: where the gate is blocking, it needs a probe that found the server and an upload that was not forgiven. End SonarQube publishes that second fact as a new analysed output.

How this was checked

  • The canonical file was built from the superset variant by anchored transforms that refuse to apply if any anchor is missing or ambiguous, then asserted structurally: the probe's id and its position before every step that fetches or runs the scanner, all five Sonar gates, the analysed output, the Release condition's four clauses, the macOS platform mapping, the Linux test rule, the coverage-path step, and which setup-dotnet blocks got the extra runtimes (the discover job, which only lists projects, deliberately keeps the plain SDK).
  • It was diffed with comments stripped against all five variants, so the executable delta for each is exactly the list above and nothing else.
  • Both Sonar step scripts were parsed with the PowerShell parser and exercised under pwsh 7.4 outside CI, capturing the process exit code and the written $GITHUB_OUTPUT rather than a pipeline's status, across nine cases:
case exit output
probe, reachable 0 available=true
probe, down, advisory 0 available=false, warning
probe, down, blocking, pull request 0 available=false, warning
probe, down, blocking, push 1 available=false, error
end, success 0 analysed=true
end, fails, blocking, push 1 analysed=false
end, fails, blocking, pull request 0 analysed=false, warning
end, fails, server answering 1 analysed=false, error
end, fails, server unreachable 0 analysed=false, warning

A leftover non-zero $LASTEXITCODE was confirmed not to leak into a shell: pwsh step's exit code, which is what the forgiving branch rests on.

CI on this pull request is the real test of the matrix and runtime changes. This and ktsu-dev/KtsuBuild#128 are the two pilots for the org-wide roll-out; the remaining 45 repositories follow once both are green.

🤖 Generated with Claude Code

https://claude.ai/code/session_015qxqZVzN8CJcDxTb5gtWua

Every ktsu .NET repository carried its own copy of dotnet.yml and they had
drifted into five different files. This replaces the local copy with one
canonical workflow that is byte-identical in every repository, folding in the
drift rather than flattening it.

The drift, and where it went:

* macOS is back in the test matrix. It had been excluded org-wide because a
  macOS runner widened one repository's target frameworks to include a
  net10.0-ios head needing a workload this job does not install. That widening
  is now opt-in, so every other repository stops paying for it. UI test
  projects still run on Linux only, so a macOS cell stays cheap.
* The UI-test rule is now "Linux runs them, every other platform does not",
  rather than naming Windows, so a platform added later gets the cheap
  treatment by default instead of silently inheriting the expensive one.
* The Linux coverage report's paths are rewritten to this job's workspace
  before analysis. Sonar matches coverage to source by path, so a Linux-only
  suite's coverage was being dropped without a word. The step no-ops where
  there is no Linux report.
* The test and release jobs install the 8.0 and 9.0 runtimes alongside the
  pinned SDK, because test projects commonly multi-target every framework
  their library publishes and the test host needs those runtimes present.
  global.json still pins the SDK, so this only adds runtimes.
* Coverage exclusions are extensible per repository through the
  SONAR_COVERAGE_EXCLUSIONS_EXTRA variable, so a repository with a file that
  cannot be executed rather than one nobody has tested yet can say so without
  editing this file and making every other repository carry its paths.
* A SonarQube Cloud outage skips analysis instead of failing the build, and is
  never dressed up as a pass. Where the gate is blocking, an outage still fails.
* Stale comments explaining one repository's circumstances are rewritten to
  explain the shared rule, with the measurements kept and attributed.

Nothing in the pipeline's shape changed: the same jobs, the same steps, the
same order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qxqZVzN8CJcDxTb5gtWua
…request [patch]

Observed live on ktsu-dev/ImGuiApp#395, where SONAR_BLOCKING_GATE is set: a
SonarCloud 503 failed the pull request on all three probe attempts. The probe
did exactly what it was written to do, but what it was written to do was wrong
-- that is precisely the false signal it was added to remove.

A pull request cannot publish, so failing it buys nothing. A required
SonarCloud check still holds the merge, because a skipped analysis reports no
gate at all. So an outage is now forgiven on a pull request whatever the gate
setting, and only a run that could publish is held to a blocking gate.

What must not happen is a release going out ungated, and that was previously
only prevented as a side effect of the whole job failing -- which does not
cover a gate that never happened, since a skipped step is not a failed one.
The Release step now names the two Sonar outputs explicitly: where the gate is
blocking, it needs a probe that found the server and an upload that was not
forgiven. End SonarQube publishes that second fact as a new `analysed` output.

The probe also moves ahead of the two Sonar caches and the scanner install, and
those three are gated on its answer, so an outage no longer pays for a cache
restore and a `dotnet tool install` it is about to throw away.

Re-exercised under pwsh 7.4 outside CI across nine cases, checking the process
exit code and the step outputs: the probe reachable, and unreachable under an
advisory gate, a blocking gate on a pull request, and a blocking gate on a
push; the end pass on success, a blocking-gate push failure, a blocking-gate
pull request, a genuine analysis failure and a mid-run outage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qxqZVzN8CJcDxTb5gtWua
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 08cae88 into main Sep 13, 2026
13 checks passed
@matt-edmondson
matt-edmondson deleted the claude/happy-rubin-w67sx1 branch September 13, 2026 08:34
This was referenced Sep 14, 2026
This was referenced Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants