From cdf0df5cfe98be512b4e16f5667ebea08a785ce7 Mon Sep 17 00:00:00 2001 From: nicodes Date: Sat, 1 Aug 2026 18:45:17 -0600 Subject: [PATCH] Standardize CI on the org shape The checks move into .github/actions/test, called by CI. Nine of the fourteen gd-* repos carried a byte-identical ci.yml and five had drifted; the drift was almost entirely legitimate -- each addon validates its own file manifest -- so those manifests are preserved verbatim. The `if: hashFiles(...)` guards are gone. A step that skips itself when its test script is absent is indistinguishable from one whose script was renamed or deleted: a skipped test is a green tick. All fourteen repos were surveyed first and every one of them does run a suite, so removing the guards costs nothing today -- which is exactly why now is the cheapest time to remove them. Godot comes from a local setup-godot composite action, copied from the one castledrop and prizm already carry, rather than a download URL written into ci.yml. The engine version this addon is tested against was living in workflow YAML. One job named ci, timeout-minutes, and third-party actions pinned by SHA with the tag in a trailing comment. Co-Authored-By: Claude Opus 5 (1M context) Release also runs Test now. It used to read the version, package the addon and publish it to the GDAM registry without running any check at all -- the only thing between a broken commit and a published release was whether somebody had looked at CI first. --- .github/actions/setup-godot/action.yml | 55 +++++++++++++++++++++++++ .github/actions/test/action.yml | 44 ++++++++++++++++++++ .github/workflows/ci.yml | 56 +++++++++++++------------- .github/workflows/release.yml | 13 +++++- 4 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 .github/actions/setup-godot/action.yml create mode 100644 .github/actions/test/action.yml diff --git a/.github/actions/setup-godot/action.yml b/.github/actions/setup-godot/action.yml new file mode 100644 index 0000000..b12f973 --- /dev/null +++ b/.github/actions/setup-godot/action.yml @@ -0,0 +1,55 @@ +name: 'Setup Godot' +description: 'Install Godot binary with caching and optionally install export templates' +inputs: + godot-version: + description: 'Godot version to install (e.g., 4.4.1)' + required: true + install-templates: + description: 'Whether to install export templates' + required: false + default: 'false' + +runs: + using: 'composite' + steps: + - name: Cache Godot binary + id: cache-godot + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: /usr/local/bin/godot + key: godot-${{ inputs.godot-version }}-${{ runner.os }} + + - name: Install Godot + if: steps.cache-godot.outputs.cache-hit != 'true' + shell: bash + run: | + wget -q https://github.com/godotengine/godot/releases/download/${{ inputs.godot-version }}-stable/Godot_v${{ inputs.godot-version }}-stable_linux.x86_64.zip + unzip -q Godot_v${{ inputs.godot-version }}-stable_linux.x86_64.zip + chmod +x Godot_v${{ inputs.godot-version }}-stable_linux.x86_64 + sudo mv Godot_v${{ inputs.godot-version }}-stable_linux.x86_64 /usr/local/bin/godot + + - name: Verify Godot installation + shell: bash + run: godot --version + + - name: Cache Godot export templates + if: inputs.install-templates == 'true' + id: cache-godot-templates + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: ~/.local/share/godot/export_templates/${{ inputs.godot-version }}.stable + key: godot-templates-${{ inputs.godot-version }}-${{ runner.os }} + + - name: Install Godot export templates + if: inputs.install-templates == 'true' && steps.cache-godot-templates.outputs.cache-hit != 'true' + shell: bash + run: | + mkdir -p ~/.local/share/godot/export_templates/${{ inputs.godot-version }}.stable + wget -q https://github.com/godotengine/godot/releases/download/${{ inputs.godot-version }}-stable/Godot_v${{ inputs.godot-version }}-stable_export_templates.tpz + unzip -q Godot_v${{ inputs.godot-version }}-stable_export_templates.tpz + mv templates/* ~/.local/share/godot/export_templates/${{ inputs.godot-version }}.stable/ + + - name: Verify Godot templates + if: inputs.install-templates == 'true' + shell: bash + run: ls -la ~/.local/share/godot/export_templates/${{ inputs.godot-version }}.stable/ diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..ecdedef --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,44 @@ +name: Test +description: > + Is this commit good. Nothing is packaged for release here and nothing is + published; a failure means the addon is wrong, not that the pipeline is. + + A composite action rather than a reusable workflow so it runs in the caller's + job, under the caller's name -- CI / Test, Release / Test -- rather than as a + nested "caller / callee" check. + + ONE definition, called by CI and by Release both. Release did not run any of + this before: it read the version, packaged the addon and published it, so the + only thing between a broken commit and the registry was whether somebody + looked at CI first. + + A per-repo copy, not a shared action. `uses: ./` is repo-local, so the + fourteen copies of this file are copies -- the accepted cost of no repo's CI + breaking because another repo changed. + +runs: + using: composite + steps: + # The addon's own file manifest. Listed rather than globbed: a file dropped + # in a refactor is exactly what this catches, and a glob would happily match + # whatever was left. + - name: Validate addon package + shell: bash + run: | + set -euo pipefail + test -f addon/plugin.cfg + + # Godot comes from a LOCAL action, copied from the one castledrop and prizm + # carry. The version used to be a download URL written into ci.yml -- the + # engine this addon is tested against lived in workflow YAML. + - uses: ./.github/actions/setup-godot + with: + godot-version: '4.4.1' + + # No `if: hashFiles(...)` guard. This step used to skip itself when + # tests/test.sh was absent, which is indistinguishable from the script being + # renamed or deleted -- a skipped test is a green tick. This addon has a + # suite, so the step runs unconditionally and a missing script now fails. + - name: Godot tests + shell: bash + run: ./tests/test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26de069..c323cbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,41 +1,41 @@ name: CI +# Every pull request and every merge to main: test, on one runner. +# +# There is no build. A Godot addon is GDScript in a directory -- what a release +# ships is the same files, zipped -- so the only question is whether it is +# correct, and that is Test's job. +# +# The push trigger is not redundant here: this repository has no CD, so nothing +# else covers a merge to main. +# +# The steps are .github/actions/test, the same definition Release runs. There is +# one of it, and it is the one guarding a publish to the GDAM registry. + on: pull_request: push: - branches: - - main + branches: [main] + workflow_dispatch: permissions: contents: read +concurrency: + group: ci-${ github.ref } + cancel-in-progress: true + jobs: - sanity: + ci: runs-on: ubuntu-latest + # Bounded, so a step that hangs fails here rather than sitting until the + # runner's own timeout hours later. + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 - - - name: Validate addon package - run: test -f addon/plugin.cfg - - - name: Install Godot - if: ${{ hashFiles('tests/test.sh') != '' }} - run: | - set -euo pipefail - curl -fsSL -o /tmp/godot.zip https://github.com/godotengine/godot/releases/download/4.4.1-stable/Godot_v4.4.1-stable_linux.x86_64.zip - unzip -q /tmp/godot.zip -d /tmp/godot - sudo install -m 0755 /tmp/godot/Godot_v4.4.1-stable_linux.x86_64 /usr/local/bin/godot - - - name: Run Godot tests - if: ${{ hashFiles('tests/test.sh') != '' }} - run: ./tests/test.sh - - - uses: actions/setup-go@v5 - if: ${{ hashFiles('cli/go.mod') != '' }} - with: - go-version-file: cli/go.mod + # Third-party actions are pinned by SHA, with the tag in a trailing + # comment so the version is still readable. A tag is a moving reference: + # whoever can move it can run code in this job. + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - name: Run Go tests - if: ${{ hashFiles('cli/go.mod') != '' }} - working-directory: cli - run: go test ./... + - name: Test + uses: ./.github/actions/test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a003ca..50e8592 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,8 +20,11 @@ concurrency: release-${{ github.repository }} jobs: release: runs-on: ubuntu-latest + # Bounded, so a step that hangs fails here rather than sitting until the + # runner's own timeout hours later. + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 @@ -63,6 +66,14 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" + # The same checks CI runs, from the same definition. This workflow used + # to package and publish without running any of them -- the only thing + # between a broken commit and the GDAM registry was whether somebody had + # looked at CI. Running them here against this exact commit is the point: + # CI passing on this SHA earlier is a claim about that run. + - name: Test + uses: ./.github/actions/test + - name: Package addon run: | set -euo pipefail