diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 524bfaf69f..76366bc17d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,51 +10,7 @@ jobs: # Every test job runs unless this job succeeded and positively reported # code=false, so a broken detector costs time rather than coverage. changes: - name: Detect code changes - runs-on: ubuntu-latest - outputs: - code: ${{ steps.filter.outputs.code }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Decide whether anything testable changed - id: filter - env: - BASE_REF: ${{ github.base_ref }} - run: | - # Conservative by construction: only a recognised documentation path - # votes "no code". Anything unrecognised -- and every push build, which - # has no base to diff against -- runs the full suite. - if [ -z "$BASE_REF" ]; then - echo "Push build: running everything." - echo "code=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - git fetch --no-tags origin "$BASE_REF" - base=$(git merge-base FETCH_HEAD HEAD) - # --no-renames: a rename is reported as its destination alone, so moving - # a module under docs/ would otherwise hide the deleted code path. - changed=$(git diff --no-renames --name-only "$base" HEAD) - - echo "Changed files:" - echo "$changed" - - # `grep -v` exits 1 when everything was filtered out, which is exactly - # the documentation-only case, so it must not trip `set -e`. - testable=$(echo "$changed" | grep -Ev '(\.md$|^docs/|^\.github/ISSUE_TEMPLATE/)' || true) - - if [ -z "$testable" ]; then - echo "Documentation only: skipping the test jobs." - echo "code=false" >> "$GITHUB_OUTPUT" - else - echo "Testable changes present:" - echo "$testable" - echo "code=true" >> "$GITHUB_OUTPUT" - fi + uses: ./.github/workflows/detect-code-changes.yml unit-tests: name: Unit Tests diff --git a/.github/workflows/detect-code-changes.yml b/.github/workflows/detect-code-changes.yml new file mode 100644 index 0000000000..29af4aadc5 --- /dev/null +++ b/.github/workflows/detect-code-changes.yml @@ -0,0 +1,56 @@ +name: Detect code changes + +on: + workflow_call: + outputs: + code: + description: '"true" when anything outside the documentation paths changed.' + value: ${{ jobs.filter.outputs.code }} + +jobs: + filter: + name: Detect code changes + runs-on: ubuntu-latest + outputs: + code: ${{ steps.filter.outputs.code }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Decide whether anything testable changed + id: filter + env: + BASE_REF: ${{ github.base_ref }} + run: | + # Conservative by construction: only a recognised documentation path + # votes "no code". Anything unrecognised -- and every push build, which + # has no base to diff against -- runs the full suite. + if [ -z "$BASE_REF" ]; then + echo "Push build: running everything." + echo "code=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin "$BASE_REF" + base=$(git merge-base FETCH_HEAD HEAD) + # --no-renames: a rename is reported as its destination alone, so moving + # a module under docs/ would otherwise hide the deleted code path. + changed=$(git diff --no-renames --name-only "$base" HEAD) + + echo "Changed files:" + echo "$changed" + + # `grep -v` exits 1 when everything was filtered out, which is exactly + # the documentation-only case, so it must not trip `set -e`. + testable=$(echo "$changed" | grep -Ev '(\.md$|^docs/|^\.github/ISSUE_TEMPLATE/)' || true) + + if [ -z "$testable" ]; then + echo "Documentation only: skipping the test jobs." + echo "code=false" >> "$GITHUB_OUTPUT" + else + echo "Testable changes present:" + echo "$testable" + echo "code=true" >> "$GITHUB_OUTPUT" + fi diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 25bde9f831..95b9ea57d5 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -7,8 +7,18 @@ on: branches: [ main, '3.0' ] jobs: + # The matrix runs unless this job succeeded and positively reported + # code=false, so a broken detector costs time rather than coverage. + changes: + uses: ./.github/workflows/detect-code-changes.yml + test: name: ${{ matrix.py }} - ${{ matrix.os }} + needs: changes + # The matrix must expand even when there is nothing to install: a job-level + # skip reports one unexpanded check, so the six required contexts would + # never appear and every documentation-only PR would hang at "Expected". + if: always() runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -23,23 +33,31 @@ jobs: py: "3.14" steps: + # The predicate is written once here, as a step output, rather than + # repeated on every step below. + - name: Decide whether to install + id: gate + run: echo "skip=${{ needs.changes.result == 'success' && needs.changes.outputs.code == 'false' }}" >> "$GITHUB_OUTPUT" + - name: Checkout code + if: steps.gate.outputs.skip != 'true' uses: actions/checkout@v4 - name: Setup Python + if: steps.gate.outputs.skip != 'true' uses: actions/setup-python@v5 with: python-version: ${{ matrix.py }} - name: Free up disk space (Linux only) - if: runner.os == 'Linux' + if: runner.os == 'Linux' && steps.gate.outputs.skip != 'true' run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc sudo apt-get clean sudo rm -rf ~/.cache/pip /tmp/* /var/tmp/* - name: Install pufferlib (Linux) - if: runner.os == 'Linux' + if: runner.os == 'Linux' && steps.gate.outputs.skip != 'true' run: | python -m pip install -U pip "setuptools<81" wheel numpy # CPU-only torch. The CUDA stack is ~3 GB of wheels and no longer @@ -48,7 +66,7 @@ jobs: pip install -e . --no-build-isolation - name: Install pufferlib (macOS) - if: runner.os != 'Linux' + if: runner.os != 'Linux' && steps.gate.outputs.skip != 'true' run: | python -m pip install -U pip # Deliberately a plain install: macOS wheels are already CPU-only, so