From aef8b460ad0b0fdf3b8581731b5c2b5fe4b7999e Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:36:38 +0000 Subject: [PATCH 1/3] Test Bazel 7 and 8 across the supported CI platforms --- .github/workflows/main.yml | 9 +++++++-- docs/infrastructure.md | 19 +++++++++++++++---- tools/infrastructure_test.py | 13 +++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96b92b4..ff2f9fd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,12 +47,17 @@ jobs: if: always() test: + name: test (${{ matrix.os }}, Bazel ${{ matrix.bazel || 'pinned' }}) needs: pre-commit strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] + # Empty selects .bazelversion in both the checkout and consumer workspaces. + bazel: ["7.7.2", "8.8.2", ""] runs-on: ${{ matrix.os }} + env: + USE_BAZEL_VERSION: ${{ matrix.bazel }} steps: - uses: actions/checkout@v7 - name: Configure report capture @@ -65,7 +70,7 @@ jobs: cache-save: ${{ github.ref == 'refs/heads/main' }} bazelisk-cache: true # Store build cache per workflow. - disk-cache: ${{ github.workflow }} + disk-cache: ${{ github.workflow }}-${{ matrix.bazel || 'pinned' }} # Share repository cache between workflows. repository-cache: true - name: Built & Test @@ -103,7 +108,7 @@ jobs: if: always() uses: actions/upload-artifact@v7 with: - name: test-results-${{ matrix.os }} + name: test-results-${{ matrix.os }}-bazel-${{ matrix.bazel || 'pinned' }} path: ${{ runner.temp }}/test-results if-no-files-found: warn retention-days: 7 diff --git a/docs/infrastructure.md b/docs/infrastructure.md index 5397318..c3ab86a 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -11,6 +11,14 @@ Main and numeric release tags run push validation; branches run in pull-request including forks, without duplicating the complete matrix on each push. Only superseded PR runs are cancelled. The final gate retains release-site tests, pre-commit, and every test-matrix cell. +Every pull request and main/tag run tests Linux, macOS, and Windows with Bazel 7.7.2, +8.8.2, and the version in `.bazelversion`. The empty version entry in the matrix +lets Bazelisk read that file, avoiding a second copy of the default version. +Job-level `USE_BAZEL_VERSION` applies to root tests, report tooling, and external +consumer workspaces. Every cell runs both root runfiles modes and the consumer +checks; Windows additionally verifies the managed runtime with Unix tools removed +from PATH. Job names, disk caches, and artifacts distinguish each version. + The pinned setup-bazel action retains native platform-specific cache paths, including Windows where supported. In the Test workflow, PRs and release tags restore caches; only main saves them. The separate upstream Release build disables cache mounting because it cannot restrict writes @@ -27,7 +35,7 @@ may briefly lag post-job cache uploads; verify later runs before claiming saving ### Test diagnostics -Every test-matrix job retains a `test-results-OS` Actions artifact for seven days, +Every test-matrix job retains a `test-results-OS-bazel-VERSION` Actions artifact for seven days, including on failure. The workflow and consumer scripts execute ordinary Bazel commands with `--build_event_json_file`. Collection runs separately through the Bazel-built `//tools:ci_reports` tool, which copies the `test.log` and `test.xml` @@ -39,8 +47,10 @@ results in `bazel-testlogs` are not collected. Missing local outputs and malform build events are reported as collection errors; remote-only output URIs are not downloaded by this local-CI collector. -An always-run step writes per-invocation JUnit counts and limited failure/error -excerpts to the job summary. It first collects pending root results, including +An always-run step separates regular test counts, intentional failure checks, and +builds in the job summary. Counts use the final attempt of each run/shard; earlier +retries remain in the artifact. Intentional fixture diagnostics are collapsed and +excluded from ordinary failure/error counts. It first collects pending root results, including commands that failed before an explicit collection call. Consumer cleanup does the same before deleting its workspace and retains negative-analysis command logs. Build/test status comes from the build events' finished event; @@ -50,7 +60,8 @@ the program's exit status. Event files under an `expected/` directory identify intentional failure commands. Console output remains in the native Actions step logs. It counts actual cases rather than trusting aggregate XML attributes, labels intentionally failing fixture commands, and fails on -unexpected command outcomes, collection errors, or malformed JUnit XML. The +unexpected command outcomes, final-attempt Bazel/JUnit failures or errors, +collection errors, or malformed JUnit XML. The consumer scripts still verify each expected failure's precise behavior and print captured negative-test logs if those checks fail. A missing report is shown as such, leaving the original Bazel command status authoritative. diff --git a/tools/infrastructure_test.py b/tools/infrastructure_test.py index 103883d..ee47ced 100644 --- a/tools/infrastructure_test.py +++ b/tools/infrastructure_test.py @@ -13,15 +13,24 @@ def test_prs_restore_but_only_main_writes_caches(self): self.assertIn("cache-save: ${{ github.ref == 'refs/heads/main' }}", workflow) self.assertIn("cancel-in-progress: ${{ github.event_name == 'pull_request' }}", workflow) self.assertIn('repository-cache: true', workflow) - self.assertIn('disk-cache: ${{ github.workflow }}', workflow) + self.assertIn("disk-cache: ${{ github.workflow }}-${{ matrix.bazel || 'pinned' }}", workflow) self.assertIn('needs: [release-site-tests, pre-commit, test]', workflow) self.assertIn('os: [ubuntu-latest, macos-latest, windows-latest]', workflow) + def test_every_platform_tests_supported_bazel_versions(self): + workflow = (Path(__file__).parents[1] / '.github/workflows/main.yml').read_text() + self.assertIn('bazel: ["7.7.2", "8.8.2", ""]', workflow) + # Job-level selection must also reach external consumer workspaces and + # Windows commands, rather than being limited to a single root test step. + self.assertIn(' env:\n USE_BAZEL_VERSION: ${{ matrix.bazel }}', workflow) + self.assertIn("name: test (${{ matrix.os }}, Bazel ${{ matrix.bazel || 'pinned' }})", workflow) + self.assertIn("name: test-results-${{ matrix.os }}-bazel-${{ matrix.bazel || 'pinned' }}", workflow) + def test_test_diagnostics_survive_failed_steps(self): workflow = (Path(__file__).parents[1] / '.github/workflows/main.yml').read_text() self.assertIn('name: Summarize JUnit results\n if: always()', workflow) self.assertIn('name: Retain test diagnostics\n if: always()', workflow) - self.assertIn('name: test-results-${{ matrix.os }}', workflow) + self.assertIn("name: test-results-${{ matrix.os }}-bazel-${{ matrix.bazel || 'pinned' }}", workflow) self.assertIn('retention-days: 7', workflow) self.assertIn('bazel test //... --build_event_json_file=', workflow) self.assertIn('bazel test --enable_runfiles=no //... --build_event_json_file=', workflow) From 600cec3d7328a5b73b5801f46ebd0d7d766605f4 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:39:35 +0000 Subject: [PATCH 2/3] Use pre-Bazel-9 execution constraints in Bazel 8 consumer tests --- bashtest/README.md | 6 ++++-- bashtest/tests/tools_consumer_test.sh | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bashtest/README.md b/bashtest/README.md index 40c0ef8..ffe8fa3 100644 --- a/bashtest/README.md +++ b/bashtest/README.md @@ -165,8 +165,10 @@ bashtest( The script can invoke `"${GENERATOR}" --output "${TEST_TMPDIR}/result"`. Tools must be executable Bazel targets; their runtime runfiles are included. -This uses the test execution group, including `exec_group_compatible_with` on Bazel 8 and newer, so -cross-compilation builds helpers for the platform running the test. `data` +This uses the test execution group, so cross-compilation builds helpers for the +platform running the test. Set `exec_group_compatible_with = {"test": [...]}` on +Bazel 9 and newer; on Bazel 7 and 8, use `exec_compatible_with = [...]`, which also +constrains the test group on those versions. `data` continues to use the target configuration. Do not put the same target in both `data` and `tools`. Multiple names may refer to the same tool. diff --git a/bashtest/tests/tools_consumer_test.sh b/bashtest/tests/tools_consumer_test.sh index 0cf8043..b75d522 100755 --- a/bashtest/tests/tools_consumer_test.sh +++ b/bashtest/tests/tools_consumer_test.sh @@ -93,11 +93,13 @@ bashtest( tags = ["manual"], ) EOF -# Bazel 7 predates per-group constraints; still verify execution-platform tools. -if [[ "$(bazel --version)" == 'bazel 7.'* ]]; then - sed 's/exec_group_compatible_with = {"test": \[":test_role"\]}/exec_compatible_with = [":test_role"]/' BUILD.bazel > BUILD.tmp - mv BUILD.tmp BUILD.bazel -fi +# Before Bazel 9, default execution constraints also apply to the test group. +case "$(bazel --version)" in + 'bazel 7.'*|'bazel 8.'*) + sed 's/exec_group_compatible_with = {"test": \[":test_role"\]}/exec_compatible_with = [":test_role"]/' BUILD.bazel > BUILD.tmp + mv BUILD.tmp BUILD.bazel + ;; +esac cat > consumer.sh <<'EOF' #!/usr/bin/env bash set -euo pipefail From c3065c85c53a0e6d25e3ed3f7271cdfd439116f5 Mon Sep 17 00:00:00 2001 From: helly25 <6420169+helly25@users.noreply.github.com> Date: Mon, 26 Oct 2026 00:50:22 +0000 Subject: [PATCH 3/3] Use downloaded Bash for native Bazel 7 helper launchers --- bashtest/tests/windows_runtime_test.ps1 | 29 ++++++++++++++++++------- docs/infrastructure.md | 5 ++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bashtest/tests/windows_runtime_test.ps1 b/bashtest/tests/windows_runtime_test.ps1 index 1ffb39e..c92139c 100644 --- a/bashtest/tests/windows_runtime_test.ps1 +++ b/bashtest/tests/windows_runtime_test.ps1 @@ -15,23 +15,36 @@ foreach ($name in @('bash', 'grep', 'sed', 'diff')) { # collects a failed command's outputs too. $reports = if ($env:BASHTEST_CI_REPORTS) { $env:BASHTEST_CI_REPORTS } else { Join-Path $env:TEMP ([guid]::NewGuid().ToString()) } New-Item -ItemType Directory -Path $reports -Force | Out-Null -& $bazel test //... "--build_event_json_file=$reports/root-default.json" +# Resolve the downloaded source, not an execroot link pruned by later builds. +$outputBase = & $bazel info output_base +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +$bashFile = & $bazel cquery '@bashtest_windows_runtime//:usr/bin/bash.exe' --output=files +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +$bash = Join-Path $outputBase $bashFile +$version = & $bazel --version +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +$testShell = @() +if ($version -match '^bazel 7\.') { + # Native Bazel 7 sh_binary fixtures do not resolve rules_shell toolchains. + # Point their embedded interpreter at our downloaded runtime, never WSL. + $testShell = @("--shell_executable=$bash") +} +& $bazel test //... @testShell "--build_event_json_file=$reports/root-default.json" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } & $bazel run //tools:ci_reports -- collect --events "$reports/root-default.json" --output "$reports/root-default" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -& $bazel test --enable_runfiles=no //... "--build_event_json_file=$reports/root-manifest.json" +& $bazel test --enable_runfiles=no //... @testShell "--build_event_json_file=$reports/root-manifest.json" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } & $bazel run //tools:ci_reports -- collect --events "$reports/root-manifest.json" --output "$reports/root-manifest" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +# No shell override: bashtest itself must bootstrap its declared runtime. & $bazel run --enable_runfiles=no //bashtest/tests:runtime_test "--build_event_json_file=$reports/root-runtime.json" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Drive the existing external-consumer tests with the downloaded interpreter. -# Resolve the downloaded source, not an execroot link pruned by later builds. -$outputBase = & $bazel info output_base -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$bashFile = & $bazel cquery '@bashtest_windows_runtime//:usr/bin/bash.exe' --output=files -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -$bash = Join-Path $outputBase $bashFile +if ($version -match '^bazel 7\.') { + # New consumer servers need the same configuration for native helper tools. + $env:BAZEL_SH = $bash +} $env:PATH = "$(Split-Path $bash);$(Split-Path $bazel);$env:PATH" & $bash bashtest/tests/external_consumer_test.sh if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/docs/infrastructure.md b/docs/infrastructure.md index c3ab86a..d12d31b 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -17,7 +17,10 @@ lets Bazelisk read that file, avoiding a second copy of the default version. Job-level `USE_BAZEL_VERSION` applies to root tests, report tooling, and external consumer workspaces. Every cell runs both root runfiles modes and the consumer checks; Windows additionally verifies the managed runtime with Unix tools removed -from PATH. Job names, disk caches, and artifacts distinguish each version. +from PATH. Native Bazel 7 `sh_binary` fixtures need separate shell configuration: +the Windows driver points those helper launchers at the downloaded Bash. The +standalone bashtest runtime invocation still runs without a shell override or +`BAZEL_SH`. Job names, disk caches, and artifacts distinguish each version. The pinned setup-bazel action retains native platform-specific cache paths, including Windows where supported. In the Test workflow, PRs and release tags restore caches; only main saves them.