From e9ff2d2ed04683c17997739901653a3f639f911e Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Wed, 11 Mar 2026 16:18:18 -0700 Subject: [PATCH 1/8] CI: Parallelize integration tests, cache ghcup, fix tag resolution 1. Split integration tests into build + 3 parallel matrix jobs (jvm, compiled, scripting-and-other). Build job populates caches, test jobs restore them. Adds concurrency group to cancel stale runs. 2. Cache ~/.ghcup on macOS/Windows in build-all.yml to skip the ~500 MB GHC download on cache hit. 3. Fix release tag resolution in dist-newstyle cache keys. git describe needs reachable history (fails with fetch-depth: 2). Switch to git tag --sort=-v:refname which lists fetched tag refs regardless of shallow clone depth. Also upgrades actions/cache v4 to v5 (node20 -> node22 runtime). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/bench.yml | 6 +- .github/workflows/build-all.yml | 22 ++- .github/workflows/integrations-test.yml | 172 +++++++++++++++++++++--- 3 files changed, 174 insertions(+), 26 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 965e0aed6..966460ba9 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -20,7 +20,7 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT # Adding the "git config ..." line ensures git doesn't fail during our build. - name: Configure Git @@ -55,7 +55,7 @@ jobs: # Benchmarks build at a different optimization level, so they use # their own cache prefix to avoid collisions with the main build. - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache cabal store with: path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }} @@ -63,7 +63,7 @@ jobs: restore-keys: | ${{ runner.os }}-${{ env.GHC_VERSION }}-benchmarks-cabal-cache- - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache dist-newstyle with: path: ${{ github.workspace }}/dist-newstyle diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index a716db2d6..6038b620f 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -41,16 +41,19 @@ jobs: os-name: macOS-intel project-file: cabal.project.ci.macos ghc: '9.8.4' + cabal: '3.10.3.0' - os: windows-latest os-name: Windows project-file: cabal.project.ci.windows ghc: '9.8.4' + cabal: '3.10.3.0' - os: macos-latest os-name: macOS-arm64 project-file: cabal.project.ci.macos ghc: '9.8.4' + cabal: '3.10.3.0' steps: @@ -62,7 +65,7 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT - name: Install MacOS binary dependencies if: ${{ contains(matrix.os, 'macos') }} @@ -70,13 +73,24 @@ jobs: brew install jq # Set up Haskell. + # Cache ghcup installations so the setup action skips downloading GHC (~500 MB). + - uses: actions/cache@v5 + if: ${{ !contains(matrix.os-name, 'Linux') }} + name: Cache ghcup + with: + path: | + ~/.ghcup/bin + ~/.ghcup/ghc/${{ matrix.ghc }} + ~/.ghcup/cache + key: ${{ matrix.os-name }}-ghcup-${{ matrix.ghc }}-${{ matrix.cabal }} + - uses: haskell-actions/setup@v2 id: setup-haskell name: Setup ghc/cabal (non-alpine) if: ${{ !contains(matrix.os-name, 'Linux') }} with: ghc-version: ${{ matrix.ghc }} - cabal-version: '3.10.3.0' + cabal-version: ${{ matrix.cabal }} # Set up Rust. # This action installs the 'minimal' profile. @@ -129,7 +143,7 @@ jobs: # The home directory inside a github container run during a step is different than one run outside of it. # This is why there is special logic for 'LinuxARM'. # Its builds run inside a container but are cached by an action outside of it. - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache cabal store with: path: ${{ steps.setup-haskell.outputs.cabal-store || ( matrix.os == 'LinuxARM' && format('{0}/_github_home/.local/state/cabal', runner.temp) || '~/.local/state/cabal') }} @@ -137,7 +151,7 @@ jobs: restore-keys: | ${{ matrix.os-name }}-${{ matrix.ghc }}-cabal-cache- - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache dist-newstyle with: path: ${{ github.workspace }}/dist-newstyle diff --git a/.github/workflows/integrations-test.yml b/.github/workflows/integrations-test.yml index f43270a6f..dd0f4187d 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -1,20 +1,36 @@ name: Integration Tests -# Only run workflow manually -# Refer to https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_dispatch on: push: workflow_dispatch: schedule: - cron: "0 5 * * *" # At 05:00 on every day. +# Cancel in-progress runs for the same branch so stale runs don't block the runner. +concurrency: + group: integration-${{ github.ref }} + cancel-in-progress: true + +# All match patterns for integration test groups. Each group runs a subset; +# the build job validates that every test belongs to exactly one group. +env: + TEST_GROUPS: >- + Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure + Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir + Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods + Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner + Container.Analysis Reachability.Upload + jobs: - integration-test: - name: integration-test + # Build once, then run test groups in parallel. + # The build job populates the cabal and dist-newstyle caches. + # Each test job restores those caches so it doesn't need to rebuild. + build: + name: build runs-on: "fossa-cli-integration-runner" - # Be sure to update the env below too container: fossa/haskell-static-alpine:ghc-9.8.4 - + outputs: + latest-tag: ${{ steps.latest-tag.outputs.tag }} env: GHC_VERSION: '9.8.4' @@ -29,13 +45,11 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT - name: Ensures git ownership check does not lead to compile error (we run git during compile for version tagging, etc.) run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - # adduser cannot add users to group: https://unix.stackexchange.com/a/397733 - # so we edit /etc/group directly - name: Create nixbuild users/group run: | addgroup nixbld @@ -60,11 +74,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - # Cache cabal store and dist-newstyle keyed on spectrometer.cabal + project file. - # cabal build handles staleness internally — if a transitive dep changed, - # it recompiles only what's needed. This avoids the 8-min cabal update + - # dry-run solver step that the plan-hash approach requires. - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache cabal store with: path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }} @@ -72,7 +82,7 @@ jobs: restore-keys: | ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache- - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache dist-newstyle with: path: ${{ github.workspace }}/dist-newstyle @@ -98,8 +108,132 @@ jobs: cabal update $RUN_CMD || $RUN_CMD - # This is set up to run integration tests in parallel. - # If that becomes a problem disable it by removing the "+RTS -N -RTS" test options. - - name: Run all integration tests + # Verify every integration test is covered by at least one group pattern. + # Counts tests from an unfiltered --dry-run vs the union of all --match + # patterns. Fails if any tests would be silently skipped. + - name: Validate test groups cover all tests run: | - cabal test --project-file=cabal.project.ci.linux --test-show-details=direct --test-option=--times integration-tests --test-options="+RTS -N -RTS" + TOTAL=$(cabal test --project-file=cabal.project.ci.linux \ + --test-show-details=direct \ + integration-tests \ + --test-option=--dry-run 2>&1 | grep -c "^ " || echo 0) + + MATCH_ARGS="" + for pattern in $TEST_GROUPS; do + MATCH_ARGS="$MATCH_ARGS --test-option=--match --test-option=$pattern" + done + MATCHED=$(cabal test --project-file=cabal.project.ci.linux \ + --test-show-details=direct \ + $MATCH_ARGS \ + integration-tests \ + --test-option=--dry-run 2>&1 | grep -c "^ " || echo 0) + + echo "Total tests: $TOTAL" + echo "Matched tests: $MATCHED" + if [ "$TOTAL" -ne "$MATCHED" ]; then + echo "ERROR: $((TOTAL - MATCHED)) integration tests are not covered by any group pattern." + echo "Update TEST_GROUPS in integrations-test.yml to include the missing patterns." + exit 1 + fi + + # Upload artifacts that test jobs need so they don't have to rebuild + # Rust or re-download vendor binaries. + - uses: actions/upload-artifact@v4 + with: + name: vendor-bins + path: vendor-bins/ + + - uses: actions/upload-artifact@v4 + with: + name: rust-release-bins + path: | + target/release/berkeleydb + target/release/millhone + + integration-test: + name: test-${{ matrix.group }} + needs: build + runs-on: "fossa-cli-integration-runner" + container: fossa/haskell-static-alpine:ghc-9.8.4 + env: + GHC_VERSION: '9.8.4' + + strategy: + fail-fast: false + matrix: + include: + - group: jvm + matches: "Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure" + + - group: compiled + matches: "Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir" + + - group: scripting-and-other + matches: "Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner Container.Analysis Reachability.Upload" + + steps: + - uses: actions/checkout@v4 + with: + lfs: true + fetch-depth: 2 + + - name: Ensures git ownership check does not lead to compile error + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Create nixbuild users/group + run: | + addgroup nixbld + adduser -D nixbld-1 + adduser -D nixbld-2 + adduser -D nixbld-3 + sed 's/nixbld:x:\([[:digit:]]*\):$/nixbld:x:\1:nixbld-1,nixbld-2,nixbld-3/' /etc/group > group-changed + mv group-changed /etc/group + + - uses: cachix/install-nix-action@v25 + with: + nix_path: nixpkgs=channel:nixos-25.05 + extra_nix_config: "build-users-group = nixbld" + + # Restore caches populated by the build job. + - uses: actions/cache@v5 + name: Cache cabal store + with: + path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }} + key: ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }} + restore-keys: | + ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache- + + - uses: actions/cache@v5 + name: Cache dist-newstyle + with: + path: ${{ github.workspace }}/dist-newstyle + key: ${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle-${{ needs.build.outputs.latest-tag }}-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }} + restore-keys: | + ${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle-${{ needs.build.outputs.latest-tag }}- + ${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle- + + # extra-source-files (vendor-bins/*, target/release/*) must exist on + # disk or cabal recompiles EmbeddedBinary.hs (fails under -Werror). + - uses: actions/download-artifact@v4 + with: + name: vendor-bins + path: vendor-bins/ + + - uses: actions/download-artifact@v4 + with: + name: rust-release-bins + path: target/release/ + + - name: Run integration tests (${{ matrix.group }}) + run: | + cabal update + MATCH_ARGS="" + for pattern in ${{ matrix.matches }}; do + MATCH_ARGS="$MATCH_ARGS --test-option=--match --test-option=$pattern" + done + cabal test --project-file=cabal.project.ci.linux \ + --test-show-details=direct \ + --test-option=--times \ + $MATCH_ARGS \ + integration-tests \ + --test-options="+RTS -N -RTS" From 2a755f3270467f45f04238a43246bfcda7171b15 Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Wed, 11 Mar 2026 23:51:13 -0700 Subject: [PATCH 2/8] Trigger CI From 3767548cf5499a6e7199aa86789c83a827878dd0 Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 00:15:13 -0700 Subject: [PATCH 3/8] CI: Bump actions to v5 (Node.js 24), fix tag fallback, fail on missing artifacts - Upgrade actions/checkout, actions/upload-artifact, actions/download-artifact from v4 to v5 across all workflows (Node.js 20 deprecation). - Fix broken tag fallback in build-all.yml (head -1 always exits 0, so || echo none never ran). - Add if-no-files-found: error to upload-artifact steps in integration tests so missing vendor-bins or Rust binaries fail the build job. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/badges.yml | 2 +- .github/workflows/bench.yml | 2 +- .github/workflows/build-all.yml | 12 +++++++----- .github/workflows/dependency-scan.yml | 2 +- .github/workflows/install-script-test.yml | 6 +++--- .github/workflows/integrations-test.yml | 14 ++++++++------ .github/workflows/lint.yml | 14 +++++++------- .github/workflows/report.yml | 2 +- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/.github/workflows/badges.yml b/.github/workflows/badges.yml index d01c0a5d6..993ce47a5 100644 --- a/.github/workflows/badges.yml +++ b/.github/workflows/badges.yml @@ -11,7 +11,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Update badge run: | CLI_DOWNLOADS=$(gh api /repos/fossas/fossa-cli/releases --paginate --cache 1h | jq '.[] | .assets | .[] | .download_count' | jq --slurp 'add') diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 966460ba9..c9883d3c0 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -13,7 +13,7 @@ jobs: steps: - uses: dtolnay/rust-toolchain@stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-tags: true diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 6038b620f..955700067 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -57,7 +57,7 @@ jobs: steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-depth: 2 @@ -65,7 +65,9 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: | + tag="$(git tag --sort=-v:refname 2>/dev/null | head -1)" + echo "tag=${tag:-none}" >> "$GITHUB_OUTPUT" - name: Install MacOS binary dependencies if: ${{ contains(matrix.os, 'macos') }} @@ -263,7 +265,7 @@ jobs: exit 1 fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: ${{ matrix.os-name }}-binaries path: release @@ -277,7 +279,7 @@ jobs: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 # Sets VERSION from git's tag or sha. # refer to: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#example-of-setting-an-output-parameter @@ -452,7 +454,7 @@ jobs: # need to run for tagged release versions. - name: Upload release archives if: ${{ !startsWith(github.ref, 'refs/tags/v') }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: release-archives path: release diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-scan.yml index bf564eb3e..0cebc20a1 100644 --- a/.github/workflows/dependency-scan.yml +++ b/.github/workflows/dependency-scan.yml @@ -9,7 +9,7 @@ jobs: FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install and set GHC and Cabal versions # The first line works around a ghcup issue: diff --git a/.github/workflows/install-script-test.yml b/.github/workflows/install-script-test.yml index c9f45442c..0000dc2c4 100644 --- a/.github/workflows/install-script-test.yml +++ b/.github/workflows/install-script-test.yml @@ -8,7 +8,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest-large] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: install script performs installation shell: bash @@ -61,7 +61,7 @@ jobs: test-macos-arm: runs-on: "macos-latest" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: install latest script can install a specific version shell: bash run: | @@ -80,7 +80,7 @@ jobs: test-windows: runs-on: "windows-latest" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: get latest release version from github shell: pwsh diff --git a/.github/workflows/integrations-test.yml b/.github/workflows/integrations-test.yml index dd0f4187d..f6ef21a93 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -37,7 +37,7 @@ jobs: steps: - uses: dtolnay/rust-toolchain@stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-depth: 2 @@ -138,17 +138,19 @@ jobs: # Upload artifacts that test jobs need so they don't have to rebuild # Rust or re-download vendor binaries. - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: vendor-bins path: vendor-bins/ + if-no-files-found: error - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: rust-release-bins path: | target/release/berkeleydb target/release/millhone + if-no-files-found: error integration-test: name: test-${{ matrix.group }} @@ -172,7 +174,7 @@ jobs: matches: "Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner Container.Analysis Reachability.Upload" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-depth: 2 @@ -214,12 +216,12 @@ jobs: # extra-source-files (vendor-bins/*, target/release/*) must exist on # disk or cabal recompiles EmbeddedBinary.hs (fails under -Werror). - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: vendor-bins path: vendor-bins/ - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: rust-release-bins path: target/release/ diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8713007b0..b446b12a0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: with: components: "clippy,rustfmt" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run hlint run: | @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Check Markdown links uses: tcort/github-action-markdown-link-check@v1.1.0 @@ -46,7 +46,7 @@ jobs: with: components: "clippy,rustfmt" - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Run the formatter - name: run fourmolu @@ -59,7 +59,7 @@ jobs: container: ghcr.io/fossas/haskell-dev-tools:9.8.4 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Run the formatter - name: "run cabal-fmt" @@ -70,7 +70,7 @@ jobs: name: "schema lint check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: json-syntax-check uses: limitusus/json-syntax-check@v1 with: @@ -80,7 +80,7 @@ jobs: name: "Check for correct spelling of FOSSA" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: "Check for incorrect FOSSA wording" run: | ! grep 'Fossa ' **/*.md @@ -89,7 +89,7 @@ jobs: name: "Lint bash scripts" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: run shellcheck uses: sudo-bot/action-shellcheck@latest with: diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml index f2d77c47e..ee1ac4ccd 100644 --- a/.github/workflows/report.yml +++ b/.github/workflows/report.yml @@ -12,7 +12,7 @@ jobs: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install fossa-cli run: | ./install-latest.sh -d From b393b96174469c0737db4d46a5af52316c83f4f2 Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 00:18:32 -0700 Subject: [PATCH 4/8] CI: Fix git tag fallback under bash pipefail GitHub Actions runs bash with -e -o pipefail. When git tag exits 128 (e.g. no tags fetched), pipefail propagates the error even though head -1 succeeds. Add || true to the pipeline so ${tag:-none} handles the empty case. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/bench.yml | 4 +++- .github/workflows/build-all.yml | 2 +- .github/workflows/integrations-test.yml | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index c9883d3c0..586c9408d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -20,7 +20,9 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: | + tag="$(git tag --sort=-v:refname 2>/dev/null | head -1 || true)" + echo "tag=${tag:-none}" >> "$GITHUB_OUTPUT" # Adding the "git config ..." line ensures git doesn't fail during our build. - name: Configure Git diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 955700067..bee579845 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -66,7 +66,7 @@ jobs: - name: Get latest release tag id: latest-tag run: | - tag="$(git tag --sort=-v:refname 2>/dev/null | head -1)" + tag="$(git tag --sort=-v:refname 2>/dev/null | head -1 || true)" echo "tag=${tag:-none}" >> "$GITHUB_OUTPUT" - name: Install MacOS binary dependencies diff --git a/.github/workflows/integrations-test.yml b/.github/workflows/integrations-test.yml index f6ef21a93..caefa2ed2 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -45,7 +45,9 @@ jobs: - name: Get latest release tag id: latest-tag - run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT + run: | + tag="$(git tag --sort=-v:refname 2>/dev/null | head -1 || true)" + echo "tag=${tag:-none}" >> "$GITHUB_OUTPUT" - name: Ensures git ownership check does not lead to compile error (we run git during compile for version tagging, etc.) run: git config --global --add safe.directory "$GITHUB_WORKSPACE" From afe4dbad89e59ab1b02b887886553c7af984ec6d Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 15:23:22 -0700 Subject: [PATCH 5/8] CI: Revert cabal version back to hardcoded in build-all.yml The cabal version is intentionally the same across all matrix entries. Keep it hardcoded in the setup and cache key rather than adding a matrix variable. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-all.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index bee579845..bfd125b81 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -41,19 +41,16 @@ jobs: os-name: macOS-intel project-file: cabal.project.ci.macos ghc: '9.8.4' - cabal: '3.10.3.0' - os: windows-latest os-name: Windows project-file: cabal.project.ci.windows ghc: '9.8.4' - cabal: '3.10.3.0' - os: macos-latest os-name: macOS-arm64 project-file: cabal.project.ci.macos ghc: '9.8.4' - cabal: '3.10.3.0' steps: @@ -84,7 +81,7 @@ jobs: ~/.ghcup/bin ~/.ghcup/ghc/${{ matrix.ghc }} ~/.ghcup/cache - key: ${{ matrix.os-name }}-ghcup-${{ matrix.ghc }}-${{ matrix.cabal }} + key: ${{ matrix.os-name }}-ghcup-${{ matrix.ghc }}-3.10.3.0 - uses: haskell-actions/setup@v2 id: setup-haskell @@ -92,7 +89,7 @@ jobs: if: ${{ !contains(matrix.os-name, 'Linux') }} with: ghc-version: ${{ matrix.ghc }} - cabal-version: ${{ matrix.cabal }} + cabal-version: '3.10.3.0' # Set up Rust. # This action installs the 'minimal' profile. From ad92f0a73e507461eee60739180f0b4a38645b19 Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 15:26:56 -0700 Subject: [PATCH 6/8] CI: Single source of truth for integration test groups Define test groups as JSON in env.TEST_MATRIX. The matrix strategy uses fromJson() and the validation step extracts patterns with jq, both from the same variable. Prevents adding a pattern to the validation list without assigning it to a matrix group. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/integrations-test.yml | 31 ++++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/.github/workflows/integrations-test.yml b/.github/workflows/integrations-test.yml index caefa2ed2..6b709a28b 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -11,15 +11,15 @@ concurrency: group: integration-${{ github.ref }} cancel-in-progress: true -# All match patterns for integration test groups. Each group runs a subset; -# the build job validates that every test belongs to exactly one group. +# Single source of truth for integration test groups. Used by both the +# matrix strategy and the build job's coverage validation step. env: - TEST_GROUPS: >- - Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure - Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir - Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods - Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner - Container.Analysis Reachability.Upload + TEST_MATRIX: >- + [ + {"group": "jvm", "matches": "Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure"}, + {"group": "compiled", "matches": "Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir"}, + {"group": "scripting-and-other", "matches": "Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner Container.Analysis Reachability.Upload"} + ] jobs: # Build once, then run test groups in parallel. @@ -120,8 +120,9 @@ jobs: integration-tests \ --test-option=--dry-run 2>&1 | grep -c "^ " || echo 0) + ALL_PATTERNS=$(echo '${{ env.TEST_MATRIX }}' | jq -r '.[].matches' | tr ' ' '\n' | sort -u | tr '\n' ' ') MATCH_ARGS="" - for pattern in $TEST_GROUPS; do + for pattern in $ALL_PATTERNS; do MATCH_ARGS="$MATCH_ARGS --test-option=--match --test-option=$pattern" done MATCHED=$(cabal test --project-file=cabal.project.ci.linux \ @@ -134,7 +135,7 @@ jobs: echo "Matched tests: $MATCHED" if [ "$TOTAL" -ne "$MATCHED" ]; then echo "ERROR: $((TOTAL - MATCHED)) integration tests are not covered by any group pattern." - echo "Update TEST_GROUPS in integrations-test.yml to include the missing patterns." + echo "Update TEST_MATRIX in integrations-test.yml to include the missing patterns." exit 1 fi @@ -165,15 +166,7 @@ jobs: strategy: fail-fast: false matrix: - include: - - group: jvm - matches: "Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure" - - - group: compiled - matches: "Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir" - - - group: scripting-and-other - matches: "Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner Container.Analysis Reachability.Upload" + include: ${{ fromJson(env.TEST_MATRIX) }} steps: - uses: actions/checkout@v5 From 6b16ad33fdb6749bb491b9e7aa84677d6902903f Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 15:36:45 -0700 Subject: [PATCH 7/8] CI: Fix env context in strategy block, remove dead setup-haskell refs env is not available in strategy.matrix evaluation. Pass TEST_MATRIX through build job outputs so the integration-test matrix can consume it via needs.build.outputs.test-matrix. Also replace dead steps.setup-haskell.outputs.cabal-store references with the literal ~/.local/state/cabal path (no setup-haskell step exists in these jobs). Co-Authored-By: Claude Opus 4.6 --- .github/workflows/integrations-test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integrations-test.yml b/.github/workflows/integrations-test.yml index 6b709a28b..b239e2093 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -31,6 +31,7 @@ jobs: container: fossa/haskell-static-alpine:ghc-9.8.4 outputs: latest-tag: ${{ steps.latest-tag.outputs.tag }} + test-matrix: ${{ env.TEST_MATRIX }} env: GHC_VERSION: '9.8.4' @@ -79,7 +80,7 @@ jobs: - uses: actions/cache@v5 name: Cache cabal store with: - path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }} + path: ~/.local/state/cabal key: ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }} restore-keys: | ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache- @@ -166,7 +167,7 @@ jobs: strategy: fail-fast: false matrix: - include: ${{ fromJson(env.TEST_MATRIX) }} + include: ${{ fromJson(needs.build.outputs.test-matrix) }} steps: - uses: actions/checkout@v5 @@ -195,7 +196,7 @@ jobs: - uses: actions/cache@v5 name: Cache cabal store with: - path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }} + path: ~/.local/state/cabal key: ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }} restore-keys: | ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache- From 9ec497c4b58ac54e58bab5e345e0c05e8a426ed8 Mon Sep 17 00:00:00 2001 From: Zach LaVallee Date: Thu, 12 Mar 2026 15:56:41 -0700 Subject: [PATCH 8/8] CI: Fix ghcup cache paths for Windows On Windows, ghcup installs to C:\ghcup\, not ~/.ghcup/. Use runner.os to select the correct path per platform so the Windows ghcup cache actually hits. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/build-all.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index bfd125b81..b092e4bc4 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -78,9 +78,9 @@ jobs: name: Cache ghcup with: path: | - ~/.ghcup/bin - ~/.ghcup/ghc/${{ matrix.ghc }} - ~/.ghcup/cache + ${{ runner.os == 'Windows' && 'C:\ghcup\bin' || '~/.ghcup/bin' }} + ${{ runner.os == 'Windows' && format('C:\ghcup\ghc\{0}', matrix.ghc) || format('~/.ghcup/ghc/{0}', matrix.ghc) }} + ${{ runner.os == 'Windows' && 'C:\ghcup\cache' || '~/.ghcup/cache' }} key: ${{ matrix.os-name }}-ghcup-${{ matrix.ghc }}-3.10.3.0 - uses: haskell-actions/setup@v2