0.2.0 —— 门槛通过:一份探针源码,两台确实不同的机器 #5
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
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| # Three jobs, and the first two are ONE job written once: the gate this layer is | |
| # judged by is that a single probe source runs on two genuinely different | |
| # machines, so the two rows below differ only in a triple and an emulator | |
| # package. If they ever need to differ in anything else, the abstraction has | |
| # failed and this workflow is where that becomes visible. | |
| jobs: | |
| gate: | |
| name: the probe runs on ${{ matrix.arch }} | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { arch: riscv64, triple: riscv64-none-elf, qemu: 'xim:qemu-riscv' } | |
| - { arch: aarch64, triple: aarch64-none-elf, qemu: 'xim:qemu-arm' } | |
| env: | |
| MCPP_VERSION: 2026.8.20.3 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install mcpp | |
| run: | | |
| # ⚠️ A LOOP, BECAUSE ONE `xlings update` CAN RETURN A STALE INDEX | |
| # WITHOUT SAYING SO. | |
| # | |
| # The index is published as an artifact behind a pointer, and that | |
| # pointer propagates asynchronously after a version bump is merged. | |
| # Measured on release day: an update run four minutes after the merge | |
| # printed `index updated`, and the install then failed with | |
| # | |
| # package 'mcpp@<ver>' not found in the synced index | |
| # (xim@artifact:<an older sha>, ...), synced 0 seconds ago | |
| # | |
| # Nothing had gone wrong. The update fetched the PREVIOUS artifact, | |
| # and "synced 0 seconds ago" describes when it was fetched rather than | |
| # what it contains — which is why the message reads as freshness. | |
| # | |
| # So this is not a retry around flakiness; it is the wait that a | |
| # single update does not perform. A pin naming a version that was | |
| # never published still fails, after the last attempt, and says which | |
| # of the two situations it is. | |
| for attempt in 1 2 3 4 5 6; do | |
| xlings update > /dev/null 2>&1 || true | |
| if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi | |
| if [ "$attempt" = 6 ]; then | |
| echo "::error::mcpp@$MCPP_VERSION never appeared in the index (6 attempts over 5 minutes). If it was just released, the pointer has not propagated; if the pin names a version that was never published, it never will." | |
| exit 1 | |
| fi | |
| echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s" | |
| sleep 60 | |
| done | |
| mcpp --version | |
| mcpp self config --mirror GLOBAL | |
| # ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL. THIS IS NOT SUPERSTITION. | |
| # | |
| # mcpp installs the target's C library LAZILY — during a build, from the | |
| # target's own row. So the very first build on a machine that has never | |
| # targeted this triple compiles sources that need its headers before they | |
| # exist, and dies on `'stdio.h' file not found` or `'inttypes.h' file not | |
| # found` pointing inside a dependency. The second build has them. mcpp | |
| # says as much itself for the sibling case: "expected on the first build | |
| # in a fresh MCPP_HOME; a second build resolves it." | |
| # | |
| # ⚠️ Deliberately NOT `xlings install xim:picolibc-riscv`. That would make | |
| # the target world exist without going through the engine's own install, | |
| # and a regression in that install is exactly what this repository would | |
| # then stop noticing. A warm-up build exercises it; a manual install | |
| # replaces it. | |
| # ⚠️ BOTH HOMES, AND THIS IS NOT BELT-AND-BRACES. The shim on PATH | |
| # dispatches against whichever home owns it, while `mcpp run` starts the | |
| # runner through mcpp's own — an emulator present in only one of them | |
| # answers "not installed" from the other. | |
| # | |
| # The two packages are different builds: xPack compiles QEMU per target | |
| # family, so `qemu-riscv` carries only the two RISC-V emulators and | |
| # `qemu-arm` only the two Arm ones. Measured; no single package runs both. | |
| - name: Install the emulator | |
| run: | | |
| xlings install ${{ matrix.qemu }} -y | |
| XLINGS_HOME="$HOME/.mcpp/registry" xlings install ${{ matrix.qemu }} -y | |
| - name: The layer builds for ${{ matrix.arch }} | |
| run: | | |
| # Twice, the first allowed to fail: the toolchain payload is installed | |
| # during a build, so the first build on a machine that has never | |
| # targeted this triple is the one that installs it. | |
| mcpp build --target ${{ matrix.triple }} || true | |
| mcpp build --target ${{ matrix.triple }} | |
| # ⚠️ THE THIRD ASSERTION IS THE ONE THAT CATCHES A HALF-CORRECT BACKEND. | |
| # | |
| # A switch that saved the return address and the stack pointer and nothing | |
| # else would reach the task and deliver its argument — the first two lines | |
| # would appear — and would corrupt the caller's callee-saved registers. | |
| # `before=1234` is a `volatile` local read after the round trip, and it is | |
| # what fails when that happens. | |
| - name: The probe switches, returns, and preserves callee-saved registers | |
| working-directory: examples/switch | |
| run: | | |
| set -euo pipefail | |
| mcpp run --target ${{ matrix.triple }} 2>&1 | tee run.log | |
| grep -q "task: arg=42" run.log | |
| grep -q "witness=7 before=1234" run.log | |
| grep -q "switch ok" run.log | |
| # The same source produced that output. Asserted rather than trusted: a | |
| # probe that had quietly grown a per-architecture branch would still pass | |
| # every line above, and the gate would be measuring two programs. | |
| - name: The probe is one source, not two | |
| run: | | |
| test -f examples/switch/src/main.cpp | |
| if grep -qE '__riscv|__aarch64__|MCPP_TARGET_ARCH' examples/switch/src/main.cpp; then | |
| echo "the probe branches on the architecture, which is what it exists to avoid" | |
| exit 1 | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # The half no emulator can check. | |
| # | |
| # A mis-shifted field in a page-table entry still maps something, still boots, | |
| # and faults later somewhere that names neither the encoder nor the entry. The | |
| # encoders are pure functions in per-architecture namespaces, so a host build | |
| # holds BOTH and compares them — which is what several of the assertions are, | |
| # and what no single-target build could express. | |
| host-encoders: | |
| name: the page-table encoders agree (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-24.04, macos-14, windows-2022] | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| MCPP_VERSION: 2026.8.20.3 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install xlings (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| irm https://d2learn.org/xlings-install.ps1.txt | iex | |
| "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install mcpp | |
| run: | | |
| for attempt in 1 2 3 4 5 6; do | |
| xlings update > /dev/null 2>&1 || true | |
| if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi | |
| if [ "$attempt" = 6 ]; then | |
| echo "::error::mcpp@$MCPP_VERSION never appeared in the index (6 attempts over 5 minutes)." | |
| exit 1 | |
| fi | |
| echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s" | |
| sleep 60 | |
| done | |
| mcpp --version | |
| mcpp self config --mirror GLOBAL | |
| # ⚠️ NO FEATURE FLAGS, AND THAT IS THE OUTCOME OF A MEASUREMENT. The | |
| # encoders were feature-selected translation units until two facts ended | |
| # that design: a file named by any feature belongs to it exclusively (with | |
| # the feature inactive it is not compiled even when a `cfg` block lists it, | |
| # and the library build still reports success), and compiling both | |
| # unconditionally instead put 450 bytes of the foreign encoder into a | |
| # riscv64 image whose whole probe is about 2000. They are `inline` in a | |
| # header now: a target build instantiates only what it calls — measured, | |
| # zero foreign symbols in either image — and a host build that calls both | |
| # gets both, with nothing to activate. | |
| - name: Both encoders compile here and agree | |
| run: mcpp test | |
| # --------------------------------------------------------------------------- | |
| # The cross-compilation is performed FROM three systems, not only from Linux. | |
| # | |
| # Every target in this repository is a cross target, which makes the host a | |
| # separate axis from the target: the compiler, the target C library and the | |
| # emulator are payloads mcpp resolves for whichever system it is running on. | |
| # A package that has only ever been built from Linux is a package whose | |
| # consumers must use Linux, and nothing in these sources says so. | |
| # | |
| # ⚠️ A TOOLCHAIN AXIS IS ABSENT HERE, AND THAT IS MEASURED RATHER THAN | |
| # ASSUMED. The row for a bare-metal triple names its compiler, and the | |
| # command-line override does not displace it: `--toolchain gcc@16.1.0` on a | |
| # `riscv64-none-elf` build resolves llvm@22.1.8 regardless. A matrix over | |
| # compiler families would therefore run the same compiler on every row and | |
| # report coverage it does not have. The toolchain axis belongs where the | |
| # choice is real — openkal, whose declarations are compiled by three families | |
| # on three systems. | |
| # | |
| # ⚠️ BUILD ONLY, AND DELIBERATELY. Behaviour is asserted once, above, under an | |
| # emulator. Booting the same image from three systems would be a statement | |
| # about the emulator rather than about this package, and "the image does what | |
| # the README says" does not become more true for having been observed from | |
| # macOS. | |
| portability: | |
| name: cross-builds from ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-14, windows-2022] | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| MCPP_VERSION: 2026.8.20.3 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install xlings (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| irm https://d2learn.org/xlings-install.ps1.txt | iex | |
| # The installer amends the user's environment; no later step in this | |
| # job reads it back, so the directory is named here. | |
| "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install mcpp | |
| run: | | |
| # ⚠️ A LOOP, BECAUSE ONE `xlings update` CAN RETURN A STALE INDEX | |
| # WITHOUT SAYING SO. | |
| # | |
| # The index is published as an artifact behind a pointer, and that | |
| # pointer propagates asynchronously after a version bump is merged. | |
| # Measured on release day: an update run four minutes after the merge | |
| # printed `index updated`, and the install then failed with | |
| # | |
| # package 'mcpp@<ver>' not found in the synced index | |
| # (xim@artifact:<an older sha>, ...), synced 0 seconds ago | |
| # | |
| # Nothing had gone wrong. The update fetched the PREVIOUS artifact, | |
| # and "synced 0 seconds ago" describes when it was fetched rather than | |
| # what it contains — which is why the message reads as freshness. | |
| # | |
| # So this is not a retry around flakiness; it is the wait that a | |
| # single update does not perform. A pin naming a version that was | |
| # never published still fails, after the last attempt, and says which | |
| # of the two situations it is. | |
| for attempt in 1 2 3 4 5 6; do | |
| xlings update > /dev/null 2>&1 || true | |
| if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi | |
| if [ "$attempt" = 6 ]; then | |
| echo "::error::mcpp@$MCPP_VERSION never appeared in the index (6 attempts over 5 minutes). If it was just released, the pointer has not propagated; if the pin names a version that was never published, it never will." | |
| exit 1 | |
| fi | |
| echo "the index has not caught up yet (attempt $attempt of 6); waiting 60s" | |
| sleep 60 | |
| done | |
| mcpp --version | |
| mcpp self config --mirror GLOBAL | |
| - name: The arch mechanisms cross-build | |
| run: | | |
| # ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL — every row of this | |
| # matrix is a machine that has never targeted this triple, which is | |
| # precisely where mcpp's lazy install of the target C library shows. | |
| # The first build compiles sources needing its headers before they | |
| # exist; the second has them. A single build here reported a | |
| # portability failure that was really a cold-machine one. | |
| mcpp build --target riscv64-none-elf || true | |
| mcpp build --target riscv64-none-elf |