diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44dea0567271..5102040eda11 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,25 @@ defaults: shell: bash jobs: + # generates the full test matrix for each tier, same for every run + calculate_vars: + name: Calculate CI variables + runs-on: ubuntu-26.04 + timeout-minutes: 5 + outputs: + test_tier1_matrix: ${{ steps.vars.outputs.test_tier1_matrix }} + test_tier2_matrix: ${{ steps.vars.outputs.test_tier2_matrix }} + test_tier2_vm_matrix: ${{ steps.vars.outputs.test_tier2_vm_matrix }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Compute test matrices + id: vars + run: | + set -eo pipefail + python3 ci/ci-util.py generate-matrix | tee "$GITHUB_OUTPUT" + style_check: name: Style check runs-on: ubuntu-26.04 @@ -129,25 +148,10 @@ jobs: test_tier1: name: Test tier1 + needs: calculate_vars strategy: matrix: - include: - - target: aarch64-apple-darwin - os: macos-26 - - target: aarch64-pc-windows-msvc - os: windows-11-arm - - target: aarch64-unknown-linux-gnu - os: ubuntu-26.04-arm - - target: i686-pc-windows-gnu - os: windows-2025 - - target: i686-pc-windows-msvc - os: windows-2025 - - target: i686-unknown-linux-gnu - - target: x86_64-pc-windows-gnu - os: windows-2025 - - target: x86_64-pc-windows-msvc - os: windows-2025 - - target: x86_64-unknown-linux-gnu + include: ${{ fromJSON(needs.calculate_vars.outputs.test_tier1_matrix) }} runs-on: ${{ matrix.os && matrix.os || 'ubuntu-26.04' }} timeout-minutes: 25 env: @@ -189,67 +193,12 @@ jobs: test_tier2: name: Test tier2 - needs: [test_tier1, style_check] + needs: [test_tier1, calculate_vars, style_check] strategy: fail-fast: true max-parallel: 16 matrix: - include: - - target: aarch64-linux-android - - target: aarch64-unknown-linux-musl - - target: aarch64-unknown-linux-musl - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - - target: arm-linux-androideabi - - target: arm-unknown-linux-gnueabihf - - target: arm-unknown-linux-musleabihf - - target: arm-unknown-linux-musleabihf - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - # FIXME(#4297): Disabled due to spurious failue - # - target: i686-linux-android - - target: i686-unknown-linux-musl - - target: i686-unknown-linux-musl - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - - target: loongarch64-unknown-linux-gnu - - target: loongarch64-unknown-linux-musl - - target: loongarch64-unknown-linux-musl - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - - target: powerpc64-unknown-linux-gnu - - target: powerpc64-unknown-linux-musl - - target: powerpc64-unknown-linux-musl - env: { RUST_LIBC_UNSTABLE_MUSL_V1_2: 1 } - artifact-tag: new-musl - - target: powerpc64le-unknown-linux-gnu - - target: powerpc64le-unknown-linux-musl - - target: powerpc64le-unknown-linux-musl - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - - target: riscv64gc-unknown-linux-gnu - - target: s390x-unknown-linux-gnu - - target: sparc64-unknown-linux-gnu - - target: wasm32-unknown-emscripten - - target: wasm32-wasip1 - - target: wasm32-wasip2 - - target: x86_64-apple-darwin - os: macos-26-intel - - target: x86_64-linux-android - # Keep in sync with the Android build pinned in ci/cuttlefish-setup.sh - artifact-tag: android17 - # FIXME: Exec format error (os error 8) - # - target: x86_64-unknown-linux-gnux32 - - target: x86_64-unknown-linux-musl - - target: x86_64-unknown-linux-musl - env: { TEST_MUSL_V1_2: 1 } - artifact-tag: new-musl - # FIXME: It seems some items in `src/unix/mod.rs` aren't defined on redox actually. - # - target: x86_64-unknown-redox - - # FIXME(ppc): SIGILL running tests, see - # https://github.com/rust-lang/libc/pull/4254#issuecomment-2636288713 - # - target: powerpc-unknown-linux-gnu + include: ${{ fromJSON(needs.calculate_vars.outputs.test_tier2_matrix) }} runs-on: ${{ matrix.os && matrix.os || 'ubuntu-26.04' }} timeout-minutes: 25 env: @@ -291,21 +240,12 @@ jobs: test_tier2_vm: name: Test tier2 VM - needs: [test_tier1, style_check] + needs: [test_tier1, calculate_vars, style_check] runs-on: ubuntu-latest strategy: fail-fast: true matrix: - include: - - release: "15.0" - target: i686-unknown-freebsd - - release: "14.4" - target: x86_64-unknown-freebsd - - release: "15.0" - target: x86_64-unknown-freebsd - - target: x86_64-pc-solaris - - target: x86_64-unknown-netbsd - - target: x86_64-unknown-illumos + include: ${{ fromJSON(needs.calculate_vars.outputs.test_tier2_vm_matrix) }} timeout-minutes: 25 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -420,6 +360,7 @@ jobs: name: success runs-on: ubuntu-26.04 needs: + - calculate_vars - style_check - test_tier1 - test_tier2 diff --git a/.gitignore b/.gitignore index 8ee6c810f3da..787c93df98f2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target *~ # Used by libc-util .libc-backports +ci/__pycache__/ diff --git a/ci/ci-util.py b/ci/ci-util.py new file mode 100644 index 000000000000..0cffb76085eb --- /dev/null +++ b/ci/ci-util.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +"""Utilities for CI. + +Generate the test matrices for the CI workflow as GitHub Actions output. +Each tier is printed on its own line as `test__matrix=` so the +workflow can feed it straight into a `matrix: include` block. Merge queues, +schedules and manual runs always get every target; there is no file +detection yet. +""" + +import json +import sys +from dataclasses import dataclass, field +from enum import IntEnum, StrEnum + + +class Tier(IntEnum): + """Rust tier of the target, as defined in + . + """ + + T1 = 1 + T2 = 2 + + +class CiJob(StrEnum): + """Which CI job the target is tested by.""" + + T1 = "tier1" + T2 = "tier2" + T2_VM = "tier2_vm" + + +@dataclass(frozen=True) +class TestTarget: + """One row of the test matrix. + + The fields map straight to matrix variables in ci.yaml; `os` defaults to + the ubuntu-26.04 runner. + """ + + name: str # rust target triple + #: runner OS + os: str = "ubuntu-26.04" + tier: Tier = Tier.T1 + vm: bool = False + release: str | None = None # OS version for the VM jobs + env: dict[str, str | int] = field(default_factory=dict) + artifact_tag: str | None = None + + def ci_job(self) -> CiJob: + """The CI job this target runs in, based on its tier and VM-ness.""" + if self.tier is Tier.T1: + return CiJob.T1 + return CiJob.T2_VM if self.vm else CiJob.T2 + + +# the full list of matrix rows, grouped by tier for readability +TARGETS: list[TestTarget] = [ + # tier 1 + TestTarget("aarch64-apple-darwin", os="macos-26"), + TestTarget("aarch64-pc-windows-msvc", os="windows-11-arm"), + TestTarget("aarch64-unknown-linux-gnu", os="ubuntu-26.04-arm"), + TestTarget("i686-pc-windows-gnu", os="windows-2025"), + TestTarget("i686-pc-windows-msvc", os="windows-2025"), + TestTarget("i686-unknown-linux-gnu"), + TestTarget("x86_64-pc-windows-gnu", os="windows-2025"), + TestTarget("x86_64-pc-windows-msvc", os="windows-2025"), + TestTarget("x86_64-unknown-linux-gnu"), + # tier 2 + TestTarget("aarch64-linux-android", tier=Tier.T2), + TestTarget("aarch64-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "aarch64-unknown-linux-musl", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + TestTarget("arm-linux-androideabi", tier=Tier.T2), + TestTarget("arm-unknown-linux-gnueabihf", tier=Tier.T2), + TestTarget("arm-unknown-linux-musleabihf", tier=Tier.T2), + TestTarget( + "arm-unknown-linux-musleabihf", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + # FIXME(#4297): spurious test failures, keep disabled + # TestTarget("i686-linux-android", tier=Tier.T2), + TestTarget("i686-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "i686-unknown-linux-musl", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + TestTarget("loongarch64-unknown-linux-gnu", tier=Tier.T2), + TestTarget("loongarch64-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "loongarch64-unknown-linux-musl", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + TestTarget("powerpc64-unknown-linux-gnu", tier=Tier.T2), + TestTarget("powerpc64-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "powerpc64-unknown-linux-musl", + tier=Tier.T2, + env={"RUST_LIBC_UNSTABLE_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + TestTarget("powerpc64le-unknown-linux-gnu", tier=Tier.T2), + TestTarget("powerpc64le-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "powerpc64le-unknown-linux-musl", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + TestTarget("riscv64gc-unknown-linux-gnu", tier=Tier.T2), + TestTarget("s390x-unknown-linux-gnu", tier=Tier.T2), + TestTarget("sparc64-unknown-linux-gnu", tier=Tier.T2), + TestTarget("wasm32-unknown-emscripten", tier=Tier.T2), + TestTarget("wasm32-wasip1", tier=Tier.T2), + TestTarget("wasm32-wasip2", tier=Tier.T2), + TestTarget("x86_64-apple-darwin", os="macos-26-intel", tier=Tier.T2), + # keep in sync with the android build pinned in ci/cuttlefish-setup.sh + TestTarget("x86_64-linux-android", tier=Tier.T2, artifact_tag="android17"), + # FIXME: fails to run, exec format error (os error 8) + # TestTarget("x86_64-unknown-linux-gnux32", tier=Tier.T2), + TestTarget("x86_64-unknown-linux-musl", tier=Tier.T2), + TestTarget( + "x86_64-unknown-linux-musl", + tier=Tier.T2, + env={"TEST_MUSL_V1_2": 1}, + artifact_tag="new-musl", + ), + # FIXME: some items in `src/unix/mod.rs` aren't defined on redox yet + # TestTarget("x86_64-unknown-redox", tier=Tier.T2), + # FIXME(ppc): SIGILL running tests, see rust-lang/libc#4254 + # TestTarget("powerpc-unknown-linux-gnu", tier=Tier.T2), + # tier 2, VM only + TestTarget("i686-unknown-freebsd", tier=Tier.T2, vm=True, release="15.0"), + TestTarget("x86_64-unknown-freebsd", tier=Tier.T2, vm=True, release="14.4"), + TestTarget("x86_64-unknown-freebsd", tier=Tier.T2, vm=True, release="15.0"), + TestTarget("x86_64-pc-solaris", tier=Tier.T2, vm=True), + TestTarget("x86_64-unknown-netbsd", tier=Tier.T2, vm=True), + TestTarget("x86_64-unknown-illumos", tier=Tier.T2, vm=True), +] + + +def emit_workflow_output() -> None: + """Print the test matrices, one `test__matrix=` line per job.""" + rows = {job: [] for job in CiJob} + for target in TARGETS: + row: dict[str, str | int] = { + "target": target.name, + "os": target.os, + "env": dict(target.env) or None, + "artifact-tag": target.artifact_tag, + "release": target.release, + } + rows[target.ci_job()].append({k: v for k, v in row.items() if v is not None}) + for job, targets in rows.items(): + print(f"test_{job.value}_matrix={json.dumps(targets)}") + + +def main() -> None: + match sys.argv[1:]: + case ["generate-matrix"]: + emit_workflow_output() + case ["--help" | "-h"] | []: + print( + """usage: ci/ci-util.py + +COMMAND: + generate-matrix + Print the test matrix for each CI job as `test__matrix=`.""" + ) + case _: + print(f"error: unknown command {sys.argv[1:]}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()