diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 5dcec6a..4664479 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -315,3 +315,14 @@ jobs: "$MCPP" build "$MCPP" --version echo ":: Self-host smoke PASS" + + - name: Fresh user experience (xlings install mcpp → new → run) + continue-on-error: true + run: | + # Test real user flow with xlings-distributed mcpp. + # May fail if xlings mcpp version lacks recent fixes. + TMP=$(mktemp -d) + cd "$TMP" + mcpp new hello + cd hello + mcpp run diff --git a/.github/workflows/ci-windows.yml b/.github/workflows/ci-windows.yml index 9114523..d8b9aa0 100644 --- a/.github/workflows/ci-windows.yml +++ b/.github/workflows/ci-windows.yml @@ -108,6 +108,19 @@ jobs: "$MCPP_SELF" --version echo ":: Self-host smoke PASS" + - name: Fresh user experience (xlings install mcpp → new → run) + continue-on-error: true + shell: bash + run: | + # Test the real user flow with the xlings-distributed mcpp. + # Currently xlings ships 0.0.17 which lacks Windows fixes. + # This step will auto-pass once xlings updates to 0.0.19+. + TMP=$(mktemp -d) + cd "$TMP" + "$MCPP" new hello + cd hello + "$MCPP" run + - name: Package Windows release zip id: package shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89fcf68..e0ad346 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,3 +133,14 @@ jobs: MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)") "$MCPP" build "$MCPP" test + + - name: Fresh user experience (xlings install mcpp → new → run) + continue-on-error: true + run: | + # Test real user flow with xlings-distributed mcpp. + # May fail if xlings mcpp version lacks recent fixes. + TMP=$(mktemp -d) + cd "$TMP" + mcpp new hello + cd hello + mcpp run diff --git a/src/build/ninja_backend.cppm b/src/build/ninja_backend.cppm index f2c7073..b5cccce 100644 --- a/src/build/ninja_backend.cppm +++ b/src/build/ninja_backend.cppm @@ -317,8 +317,10 @@ std::string emit_ninja_string(const BuildPlan& plan) { } else { // Clang path: clang-scan-deps produces P1689 JSON to stdout. #if defined(_WIN32) - append(" command = $scan_deps -format=p1689 -- " - "$cxx $cxxflags -c $in -o $compile_target > $out\n"); + // Wrap in cmd /c for shell redirection (ninja on Windows uses + // CreateProcess which doesn't interpret > as redirect). + append(" command = cmd /c \"$scan_deps -format=p1689 -- " + "$cxx $cxxflags -c $in -o $compile_target > $out\"\n"); #else append(" command = $toolenv $scan_deps -format=p1689 -- " "$cxx $cxxflags -c $in -o $compile_target > $out\n"); diff --git a/src/cli.cppm b/src/cli.cppm index f0aaf8e..a8ba0f1 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -1110,7 +1110,7 @@ prepare_build(bool print_fingerprint, // CI / offline / test opt-out: hard-error instead of silently // pulling ~800 MB of toolchain. Preserves the original M5.5 // contract for environments that need it. -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(_WIN32) return std::unexpected( "no toolchain configured.\n" " run one of:\n" diff --git a/src/config.cppm b/src/config.cppm index 6c843f7..2c51951 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -84,6 +84,18 @@ struct GlobalConfig { // Create an xlings::Env from the resolved GlobalConfig. mcpp::xlings::Env make_xlings_env(const GlobalConfig& cfg) { +#if defined(_WIN32) + // On Windows, the copied xlings binary in the sandbox may not function + // correctly for large package installs (missing runtime environment). + // When MCPP_VENDORED_XLINGS is set, use the original xlings binary + // directly — it has the full xlings runtime. The XLINGS_HOME env var + // ensures packages are installed into the mcpp sandbox. + if (auto* e = std::getenv("MCPP_VENDORED_XLINGS"); e && *e) { + std::filesystem::path vendored{e}; + if (std::filesystem::exists(vendored)) + return { vendored, cfg.xlingsHome() }; + } +#endif return { cfg.xlingsBinary, cfg.xlingsHome() }; } diff --git a/src/pm/package_fetcher.cppm b/src/pm/package_fetcher.cppm index 52f2bd8..696a961 100644 --- a/src/pm/package_fetcher.cppm +++ b/src/pm/package_fetcher.cppm @@ -605,28 +605,37 @@ Fetcher::resolve_xpkg_path(std::string_view target, }; auto resolve = [&]() -> std::expected { - // xlings may install the package into its global home rather than - // the mcpp sandbox. If the expected path is missing, copy from the - // global xlings data directory. - if (!std::filesystem::exists(verdir)) { - auto xhome = std::getenv("HOME"); #if defined(_WIN32) - if (!xhome) xhome = std::getenv("USERPROFILE"); -#endif + // Workaround: xlings on Windows may extract large packages (e.g. LLVM) + // into its global data dir instead of the mcpp sandbox, because the + // extraction subprocess doesn't inherit XLINGS_HOME. Detect this and + // copy the payload into the sandbox so mcpp remains self-contained. + if (!std::filesystem::exists(verdir)) { + // Try xlings' own data dir (where `xlings self install` placed it) + auto xhome = std::getenv("USERPROFILE"); + if (!xhome) xhome = std::getenv("HOME"); if (xhome) { - auto globalDir = std::filesystem::path(xhome) - / ".xlings" / "data" / "xpkgs" - / verdir.parent_path().filename() - / verdir.filename(); - std::error_code ec; - if (std::filesystem::exists(globalDir, ec)) { - std::filesystem::create_directories(verdir.parent_path(), ec); - std::filesystem::copy(globalDir, verdir, - std::filesystem::copy_options::recursive - | std::filesystem::copy_options::overwrite_existing, ec); + // xlings stores xpkgs at /.xlings/data/xpkgs/ or + // /.xlings/subos/default/data/xpkgs/ + auto pkgDir = verdir.parent_path().filename().string(); + auto verName = verdir.filename().string(); + std::filesystem::path candidates[] = { + std::filesystem::path(xhome) / ".xlings" / "data" / "xpkgs" / pkgDir / verName, + std::filesystem::path(xhome) / ".xlings" / "subos" / "default" / "data" / "xpkgs" / pkgDir / verName, + }; + for (auto& src : candidates) { + std::error_code ec; + if (std::filesystem::exists(src, ec) && std::filesystem::is_directory(src, ec)) { + std::filesystem::create_directories(verdir.parent_path(), ec); + std::filesystem::copy(src, verdir, + std::filesystem::copy_options::recursive + | std::filesystem::copy_options::overwrite_existing, ec); + if (!ec) break; + } } } } +#endif if (!std::filesystem::exists(verdir)) { return std::unexpected(CallError{ std::format("xpkg payload missing: {}", verdir.string())}); diff --git a/src/xlings.cppm b/src/xlings.cppm index 39ebfb8..7d5c8af 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -428,18 +428,13 @@ std::filesystem::path sandbox_init_marker(const Env& env) { std::string build_command_prefix(const Env& env) { auto xvmBin = paths::sandbox_bin(env).string(); #if defined(_WIN32) - // Windows: set environment variables via the process environment - // (cmd.exe `set` in compound &&-chains is unreliable) then invoke - // xlings directly. _putenv_s is inherited by popen/system child. _putenv_s("XLINGS_HOME", env.home.string().c_str()); _putenv_s("XLINGS_PROJECT_DIR", env.projectDir.empty() ? "" : env.projectDir.string().c_str()); - // Prepend sandbox bin to PATH { std::string newPath = xvmBin + ";" + (std::getenv("PATH") ? std::getenv("PATH") : ""); _putenv_s("PATH", newPath.c_str()); } - // Return raw path — no quoting to avoid cmd.exe double-quote parsing issues return env.binary.string(); #else if (env.projectDir.empty()) { @@ -666,9 +661,17 @@ int install_with_progress(const Env& env, std::string_view target, #if defined(_WIN32) _putenv_s("XLINGS_HOME", env.home.string().c_str()); _putenv_s("XLINGS_PROJECT_DIR", ""); - // Use raw path (no quoting) to avoid cmd.exe double-quote parsing issues. - // Wrap only the JSON arg in single-escaped quotes for the C runtime. - auto cmd = std::format("{} interface install_packages --args {} 2>nul", + std::error_code ec_mkdir; + std::filesystem::create_directories(env.home, ec_mkdir); + // Use direct `install` command instead of `interface install_packages` + // on Windows. The NDJSON interface may have issues with large packages + // where the extraction subprocess doesn't respect XLINGS_HOME. + auto directCmd = std::format("{} install {} -y", + env.binary.string(), target); + int directRc = std::system(directCmd.c_str()); + if (directRc == 0) return 0; + // Fallback to interface path if direct install fails + auto cmd = std::format("{} interface install_packages --args {}", env.binary.string(), shq(argsJson)); #else