Skip to content

runner beyond bare metal: honour [target.<triple>].runner on hosted targets, typed launch failures, not-run reporting, [xlings] values per host (#544) - #545

Open
Sunrisepeak wants to merge 7 commits into
mainfrom
fix/runner-hosted-targets
Open

runner beyond bare metal: honour [target.<triple>].runner on hosted targets, typed launch failures, not-run reporting, [xlings] values per host (#544)#545
Sunrisepeak wants to merge 7 commits into
mainfrom
fix/runner-hosted-targets

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Closes #544.

The defect

[target.<triple>].runner was parsed, validated and documented for every exact triple, and consulted only when os == "none". On a hosted cross target (aarch64-linux-musl built on x86_64) mcpp run executed the artifact bare, the kernel refused it with ENOEXEC, and run_exec turned the refusal into a bare 127 with nothing printed; mcpp test reported FAIL (exit 127), which states that the test ran. The bounded launcher's own invariant ("could not spawn" and "ran and failed" must not share an exit code) was defeated one layer down, and both deadline wrappers spawned a second time and discarded the first errno.

Design record: .agents/docs/2026-09-02-runner-beyond-baremetal-design.md (sections 0 and 4 are normative). Plan: .agents/docs/2026-09-02-issue544-runner-implementation-plan.md.

What changes

Launcher layer. DeadlineRun and BoundedOutcome carry the spawn error; run_exec, capture_exec and both deadline wrappers take a trailing int* spawn_error. A refused spawn is reported exactly once: typed to the caller when it asked, printed by the launcher otherwise. There is no second spawn.

choose_runner reads both producers for every target. The freestanding predicate decides one thing only: whether an absent runner is fatal before any spawn. The runner's first element is located by mcpp — the bin/ of each payload declared under [xlings] deps, then PATH — because a bare name on PATH reaches an xvm shim that answers for the current SubOS rather than for the package (e2e 130 recorded this in CI). Not found anywhere is decided before any spawn and is an error, not a fallback to bare execution.

mcpp run. A declared runner is used; a runner that cannot be found or started is an error naming the program, the directories searched and the errno; an artifact the kernel refuses with no runner declared is reported with the key to write. All three exit 2, distinct from 1 ("ran and failed"). The run fast path declines an entry whose target has a runner declared (runner= line in .build_cache), so the second mcpp run behaves as the first.

mcpp test. TestResult::St::NotRun with a reason. The runner is resolved once per invocation; the first refused spawn sets an invocation-wide flag, later tests are reported not-run without a spawn, and the reason is printed once. The summary line carries the count at the same weight as failures; --message-format json carries not_run and reason per record and not_run / not_run_reason on the summary; --workspace adds tests_not_run and unrunnable_members. Exit code 2 whenever the count is non-zero — the code the freestanding no-runner path already returns for the same situation.

--no-runner on run and test: execute directly, ignoring a declared runner. The manifest has no host axis; this is the operator on a host where the triple is native stating that fact.

[xlings] values per host platform. A deps entry and a [xlings.workspace] value may be { linux = "...", macos = "...", windows = "...", default = "..." }, the form xlings' own .xlings.json accepts for workspace; macosx is accepted as xlings' spelling. Resolved against the host at manifest load, so every downstream reader keeps its flat list. An unknown key is an error. Previously [xlings.workspace] dropped a table value in silence, and [xlings] deps had no conditional form at all, which made deps = ["qemu-user-aarch64"] (an x86_64-only package) a hard build error on every other host.

The [target.<triple>] unknown-key sweep now covers arrays (runnerX = [...] is reported) and lists runner among the supported keys.

Decisions taken on review (design §0)

  • D1: no fallback from a missing runner to bare execution; it reproduced the failure the key exists to prevent, and read ENOENT as "absent" when it also means "found, interpreter missing".
  • D2: not-run exits 2, not 0; skip-exits-zero is how # requires: e2e never ran in CI.
  • D3: --no-runner replaces the withdrawn principle "a declaration this machine cannot satisfy must not disable a capability it has".
  • D5: runner = "" deferred (no producer emits mcpp:runner= for a hosted triple); spelling settled as [] when needed.
  • D7: per-platform [xlings] values, in xlings' own form.

Behaviour changes for existing manifests

  • A runner already declared under a hosted triple stops being inert. Where its program is missing on a host, mcpp run / mcpp test on that host now fail with a message instead of silently running the artifact bare; --no-runner is the escape.
  • A mcpp test run that reported FAIL (exit 127) for unrunnable artifacts now reports NOT RUN and exits 2 (still non-zero).
  • [xlings] deps and [xlings.workspace] accept a table value; a table with an unknown platform key is now an error where it was previously dropped.

Verification

  • Unit: test_process_run_exec (+9), test_manifest (+6), new test_runner_lookup (9). Full unit suite: 97 test binaries pass.
  • e2e: new 330_runner_hosted_targets.sh — no # requires: beyond unix-shell; the runner is a recording shell script and the unrunnable artifact is a host binary with e_machine patched to 0xffff, so it runs identically on a host with and without binfmt_misc. Sections: declared runner used on both run doors; --no-runner; runner not found (run and test); unrunnable artifact on run; mcpp test with one and two tests (streaming and capturing paths), human and JSON; array typo in the sweep. Fails at its first section on 2026.9.1.1 and passes on this branch.
  • Neighbours re-run on this branch: e2e 01, 130, 131, 132, 178.
  • check_docs_style.sh, check_modules_wiring.sh, check_version_pins.sh pass.

Docs: 05 (§2.7.1, new §2.7.3 and §2.7.3.1, §2.13), 11 (the mcpp test stream), 13, 15, 17, each with its docs/zh/ twin.

Version: 2026.9.2.1 (mcpp.toml, modules/versioning/src/version.cppm); the bootstrap pin stays at 2026.9.1.1 until the release is indexed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[target.<triple>].runner is validated but never consulted for a hosted foreign-ISA target; mcpp run and mcpp test then lose the exec failure

2 participants