This is an automated email from the ASF dual-hosted git repository. hubcio pushed a commit to branch ci/path-gate-coverage-baseline in repository https://gitbox.apache.org/repos/asf/iggy.git
commit e1bd5060d3e0cc80c51f40d3d18a30a043659bf2 Author: Hubert Gruszecki <[email protected]> AuthorDate: Wed Aug 5 13:10:38 2026 +0200 ci: skip unaffected language jobs in coverage baseline Every master push ran all 7 language coverage suites plus the macOS cache warm (~55 runner-minutes) even when the push touched a single SDK or only docs. Every Codecov flag has carryforward enabled and all statuses are informational, so re-measuring untouched languages re-uploads identical data. A detect job now diffs the push range and gates each job on the paths feeding its Codecov flag; untouched flags carry forward their previous master report. The differ fails open to a full run on any uncertainty (initial push, force push, CI or tooling changes), and a new workflow_dispatch trigger forces a full baseline on demand. The macOS cache warm follows the rust gate since that cache only goes stale when Rust sources change. --- .github/workflows/coverage-baseline.yml | 69 +++++++++++++++++++++-- scripts/ci/coverage-baseline-affected.sh | 96 ++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage-baseline.yml b/.github/workflows/coverage-baseline.yml index 35aab1163..b23d314c6 100644 --- a/.github/workflows/coverage-baseline.yml +++ b/.github/workflows/coverage-baseline.yml @@ -15,29 +15,73 @@ # specific language governing permissions and limitations # under the License. -# Full coverage baseline for all 7 languages (Rust, Java, C#, Python, -# PHP, Node, Go). Runs on every push to master so Codecov has complete data -# for carryforward on PR builds where only a subset of SDKs is tested. +# Coverage baseline for all 7 languages (Rust, Java, C#, Python, PHP, +# Node, Go). Runs on push to master so Codecov has data on master SHAs for +# carryforward on PR builds where only a subset of SDKs is tested. +# +# Jobs are path-gated: a language job runs only when the paths feeding its +# Codecov flag changed in the push (scripts/ci/coverage-baseline-affected.sh). +# Untouched flags carry forward their previous master report, which stays +# accurate because their sources did not change. workflow_dispatch forces a +# full run (manual backfill). name: Coverage baseline on: push: branches: [master] + workflow_dispatch: permissions: contents: read +# Per-event groups so a manual backfill run is not cancelled by the next +# merge push (and vice versa). concurrency: - group: coverage-baseline-${{ github.ref }} + group: coverage-baseline-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true env: IGGY_CI_BUILD: true jobs: + detect: + name: Detect affected coverage jobs + # workflow_dispatch offers a branch picker, but baseline uploads and + # cache warms only make sense for master. Skipping detect skips every + # downstream job. + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + rust: ${{ steps.affected.outputs.rust }} + java: ${{ steps.affected.outputs.java }} + csharp: ${{ steps.affected.outputs.csharp }} + python: ${{ steps.affected.outputs.python }} + php: ${{ steps.affected.outputs.php }} + node: ${{ steps.affected.outputs.node }} + go: ${{ steps.affected.outputs.go }} + steps: + - uses: actions/[email protected] + with: + # The gate diffs against the pre-push tip, which a shallow clone + # does not have. + fetch-depth: 0 + + - name: Compute affected gates + id: affected + # github.event.before is empty on workflow_dispatch; the script + # fails open to all-true there. + run: >- + scripts/ci/coverage-baseline-affected.sh + "${{ github.event.before }}" "${{ github.sha }}" + >> "$GITHUB_OUTPUT" + shell: bash + rust-coverage: name: Rust coverage baseline + needs: detect + if: needs.detect.outputs.rust == 'true' runs-on: ubuntu-latest timeout-minutes: 45 steps: @@ -108,6 +152,8 @@ jobs: java-coverage: name: Java coverage baseline + needs: detect + if: needs.detect.outputs.java == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -151,6 +197,8 @@ jobs: csharp-coverage: name: C# coverage baseline + needs: detect + if: needs.detect.outputs.csharp == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -223,6 +271,8 @@ jobs: python-coverage: name: Python coverage baseline + needs: detect + if: needs.detect.outputs.python == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -316,6 +366,8 @@ jobs: php-coverage: name: PHP coverage baseline + needs: detect + if: needs.detect.outputs.php == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -337,6 +389,8 @@ jobs: node-coverage: name: Node coverage baseline + needs: detect + if: needs.detect.outputs.node == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -404,6 +458,8 @@ jobs: go-coverage: name: Go coverage baseline + needs: detect + if: needs.detect.outputs.go == 'true' runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -478,6 +534,11 @@ jobs: warm-cache-macos: name: Warm macOS cache + needs: detect + # Gated on the rust key: the cache goes stale only when Rust sources + # change, and the Swatinem key is Cargo.lock-based, so skipping the save + # on non-Rust pushes leaves the previous cache valid. + if: needs.detect.outputs.rust == 'true' runs-on: macos-14 timeout-minutes: 30 steps: diff --git a/scripts/ci/coverage-baseline-affected.sh b/scripts/ci/coverage-baseline-affected.sh new file mode 100755 index 000000000..60cc3bd3f --- /dev/null +++ b/scripts/ci/coverage-baseline-affected.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +set -euo pipefail + +# Decide which coverage-baseline jobs a master push needs. Every Codecov flag +# has carryforward enabled, so a language whose sources did not change keeps +# serving its previous master report; only changed surfaces are re-measured. +# +# Fail-open: on any uncertainty (initial push, force push, unknown base) emit +# true for every gate so a needed refresh is never skipped. workflow_dispatch +# passes an empty base and lands on that path by design (manual full backfill). +# +# Usage: coverage-baseline-affected.sh <base-sha> <head-sha> +# Output: one "<gate>=true|false" line per gate on stdout, shaped for +# appending to $GITHUB_OUTPUT. Diagnostics go to stderr. + +BASE="${1:-}" +HEAD="${2:-}" +ZERO="0000000000000000000000000000000000000000" +GATES=(rust java csharp python php node go) + +emit_all() { + local gate + for gate in "${GATES[@]}"; do + echo "${gate}=true" + done +} + +if [[ -z "$HEAD" ]]; then + echo "usage: $0 <base-sha> <head-sha>" >&2 + exit 2 +fi + +# Unusable base: initial push, force push, workflow_dispatch (empty base), +# or a commit not in history. +if [[ -z "$BASE" || "$BASE" == "$ZERO" ]] || ! git cat-file -e "${BASE}^{commit}" 2>/dev/null; then + echo "coverage-gate: unusable base '${BASE:-<empty>}', running all jobs" >&2 + emit_all + exit 0 +fi + +# CI plumbing changes how every job builds and measures; re-baseline all. +if ! git diff --quiet "$BASE" "$HEAD" -- .github scripts codecov.yml; then + echo "coverage-gate: CI/tooling change, running all jobs" >&2 + emit_all + exit 0 +fi + +# A git failure here (exit > 1, e.g. corrupt object) reads as "changed", +# falling open for that gate. +changed() { + ! git diff --quiet "$BASE" "$HEAD" -- "$@" +} + +# GATES is the single source for both this loop and emit_all, so a gate can +# never be present on one path and missing on the other. A GATES entry +# without a pathspec arm fails loud instead of reusing the previous +# iteration's paths. +for gate in "${GATES[@]}"; do + case "$gate" in + rust) paths=(core Cargo.toml Cargo.lock rust-toolchain.toml .cargo) ;; + java) paths=(foreign/java) ;; + csharp) paths=(foreign/csharp) ;; + python) paths=(foreign/python) ;; + php) paths=(foreign/php) ;; + node) paths=(foreign/node) ;; + # The go job also runs the bdd/go suite with foreign/go in -coverpkg. + go) paths=(foreign/go bdd/go) ;; + *) + echo "coverage-gate: no pathspecs defined for gate '$gate'" >&2 + exit 1 + ;; + esac + if changed "${paths[@]}"; then + echo "${gate}=true" + echo "coverage-gate: ${gate} affected" >&2 + else + echo "${gate}=false" + fi +done
