T-028 — first real EL0 userspace task (B6 wire-up): hello runs in EL0 + exits #167
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Tyrne CI — gate for every push and pull request. | |
| # | |
| # Goal: every hygiene check that a contributor is expected to pass | |
| # locally before opening a PR is also enforced here. A red CI blocks | |
| # merge. | |
| # | |
| # What actually gates merge (configured in GitHub branch protection): | |
| # - lint-and-host-test, kernel-build, host-stable-check (fast lane) | |
| # - miri (required, slow) | |
| # The `miri` job runs the host-test suite under Stacked Borrows; it is | |
| # slower (~1–2 min in practice on the current small suite; historically | |
| # budgeted at ~10–15 min) but a Miri regression is a hard stop. The | |
| # `coverage` job is INFORMATIONAL only (it sets `continue-on-error: true`) | |
| # and must NOT be added to the required-checks list until the post-T-011 | |
| # flip removes that flag — see docs/guides/ci.md §"Branch protection". | |
| # | |
| # Toolchain note: the kernel needs nightly (inline asm / lang items), | |
| # so the lint-and-host-test, kernel-build, and miri jobs all run the | |
| # pinned nightly ($NIGHTLY_PIN) — the same toolchain rust-toolchain.toml | |
| # selects for in-repo `cargo` invocations. A separate host-stable-check | |
| # job gives a genuine "host crates build clean on stable" signal. | |
| # | |
| # When this pipeline was born (2026-04-23, R6 retrospective work) the | |
| # kernel-build target was aarch64-unknown-none. Any time a new crate or | |
| # target lands, update the job matrix below. | |
| name: CI | |
| on: | |
| push: | |
| branches: [main, development] | |
| pull_request: | |
| branches: [main, development] | |
| # Cancel in-progress runs for the same branch — we only care about | |
| # the latest commit's verdict, not intermediate ones. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Least-privilege for the auto-provisioned GITHUB_TOKEN. This pipeline | |
| # only reads the repository and runs builds; it never writes refs, | |
| # publishes artifacts, or touches issues/PRs. Override per-job only if a | |
| # future job genuinely needs more — none do today. | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # NOTE: `-D warnings` is intentionally NOT set as a global RUSTFLAGS env | |
| # var. A process-wide RUSTFLAGS REPLACES (does not merge) the per-target | |
| # `[target.aarch64-unknown-none] rustflags` in .cargo/config.toml, which | |
| # would silently drop `panic=abort` + `force-frame-pointers=yes` from the | |
| # kernel build — CI would then compile a different ELF than a local build. | |
| # Deny-warnings is enforced instead by the `-- -D warnings` already passed | |
| # in the host-clippy / kernel-clippy aliases (.cargo/config.toml). | |
| # Pinned nightly for the kernel + miri + coverage jobs. Rolling nightly means a | |
| # miri/llvm-tools regression on the public channel breaks master | |
| # without any commit of ours being the cause. Update this pin | |
| # intentionally (open an issue citing the pin bump), not silently. | |
| # When bumping, also refresh docs/guides/ci.md's "Nightly pinning" | |
| # section. Last bumped: 2026-04-23 (initial pin). | |
| NIGHTLY_PIN: nightly-2026-01-15 | |
| jobs: | |
| # ─── Fast lane: fmt + clippy + host tests (pinned nightly) ────────────── | |
| # Expected wall time: ~2 min. Every PR must pass this before anything | |
| # else runs. Runs on the pinned nightly — `rust-toolchain.toml` overrides | |
| # `rustup default` for in-repo cargo anyway, so we select the pin | |
| # explicitly (matching the miri/coverage jobs) instead of pretending to | |
| # run on stable. A genuine stable signal lives in `host-stable-check`. | |
| lint-and-host-test: | |
| name: fmt + clippy + host tests (nightly) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| # CI only reads the repo; do not persist the token into local git | |
| # config (least-privilege; avoids credential reuse by later steps). | |
| persist-credentials: false | |
| - name: Install pinned nightly with rustfmt + clippy | |
| run: | | |
| rustup toolchain install $NIGHTLY_PIN --component rustfmt --component clippy --no-self-update | |
| rustup override set $NIGHTLY_PIN | |
| - name: Cache cargo registry and build | |
| uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| path: | | |
| ~/.cargo/bin | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ env.NIGHTLY_PIN }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-${{ env.NIGHTLY_PIN }}- | |
| - name: cargo fmt --check | |
| run: cargo +$NIGHTLY_PIN fmt --all -- --check | |
| - name: cargo host-clippy | |
| run: cargo +$NIGHTLY_PIN host-clippy | |
| - name: cargo host-test | |
| run: cargo +$NIGHTLY_PIN host-test | |
| # ─── Kernel build: aarch64-unknown-none ───────────────────────────────── | |
| # The bare-metal BSP cannot be built as part of the default workspace | |
| # test target (no_std + no_main). Build it explicitly here so the | |
| # kernel ELF and its dependencies stay compilable. | |
| kernel-build: | |
| name: aarch64-unknown-none kernel build (nightly) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| # CI only reads the repo; do not persist the token into local git | |
| # config (least-privilege; avoids credential reuse by later steps). | |
| persist-credentials: false | |
| - name: Install pinned nightly + aarch64 target | |
| run: | | |
| # llvm-tools-preview provides rust-objcopy for the userland build | |
| # step below (ADR-0039); it is pinned in rust-toolchain.toml but this | |
| # explicit install does not read that file, so name it here. | |
| rustup toolchain install $NIGHTLY_PIN --component clippy --component llvm-tools-preview --no-self-update | |
| rustup override set $NIGHTLY_PIN | |
| rustup target add aarch64-unknown-none --toolchain $NIGHTLY_PIN | |
| - name: Cache cargo registry and build | |
| uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| path: | | |
| ~/.cargo/bin | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-aarch64-${{ env.NIGHTLY_PIN }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-aarch64-${{ env.NIGHTLY_PIN }}- | |
| - name: build userland image (ADR-0039) | |
| # Produces userland/hello/hello.bin (rust-objcopy of the userland crate) | |
| # which the BSP embeds via include_bytes!; MUST run before kernel-build, | |
| # whose build.rs panics if the .bin is absent. | |
| run: tools/build-userland.sh | |
| - name: cargo kernel-build | |
| run: cargo +$NIGHTLY_PIN kernel-build | |
| - name: cargo kernel-clippy | |
| run: cargo +$NIGHTLY_PIN kernel-clippy | |
| # ─── Host stable check: host crates compile + test on stable ──────────── | |
| # A genuine "the host-buildable crates compile and pass tests on stable | |
| # Rust" gate. The kernel image itself needs nightly (inline asm / lang | |
| # items) so the bare-metal BSP is NOT built here — this job runs only the | |
| # workspace default-members (kernel, hal, test-hal), which carry no | |
| # nightly-only features today. If a host crate ever grows a `#![feature]`, | |
| # this job is the one that will (correctly) go red on stable. | |
| # | |
| # Deliberately build + test only — NOT clippy/fmt with `-D warnings`. | |
| # `clippy::pedantic` is `warn` workspace-wide and stable is a rolling | |
| # toolchain: a future stable release can add a pedantic lint that turns a | |
| # `-D warnings` gate red with no code change of ours. Lint/format | |
| # enforcement therefore lives only on the pinned-nightly jobs (which run | |
| # host-clippy + fmt), keeping this gate immune to upstream lint drift. | |
| host-stable-check: | |
| name: host crates on stable | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| # CI only reads the repo; do not persist the token into local git | |
| # config (least-privilege; avoids credential reuse by later steps). | |
| persist-credentials: false | |
| - name: Install Rust stable | |
| run: | | |
| rustup toolchain install stable --no-self-update | |
| - name: Cache cargo registry and build | |
| uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| path: | | |
| ~/.cargo/bin | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-stable-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-stable- | |
| # Explicit `+stable` bypasses the rust-toolchain.toml override so this | |
| # job exercises stable for real. `cargo build` / `host-test` operate on | |
| # default-members (host-buildable crates only); the bare-metal BSP is | |
| # never built here. No `-D warnings` lint/fmt step — see the job header. | |
| - name: cargo +stable build (host crates) | |
| run: cargo +stable build | |
| - name: cargo +stable host-test | |
| run: cargo +stable host-test | |
| # ─── Miri: aliasing validation ────────────────────────────────────────── | |
| # Runs the full host-test suite under Miri's Stacked Borrows checker | |
| # (see ADR-0021 / UNSAFE-2026-0014). Slower than the fast lane | |
| # (~1–2 min in practice; historically budgeted ~10–15 min) and requires | |
| # nightly, so it runs as its own job. A | |
| # Miri regression is a hard stop. | |
| miri: | |
| name: miri (Stacked Borrows) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| # CI only reads the repo; do not persist the token into local git | |
| # config (least-privilege; avoids credential reuse by later steps). | |
| persist-credentials: false | |
| - name: Install pinned nightly with miri | |
| run: | | |
| rustup toolchain install $NIGHTLY_PIN --component miri --no-self-update | |
| rustup override set $NIGHTLY_PIN | |
| - name: Cache cargo registry and miri target | |
| uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| path: | | |
| ~/.cargo/bin | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-miri-${{ env.NIGHTLY_PIN }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-miri-${{ env.NIGHTLY_PIN }}- | |
| - name: cargo miri setup | |
| run: cargo +$NIGHTLY_PIN miri setup | |
| - name: cargo miri test (excl. BSP) | |
| run: cargo +$NIGHTLY_PIN miri test --workspace --exclude tyrne-bsp-qemu-virt | |
| # ─── Coverage: informational ──────────────────────────────────────────── | |
| # Produces an lcov report. Does NOT fail on a coverage drop today | |
| # (no agreed floor yet); it exists to make trends visible. When the | |
| # workspace settles around its post-T-011 shape, flip this job to | |
| # enforce a floor (≥ 94 % regions or similar). | |
| coverage: | |
| name: coverage (informational) | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| # CI only reads the repo; do not persist the token into local git | |
| # config (least-privilege; avoids credential reuse by later steps). | |
| persist-credentials: false | |
| - name: Install pinned nightly with llvm-tools | |
| run: | | |
| rustup toolchain install $NIGHTLY_PIN --component llvm-tools-preview --no-self-update | |
| rustup override set $NIGHTLY_PIN | |
| - name: Install pinned cargo-llvm-cov via prebuilt-binary action | |
| # taiki-e/install-action downloads a prebuilt cargo-llvm-cov | |
| # binary instead of compiling from source. Two wins versus the | |
| # earlier `cargo install --locked`: (1) the version is pinned | |
| # explicitly on the `tool:` line below, so an upstream | |
| # cargo-llvm-cov release cannot silently change the coverage | |
| # numbers; (2) it's a download rather than a 1-2 min compile, | |
| # so the cache-miss path is fast. | |
| # The cargo-llvm-cov version pin is independent of NIGHTLY_PIN — | |
| # the two can be bumped together or independently per | |
| # docs/guides/ci.md "Nightly pinning". | |
| uses: taiki-e/install-action@e0eafa9a0d485c37f97c0f7beb930a58a2facbac # v2.79.4 | |
| with: | |
| tool: cargo-llvm-cov@0.6.16 | |
| - name: Cache cargo registry and llvm-cov target | |
| uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| # ~/.cargo/bin is intentionally omitted here (unlike the sibling | |
| # jobs): cargo-llvm-cov is installed by taiki-e/install-action, not | |
| # `cargo install`, so nothing useful lands in ~/.cargo/bin to cache. | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-llvmcov-${{ env.NIGHTLY_PIN }}-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-llvmcov-${{ env.NIGHTLY_PIN }}- | |
| - name: cargo llvm-cov --summary-only | |
| # Exclude the bare-metal crates: the BSP is no_std/no_main (cannot host- | |
| # build) and the userland crates (tyrne-user / hello) host-compile only | |
| # as aarch64-gated stubs (ADR-0039) — coverage of never-run stubs is | |
| # meaningless and would only dilute the metric. | |
| run: cargo llvm-cov --workspace --exclude tyrne-bsp-qemu-virt --exclude tyrne-user --exclude tyrne-userland-hello --summary-only |