Repository navigation
build_command_prefix: 'global mode' unsets XLINGS_PROJECT_DIR on POSIX and sets it to the empty string on Windows #614
Description
The asymmetry (source-confirmed)
src/xlings/xlings.cppm, build_command_prefix, on the same env.projectDir.empty() condition:
if constexpr (mcpp::platform::is_windows) {
mcpp::platform::env::set("XLINGS_HOME", env.home.string());
mcpp::platform::env::set("XLINGS_PROJECT_DIR",
env.projectDir.empty() ? "" : env.projectDir.string()); // ← set to ""
mcpp::platform::windows::prepend_path(xvmBin);
return env.binary.string();
} else {
if (env.projectDir.empty()) {
// Global mode: unset XLINGS_PROJECT_DIR (existing behavior).
return std::format(
"cd {} && env -u XLINGS_PROJECT_DIR PATH={}:\"$PATH\" XLINGS_HOME={} {}", …);
}
…
}The POSIX comment names the intent — "Global mode: unset XLINGS_PROJECT_DIR" — and the Windows branch does not implement it. Absent and present-but-empty are not the same answer to "which scope is this", and xlings resolves its subos scope from that variable.
The same shape appears at the self init call site (env::set("XLINGS_PROJECT_DIR", "") on Windows, env -u XLINGS_PROJECT_DIR on POSIX).
A second, smaller difference in the same block: the Windows branch mutates the process environment with env::set, so the value persists past the invocation, while the POSIX branch scopes it to the one command via env.
Where it surfaced (circumstantial — I cannot prove causation without a Windows box)
xim:vulkan-loader's Windows config() declared one subos.env{ var = "PATH", op = "prepend", … }. Same descriptor, two environments, opposite results:
| environment | result |
|---|---|
full xlings (openxlings/xim-pkgindex windows-test) |
✓ local:vulkan-loader@1.4.313 done, [PASS] new shim, subos default present |
| mcpp project sandbox (mcpplibs/mcpp-index#391, windows leg) | E_INTERNAL: [vulkan-loader] failed: config hook failed |
In #391 every other leg is green (12 pass / 1 fail) and the loader is downloaded and installed before the hook runs, so the dependency edge and the payload are both fine.
What I could establish from source: the Lua side cannot throw — subos.env and xvm.add only table.insert into _XVM_OPS and return. The failure is therefore in xlings' C++ consumer (apply_subos_env_ops_, src/core/xim/installer.cpp), whose early exits all involve the subos manifest it locates via Config::xvm_artifact_subos_dir() → the scope resolved from XLINGS_PROJECT_DIR. prepend itself is implemented (src/core/subos.cpp discusses its semantics), so the mode is not the problem.
That is where the trail ends without a Windows machine. The asymmetry above is certain; its connection to this failure is a hypothesis.
Diagnosability, which cost most of the time here
xlings logs the precise rejection — log::error("[xim] …") with the variable, the binding and the reason. None of it reaches mcpp's output: the CI log contains no [xim] line at all, only
xlings reported: E_INTERNAL: [vulkan-loader] failed: config hook failed; [vulkan-loader] failed: config hook failed
Surfacing xlings' error-level lines (or an opt-in flag) would have turned this into a one-look diagnosis instead of a source read across three repos.
Status
Not blocking anyone: openxlings/xim-pkgindex#819 routes around it by declaring exports.runtime.libdirs instead, which needs no subos — that is the mechanism the package should have used anyway (mcpp copies a dependency's *.dll beside the executable, src/build/plan.cppm). Filing so the asymmetry is recorded rather than rediscovered.
Activity
fix(vulkan-loader): the DLL is deployed by declaration, not by PATH (#…
fix(ci): provide the Vulkan loader on the windows leg, the way everyo…
feat: the engine gaps left open after the SDK batch (2026.9.12.2)
The engine gaps left open after the SDK batch (2026.9.12.2) (#619)
Sunrisepeak commentedon Sep 11, 2026
Resolved in mcpp 2026.9.12.2 (#619), with one correction to the report.
The CRT defines
_putenv_s(key, "")as removal, so the Windows branch already produced an absentXLINGS_PROJECT_DIR, and the two platforms did not disagree about global mode. What was wrong on Windows was the lifetime: the value stayed in mcpp's own environment after the invocation. The asymmetry that did exist was on POSIX, where theinstall_packagesfallback spelled global mode by hand whatever the project directory was.invocation_envdecides the environment of an xlings invocation.ScopedInvocationEnvfor the lifetime of each runner, then restores the prior value.The environment decision, the restore guarantee and the tail selection are unit-tested on both platforms.