diff --git a/.github/actions/setup-go-ci/action.yml b/.github/actions/setup-go-ci/action.yml new file mode 100644 index 00000000..53c9fe18 --- /dev/null +++ b/.github/actions/setup-go-ci/action.yml @@ -0,0 +1,110 @@ +# Share module downloads, but keep each workload's compiled code and test results +# in a cache that can refresh after source changes. Dependency-only setup-go +# caches are immutable, so a lint or release job can otherwise freeze an archive +# without the race builds needed by tests. +name: Set up Go for CI +description: Install Go with shared module downloads and workload-specific build caches + +# Composite action inputs are strings. Quote literal boolean-like flags below. +inputs: + build-cache-scope: + description: >- + Workload identifier; defaults to the workflow name and job ID. Test scopes + must include the database version and race mode to isolate test results. + required: false + build-cache-extra-hash: + description: Additional input hash for generated or embedded assets + required: false + check-latest: + description: 'String flag ("true" or "false"): check for the latest matching Go version' + default: "false" + required: false + go-version: + description: Go version or selector; takes precedence over go-version-file + required: false + go-version-file: + description: Repository-relative go.mod or go.work file + required: false + save-module-cache: + description: >- + String flag ("true" or "false"): allow a full-suite job to save module + downloads. Other workloads only restore, so they cannot seed a partial cache. + default: "false" + required: false + +runs: + using: composite + steps: + - name: Install Go + id: go + uses: actions/setup-go@v7 + with: + cache: false + check-latest: ${{ inputs.check-latest }} + go-version: ${{ inputs.go-version }} + go-version-file: ${{ inputs.go-version-file }} + + # Keep keys in outputs. Only paths use GITHUB_ENV because nested composite + # outputs are unavailable when cache post-job inputs are resolved. Invoke + # this action once per job so these job-wide path variables stay consistent. + - name: Prepare cache paths and keys + id: cache-config + shell: bash + env: + BUILD_CACHE_SCOPE: ${{ inputs.build-cache-scope || format('{0}-{1}', github.workflow, github.job) }} + DEPENDENCY_HASH: ${{ hashFiles('**/go.mod', '**/go.sum', 'go.work', 'go.work.sum') }} + EXTRA_INPUT_HASH: ${{ inputs.build-cache-extra-hash }} + GO_VERSION: ${{ steps.go.outputs.go-version }} + SOURCE_HASH: ${{ hashFiles('**/*.go', '**/*.sql', '**/testdata/**', 'public/**', 'Makefile', '.golangci.yaml', '.github/actions/setup-go-ci/**', '.github/workflows/*') }} + run: | + module_cache_prefix="go-mod-v1-${RUNNER_OS}-${RUNNER_ARCH}" + build_cache_prefix="go-build-v1-${RUNNER_OS}-${RUNNER_ARCH}-${ImageOS:-$RUNNER_OS}-${GO_VERSION}-${BUILD_CACHE_SCOPE}" + + { + echo "RIVERUI_GO_BUILD_CACHE=$(go env GOCACHE)" + echo "RIVERUI_GO_MOD_CACHE=$(go env GOMODCACHE)" + } >> "$GITHUB_ENV" + + { + echo "build-prefix=$build_cache_prefix" + echo "dependency-hash=$DEPENDENCY_HASH" + echo "module-prefix=$module_cache_prefix" + echo "source-hash=$SOURCE_HASH${EXTRA_INPUT_HASH:+-$EXTRA_INPUT_HASH}" + } >> "$GITHUB_OUTPUT" + + # Downloads are independent of the Go version and workload. Hash every + # module's manifests and checksums, plus workspace metadata, to refresh the + # immutable archive when version selection changes. Restore older downloads + # as a fallback; Go verifies checksums and fetches missing versions normally. + # Only a full-suite job saves, after downloading the complete module graph. + - name: Restore and save module cache + if: inputs.save-module-cache == 'true' + uses: actions/cache@v6 + with: + path: ${{ env.RIVERUI_GO_MOD_CACHE }} + key: ${{ steps.cache-config.outputs.module-prefix }}-${{ steps.cache-config.outputs.dependency-hash }} + restore-keys: | + ${{ steps.cache-config.outputs.module-prefix }}- + + - name: Restore module cache + if: inputs.save-module-cache != 'true' + uses: actions/cache/restore@v6 + with: + path: ${{ env.RIVERUI_GO_MOD_CACHE }} + key: ${{ steps.cache-config.outputs.module-prefix }}-${{ steps.cache-config.outputs.dependency-hash }} + restore-keys: | + ${{ steps.cache-config.outputs.module-prefix }}- + + # Go also caches successful test results and cannot detect changes to an + # external database. Keep the exact toolchain, workload, database version, + # and race mode in every restore prefix, including dependency fallbacks. + # Source hashes let changed jobs save their newly compiled artifacts rather + # than repeatedly rebuilding against an immutable dependency-only archive. + - name: Restore and save build cache + uses: actions/cache@v6 + with: + path: ${{ env.RIVERUI_GO_BUILD_CACHE }} + key: ${{ steps.cache-config.outputs.build-prefix }}-${{ steps.cache-config.outputs.dependency-hash }}-${{ steps.cache-config.outputs.source-hash }} + restore-keys: | + ${{ steps.cache-config.outputs.build-prefix }}-${{ steps.cache-config.outputs.dependency-hash }}- + ${{ steps.cache-config.outputs.build-prefix }}- diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 75242992..7ec4957f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -104,7 +104,9 @@ updates: - "@tanstack/*" - package-ecosystem: "github-actions" - directory: "/" + directories: + - "/" + - "/.github/actions/**" schedule: interval: "monthly" cooldown: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 76ceb429..dac4149a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -43,9 +43,13 @@ jobs: uses: actions/checkout@v7.0.1 - name: Setup Go ${{ matrix.go-version }} - uses: actions/setup-go@v7 + uses: ./.github/actions/setup-go-ci with: + build-cache-scope: postgres-${{ matrix.postgres-version }}-race go-version: ${{ matrix.go-version }} + # One full-suite job seeds the shared downloads; lint and release + # workloads must not freeze an archive with only their dependencies. + save-module-cache: ${{ matrix.go-version == '1.27' && matrix.postgres-version == 18 && 'true' || 'false' }} - name: Display Go version run: go version @@ -101,9 +105,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v7.0.1 - - uses: actions/setup-go@v7 + - name: Set up Go for lint + uses: ./.github/actions/setup-go-ci with: - check-latest: true + check-latest: "true" go-version-file: "./go.mod" # ensure that there is a file in `dist` to prevent a lint error about @@ -240,9 +245,12 @@ jobs: uses: actions/checkout@v7.0.1 - name: Setup Go - uses: actions/setup-go@v7 + uses: ./.github/actions/setup-go-ci with: - check-latest: true + # The binaries embed the production frontend. Refresh their build + # archive when its sources or build configuration change as well. + build-cache-extra-hash: ${{ hashFiles('src/**', 'package.json', 'package-lock.json', '*.config.*', 'tsconfig*.json', '.npmrc') }} + check-latest: "true" go-version-file: "go.mod" - name: Install Node.js