diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 95ed6a855..690cdc89b 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -16,6 +16,27 @@ env: BENCHMARK_RUSTC: nightly-2025-01-16 # Pin the toolchain for reproducable results jobs: + # Determine which tests should be run based on changed files. + calculate_vars: + name: Calculate workflow variables + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + outputs: + extensive_matrix: ${{ steps.script.outputs.extensive_matrix }} + may_skip_libm_ci: ${{ steps.script.outputs.may_skip_libm_ci }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 500 + - name: Fetch pull request ref + run: git fetch origin "$GITHUB_REF:$GITHUB_REF" + if: github.event_name == 'pull_request' + - run: python3 ci/ci-util.py generate-matrix >> "$GITHUB_OUTPUT" + id: script + test: name: Build and test timeout-minutes: 60 @@ -78,9 +99,11 @@ jobs: os: windows-2025 channel: nightly-x86_64-gnu runs-on: ${{ matrix.os }} + needs: [calculate_vars] env: BUILD_ONLY: ${{ matrix.build_only }} TEST_VERBATIM: ${{ matrix.test_verbatim }} + MAY_SKIP_LIBM_CI: ${{ needs.calculate_vars.outputs.may_skip_libm_ci }} steps: - name: Print runner information run: uname -a @@ -267,41 +290,21 @@ jobs: run: rustup set profile minimal && rustup default stable && rustup component add rustfmt - run: cargo fmt -- --check - # Determine which extensive tests should be run based on changed files. - calculate_extensive_matrix: - name: Calculate job matrix - runs-on: ubuntu-24.04 - timeout-minutes: 10 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - outputs: - matrix: ${{ steps.script.outputs.matrix }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 100 - - name: Fetch pull request ref - run: git fetch origin "$GITHUB_REF:$GITHUB_REF" - if: github.event_name == 'pull_request' - - run: python3 ci/ci-util.py generate-matrix >> "$GITHUB_OUTPUT" - id: script - extensive: name: Extensive tests for ${{ matrix.ty }} needs: # Wait on `clippy` so we have some confidence that the crate will build - clippy - - calculate_extensive_matrix + - calculate_vars runs-on: ubuntu-24.04 timeout-minutes: 240 # 4 hours strategy: matrix: - # Use the output from `calculate_extensive_matrix` to calculate the matrix + # Use the output from `calculate_vars` to create the matrix # FIXME: it would be better to run all jobs (i.e. all types) but mark those that # didn't change as skipped, rather than completely excluding the job. However, # this is not currently possible https://github.com/actions/runner/issues/1985. - include: ${{ fromJSON(needs.calculate_extensive_matrix.outputs.matrix).matrix }} + include: ${{ fromJSON(needs.calculate_vars.outputs.extensive_matrix).extensive_matrix }} env: TO_TEST: ${{ matrix.to_test }} steps: diff --git a/ci/ci-util.py b/ci/ci-util.py index d9e402d6b..7486d6b41 100755 --- a/ci/ci-util.py +++ b/ci/ci-util.py @@ -7,6 +7,7 @@ import json import os +import re import subprocess as sp import sys from dataclasses import dataclass @@ -68,6 +69,10 @@ "libm/src/math/arch/intrinsics.rs", ] +# libm PR CI takes a long time and doesn't need to run unless relevant files have been +# changed. Anything matching this regex pattern will trigger a run. +TRIGGER_LIBM_PR_CI = ".*(libm|musl).*" + TYPES = ["f16", "f32", "f64", "f128"] @@ -116,7 +121,6 @@ class FunctionDef(TypedDict): type: str -@dataclass class Context: gh_ref: str | None changed: list[Path] @@ -142,7 +146,7 @@ def _init_change_list(self): # the PR number), and sets this as `GITHUB_REF`. ref = self.gh_ref eprint(f"using ref `{ref}`") - if ref is None or "merge" not in ref: + if not self.is_pr(): # If the ref is not for `merge` then we are not in PR CI eprint("No diff available for ref") return @@ -170,6 +174,10 @@ def _init_change_list(self): ) self.changed = [Path(p) for p in textlist.splitlines()] + def is_pr(self) -> bool: + """Check if we are looking at a PR rather than a push.""" + return self.gh_ref is not None and "merge" in self.gh_ref + @staticmethod def _ignore_file(fname: str) -> bool: return any(fname.startswith(pfx) for pfx in IGNORE_FILES) @@ -196,7 +204,16 @@ def changed_routines(self) -> dict[str, list[str]]: return ret - def make_workflow_output(self) -> str: + def may_skip_libm_ci(self) -> bool: + """If this is a PR and no libm files were changed, allow skipping libm + jobs.""" + + if self.is_pr(): + return all(not re.match(TRIGGER_LIBM_PR_CI, str(f)) for f in self.changed) + + return False + + def emit_workflow_output(self): """Create a JSON object a list items for each type's changed files, if any did change, and the routines that were affected by the change. """ @@ -216,9 +233,10 @@ def make_workflow_output(self) -> str: eprint("Skipping all extensive tests") changed = self.changed_routines() - ret = [] + matrix = [] total_to_test = 0 + # Figure out which extensive tests need to run for ty in TYPES: ty_changed = changed.get(ty, []) ty_to_test = [] if skip_tests else ty_changed @@ -230,9 +248,14 @@ def make_workflow_output(self) -> str: "to_test": ",".join(ty_to_test), } - ret.append(item) - output = json.dumps({"matrix": ret}, separators=(",", ":")) - eprint(f"output: {output}") + matrix.append(item) + + ext_matrix = json.dumps({"extensive_matrix": matrix}, separators=(",", ":")) + may_skip = str(self.may_skip_libm_ci()).lower() + print(f"extensive_matrix={ext_matrix}") + print(f"may_skip_libm_ci={may_skip}") + eprint(f"extensive_matrix={ext_matrix}") + eprint(f"may_skip_libm_ci={may_skip}") eprint(f"total extensive tests: {total_to_test}") if error_on_many_tests and total_to_test > MANY_EXTENSIVE_THRESHOLD: @@ -242,8 +265,6 @@ def make_workflow_output(self) -> str: ) exit(1) - return output - def locate_baseline(flags: list[str]) -> None: """Find the most recent baseline from CI, download it if specified. @@ -398,8 +419,7 @@ def main(): match sys.argv[1:]: case ["generate-matrix"]: ctx = Context() - output = ctx.make_workflow_output() - print(f"matrix={output}") + ctx.emit_workflow_output() case ["locate-baseline", *flags]: locate_baseline(flags) case ["check-regressions", *args]: diff --git a/ci/run-docker.sh b/ci/run-docker.sh index c4d3af55d..58838fadf 100755 --- a/ci/run-docker.sh +++ b/ci/run-docker.sh @@ -77,6 +77,7 @@ run() { -e CI \ -e CARGO_TARGET_DIR=/builtins-target \ -e CARGO_TERM_COLOR \ + -e MAY_SKIP_LIBM_CI \ -e RUSTFLAGS \ -e RUST_BACKTRACE \ -e RUST_COMPILER_RT_ROOT \ diff --git a/ci/run.sh b/ci/run.sh index 0ad99da19..68d13c130 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -174,6 +174,14 @@ done # Test libm +# Make sure a simple build works +cargo check -p libm --no-default-features --target "$target" + +if [ "${MAY_SKIP_LIBM_CI:-}" = "true" ]; then + echo "skipping libm PR CI" + exit +fi + mflags=() # We enumerate features manually. @@ -226,9 +234,6 @@ case "$target" in *windows-gnu) mflags+=(--exclude libm-macros) ;; esac -# Make sure a simple build works -cargo check -p libm --no-default-features --target "$target" - if [ "${BUILD_ONLY:-}" = "1" ]; then # If we are on targets that can't run tests, verify that we can build. cmd=(cargo build --target "$target" --package libm)