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 965e0aed6..586c9408d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -13,14 +13,16 @@ jobs: steps: - uses: dtolnay/rust-toolchain@stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-tags: true - name: Get latest release tag id: latest-tag - run: echo "tag=$(git describe --tags --abbrev=0 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 @@ -55,7 +57,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 +65,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..b092e4bc4 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -54,7 +54,7 @@ jobs: steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-depth: 2 @@ -62,7 +62,9 @@ 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: | + tag="$(git tag --sort=-v:refname 2>/dev/null | head -1 || true)" + echo "tag=${tag:-none}" >> "$GITHUB_OUTPUT" - name: Install MacOS binary dependencies if: ${{ contains(matrix.os, 'macos') }} @@ -70,6 +72,17 @@ 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: | + ${{ 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 id: setup-haskell name: Setup ghc/cabal (non-alpine) @@ -129,7 +142,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 +150,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 @@ -249,7 +262,7 @@ jobs: exit 1 fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: ${{ matrix.os-name }}-binaries path: release @@ -263,7 +276,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 @@ -438,7 +451,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 f43270a6f..b239e2093 100644 --- a/.github/workflows/integrations-test.yml +++ b/.github/workflows/integrations-test.yml @@ -1,27 +1,44 @@ 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 + +# Single source of truth for integration test groups. Used by both the +# matrix strategy and the build job's coverage validation step. +env: + 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: - 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 }} + test-matrix: ${{ env.TEST_MATRIX }} env: GHC_VERSION: '9.8.4' steps: - uses: dtolnay/rust-toolchain@stable - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: lfs: true fetch-depth: 2 @@ -29,13 +46,13 @@ 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: | + 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" - # 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,19 +77,15 @@ 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' }} + 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- - - uses: actions/cache@v4 + - uses: actions/cache@v5 name: Cache dist-newstyle with: path: ${{ github.workspace }}/dist-newstyle @@ -98,8 +111,127 @@ 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: | + 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) + + ALL_PATTERNS=$(echo '${{ env.TEST_MATRIX }}' | jq -r '.[].matches' | tr ' ' '\n' | sort -u | tr '\n' ' ') + MATCH_ARGS="" + 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 \ + --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_MATRIX 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@v5 + with: + name: vendor-bins + path: vendor-bins/ + if-no-files-found: error + + - 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 }} + 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: ${{ fromJson(needs.build.outputs.test-matrix) }} + + steps: + - uses: actions/checkout@v5 + 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: ~/.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@v5 + with: + name: vendor-bins + path: vendor-bins/ + + - uses: actions/download-artifact@v5 + with: + name: rust-release-bins + path: target/release/ + + - name: Run integration tests (${{ matrix.group }}) run: | - cabal test --project-file=cabal.project.ci.linux --test-show-details=direct --test-option=--times integration-tests --test-options="+RTS -N -RTS" + 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" 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