Skip to content

Commit fda5e1b

Browse files
committed
feat(xlings): provision [xlings] deps on first build, at global scope
`[xlings] deps` was DECLARED and never installed. `ensure_project_index_dir` wrote it into `.mcpp/.xlings.json` verbatim and stopped there, so a manifest saying `deps = ["xim:mesa"]` produced a file naming mesa, no payload anywhere, and `fatal error: gbm.h: No such file or directory`. The declaration looked accepted and did nothing, which is the worst shape a config key can have — `[toolchain]` has had "declare it and mcpp provisions it on first use" all along ("First run — no toolchain configured … installing … as default"), and a build environment should not have two grades of declaration. GLOBAL SCOPE, AND THE SCOPE IS THE WHOLE POINT. The obvious implementation — `install_packages` against `make_project_xlings_env` — installs at PROJECT scope, and measurably does not work. On a fresh MCPP_HOME the headers land in `<proj>/.mcpp/.xlings/subos/_/usr/include` while `--sysroot` names `<MCPP_HOME>/registry/subos/default`: two SubOS views, payload in the one the compiler does not read, `#include <gbm.h>` still failing with the dependency installed and declared. `make_xlings_env` is the global env, so the payload lands in the registry whose SubOS *is* the sysroot — the same place `[toolchain]` installs into. That single choice is what removes the need for any sysroot-layering machinery: a project dep and a toolchain dep now agree on where they live, so one `--sysroot` sees both. `install_packages` rather than `resolve_xpkg_path`: the latter requires `<name>@<version>` and rejects a bare `mesa` (verified: "invalid xpkg target 'xim:mesa': expected `<name>@<version>`"), while a manifest is entitled to name a package without pinning it. install_packages resolves the version itself and reports an ambiguous name with its candidates, which is an error the author can act on. ORDER IS LOAD-BEARING: provisioning runs BEFORE the runtime binding resolves, because a named `[xlings] subos` that does not exist yet is a hard error ("selected SubOS '…' does not exist; create/bootstrap that environment") and provisioning is what creates it. Placed next to the custom-index sync, both first-use steps sit in one place. Idempotent by CONTENT, not existence: a stamp records the dep list, so editing the list re-provisions and an unchanged list costs no xlings round-trip. Verified — a second `mcpp run` emits no Provisioning line. VERIFIED end to end on a FRESH MCPP_HOME, with a project that has no mcpp-index dependency at all: [xlings] deps = ["xim:mesa"] [build] ldflags = ["-lgbm"] Provisioning [xlings] deps (xim:mesa) Compiling nopkg v0.1.0 (.) Running `target/.../bin/nopkg` XR24 | GBM_BACKENDS_PATH=<registry>/subos/default/usr/lib/gbm `#include <gbm.h>` compiles, `-lgbm` links, and the SubOS env declaration reaches the process — the last of those needs openxlings/xim-pkgindex#713, which adds GBM_BACKENDS_PATH to the graphics discovery table. Design: mcpp-index .agents/docs/2026-08-30-gbm-cross-repo-closed-loop-plan.md
1 parent 7f14f2c commit fda5e1b

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

src/build/prepare.cppm

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,6 +2994,109 @@ prepare_build(bool print_fingerprint,
29942994
**cfg2, runtimeSelection.ownerRoot, {}, penv);
29952995
}
29962996

2997+
// `[xlings] deps` are DECLARED above and, until now, nothing
2998+
// installed them (mcpp-index #281 §9).
2999+
//
3000+
// `ensure_project_index_dir` writes them into `.mcpp/.xlings.json`
3001+
// verbatim and stops there, so a manifest saying
3002+
// `deps = ["xim:mesa"]` produced a file naming mesa, no project
3003+
// SubOS, and `fatal error: gbm.h: No such file or directory`. The
3004+
// declaration looked accepted and did nothing — which is the worst
3005+
// shape a config key can have.
3006+
//
3007+
// This is the same "declare it and mcpp provisions it on first use"
3008+
// contract `[toolchain]` has had all along; that path is a few
3009+
// hundred lines up ("First run — no toolchain configured …
3010+
// installing … as default"). A build environment should not have
3011+
// two grades of declaration.
3012+
//
3013+
// ORDER IS LOAD-BEARING: this must run BEFORE the runtime binding
3014+
// resolves, because a named `[xlings] subos` that does not exist
3015+
// yet is a hard error ("selected SubOS '…' does not exist;
3016+
// create/bootstrap that environment"), and provisioning is what
3017+
// creates it. Placed here, next to the index sync below, both
3018+
// first-use provisioning steps sit in one place.
3019+
//
3020+
// `install_packages` rather than `fetcher.install`: the install
3021+
// DESTINATION is chosen by package scope (project vs global), and
3022+
// the project scope is what materializes the project SubOS. It also
3023+
// carries the live progress UI and captured child errors, matching
3024+
// the toolchain and custom-index paths.
3025+
if (!penv.deps.empty()) {
3026+
const auto stamp = runtimeSelection.ownerRoot / ".mcpp"
3027+
/ ".xlings-deps.stamp";
3028+
// Idempotence by CONTENT, not by existence: editing the list
3029+
// has to re-provision, and an unchanged list must not pay for
3030+
// an xlings round-trip on every build.
3031+
auto join_deps = [&](std::string_view sep) {
3032+
std::string out;
3033+
for (auto const& d : penv.deps) {
3034+
if (!out.empty()) out += sep;
3035+
out += d;
3036+
}
3037+
return out;
3038+
};
3039+
std::string want;
3040+
for (auto const& d : penv.deps) { want += d; want += '\n'; }
3041+
std::string have;
3042+
if (std::ifstream in{stamp}; in)
3043+
have.assign(std::istreambuf_iterator<char>(in), {});
3044+
if (have != want) {
3045+
mcpp::ui::status("Provisioning",
3046+
std::format("[xlings] deps ({})",
3047+
join_deps(", ")));
3048+
// GLOBAL scope, and the scope is the whole point.
3049+
//
3050+
// The obvious alternative -- `install_packages` against
3051+
// `make_project_xlings_env` -- installs at PROJECT scope,
3052+
// and that measurably does not work: on a fresh MCPP_HOME
3053+
// the headers land in
3054+
// `<proj>/.mcpp/.xlings/subos/_/usr/include` while
3055+
// `--sysroot` names `<MCPP_HOME>/registry/subos/default`,
3056+
// so `#include <gbm.h>` still failed with the dependency
3057+
// installed and declared. Two SubOS views, and the payload
3058+
// in the one the compiler does not read.
3059+
//
3060+
// `make_xlings_env` is the GLOBAL env, so this lands in the
3061+
// registry whose SubOS *is* mcpp's sysroot -- the same
3062+
// place `[toolchain]` has always installed into. A project
3063+
// dependency and a toolchain dependency now agree on where
3064+
// they live, which is the only arrangement in which one
3065+
// `--sysroot` can see both.
3066+
//
3067+
// `install_packages` rather than `resolve_xpkg_path`: the
3068+
// latter requires `<name>@<version>` and rejects a bare
3069+
// `mesa`, while a manifest is entitled to name a package
3070+
// without pinning it. install_packages resolves the version
3071+
// itself and reports an ambiguous name with its candidates,
3072+
// which is the error the author can act on.
3073+
std::string targets;
3074+
for (auto const& d : penv.deps) {
3075+
if (!targets.empty()) targets += ',';
3076+
targets += std::format("\"{}\"", d);
3077+
}
3078+
mcpp::fetcher::InstallProgressHandler progress;
3079+
auto r = mcpp::xlings::call(
3080+
mcpp::config::make_xlings_env(**cfg2), "install_packages",
3081+
std::format(R"({{"targets":[{}],"yes":true}})", targets),
3082+
&progress);
3083+
if (!r) {
3084+
// Shaped like the toolchain failure: say what failed and
3085+
// hand back a command the user can run themselves. An
3086+
// ambiguous bare name ("mesa" matching two repos) lands
3087+
// here, and xlings' own message names the candidates.
3088+
return std::unexpected(std::format(
3089+
"provisioning [xlings] deps failed: {}\n"
3090+
" you can install them manually with:\n"
3091+
" xlings install {}",
3092+
r.error(), join_deps(" ")));
3093+
}
3094+
std::error_code sec;
3095+
std::filesystem::create_directories(stamp.parent_path(), sec);
3096+
if (std::ofstream out{stamp}; out) out << want;
3097+
}
3098+
}
3099+
29973100
// On first build, the project index data root may be empty because
29983101
// ensure_project_index_dir only writes .xlings.json but does not
29993102
// trigger clone/link creation. Local path indices are read directly;

0 commit comments

Comments
 (0)