Repository navigation
Description
Observation
A workspace declares two tools for the verbs that run something:
# mcpp.toml (workspace root)
[workspace.xlings.workspace]
"xim:esbuild" = { version = "0.25.12", when = "run" }
"xim:typescript" = { version = "7.0.2", when = "run" }
[workspace]
members = ["app"]app has one program (src/main.cpp) and one test (tests/tools_test.cpp), the same file: it prints its PATH, then calls esbuild --version and tsc --version through std::system.
Both tools are provisioned on every host (the payload and its shims exist afterwards: ~/.mcpp/registry/data/xpkgs/xim-x-esbuild/0.25.12/bin/esbuild.exe, ~/.mcpp/registry/subos/default/bin/{esbuild,tsc}). Whether the program can call them depends on the verb and the host:
| host | mcpp | mcpp test -p app |
mcpp run -p app |
|---|---|---|---|
| ubuntu-24.04 | 2026.11.8.1, 2026.11.12.1 | found — PATH starts with ~/.mcpp/registry/subos/default/bin |
not found — PATH is the caller's (sh: 1: esbuild: not found) |
| windows-2022 | 2026.11.8.1, 2026.11.12.1 | not found — PATH starts with the llvm lib and the VC redist directories, then the caller's ('esbuild' is not recognized as an internal or external command) |
not found — the same PATH |
All four rows, with the PATH each program printed: https://github.com/Sunrisepeak/mui/actions/runs/43327987828 (the workflow that makes the project is in that commit: .github/workflows/probe-windows-tools.yml).
(On the Ubuntu rows tsc answers under mcpp run only because the runner image has a TypeScript of its own on PATH; esbuild, which it has not, shows the case.)
Cause
src/build/execute.cppm, where the test runner composes a test's environment:
// Prepend the toolchain's environment's bin to the CHILD PATH so test
// binaries that shell out to bootstrapped tools (patchelf, ninja) find
// them — applied to the child only, not via a leaky shell prefix.
if constexpr (!mcpplibs::os::is_windows) {
if (auto environment = mcpp::provider::environment_of(ctx->tc.binaryPath)) {
auto sandboxBin = *environment / "bin";
...
if (!pathVal.empty()) childEnv.emplace_back("PATH", pathVal);- On POSIX a test gets the environment's
bin, and awhen = "run"tool is reachable as a side effect of a prefix written for the toolchain's own tools. - On Windows the block is compiled out, so nothing puts the declared tools on a test's
PATH. mcpp run(build_run_target) setsov.will_run = true— "thewhen = "run"tool tier is part of what has to exist" — and provisions the tier, but composes only the runtime-library path (compute_run_env): the program it starts cannot find what was installed for it, on any host.
So the tier decides which verbs install a tool (chapter 23 §5), and nothing states — or does, uniformly — how the program those verbs start reaches it. For build.mcpp the chapter does say it: "the declared environment's bin goes to the front of the PATH that build.mcpp runs with".
The Windows exclusion looks deliberate after #726/#727: on Windows that directory also holds the shims xim:llvm registers (cl, link, lib, rc), and a test that starts a compiler or vcpkg would meet them instead of MSVC's. Prefixing the whole directory is therefore not the fix there.
What a consumer sees
openmui's tests call esbuild and tsc (TypeScript sources are stripped by both and compared). Declared when = "run" in the workspace, they are found on the Linux and macOS rows and not on the Windows row — with no message from mcpp, since provisioning succeeded. We first read it as a download that never completed, because of the second point below.
Proposal
- State the contract and hold it on every host and both verbs: a program started by
mcpp testormcpp runcan call the tools its graph declares for the run tier (and the unconditional ones), by name. - Give it the declared tools, not the whole environment
bin: e.g. a directory holding only the shims of this graph's[xlings.workspace]entries (per project or per plan, next to the run closure), prepended to the child'sPATHon POSIX and Windows alike. That keeps Windows: installing a payload duringmcpp buildleaves the registry's shim directory at the front of PATH for every action, so llvm's cl/link/lib shims shadow MSVC #726 fixed — llvm'scl/link/libshims are not declared tools and stay off the path — and makes the POSIX test path say what it means rather than expose everything installed in the environment. - Alternatively or additionally, the run contract's environment (
run_contract_env) could carry each declared tool's resolved program path (the way[xlings.workspace] program =already resolves a name), for programs that prefer not to searchPATH.
A smaller thing seen on the way
When two entries are fetched at once, one Downloading … done line is printed for the pair:
Source xim:esbuild@0.25.12 registry.npmjs.org (attempt 1)
Source xim:typescript@7.0.2 registry.npmjs.org (attempt 1)
Downloading xim:esbuild@0.25.12 done, 4.3 MB in 0.2s
Running bin/tools_test
(on the Ubuntu rows the one line is typescript's). Both are installed; the log reads as if one were not.
Versions
mcpp 2026.11.8.1 and 2026.11.12.1; llvm 23.1.3; windows-2022 and ubuntu-24.04 GitHub runners.
Activity
ci(red list 65): read to the root — the tools are installed, a test o…