Skip to content
111 changes: 26 additions & 85 deletions .github/workflows/ci.yaml
Comment thread
LusterSourav marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -420,6 +360,7 @@ jobs:
name: success
runs-on: ubuntu-26.04
needs:
- calculate_vars
- style_check
- test_tier1
- test_tier2
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ target
*~
# Used by libc-util
.libc-backports
ci/__pycache__/
187 changes: 187 additions & 0 deletions ci/ci-util.py

@tgross35 tgross35 Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename ci-util.py to match compiler-builtins

View changes since the review

Original file line number Diff line number Diff line change
@@ -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_<tier>_matrix=<json>` 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
<https://doc.rust-lang.org/rustc/platform-support.html>.
"""

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_<job>_matrix=<json>` 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>

COMMAND:
generate-matrix
Print the test matrix for each CI job as `test_<job>_matrix=<json>`."""
)
case _:
print(f"error: unknown command {sys.argv[1:]}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()