diff --git a/src/cli.cppm b/src/cli.cppm index ea6e6a1..d8c440e 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -2039,7 +2039,8 @@ constexpr std::string_view kBuildCacheFile = "target/.build_cache"; void write_build_cache(const std::filesystem::path& projectRoot, const std::filesystem::path& outputDir, - const std::string& ninjaProgram) { + const std::string& ninjaProgram, + const std::string& targetTriple) { auto path = projectRoot / kBuildCacheFile; std::error_code ec; std::filesystem::create_directories(path.parent_path(), ec); @@ -2047,13 +2048,15 @@ void write_build_cache(const std::filesystem::path& projectRoot, if (f) { f << outputDir.string() << '\n'; f << ninjaProgram << '\n'; + f << targetTriple << '\n'; } } // Compile a prepared BuildContext. Shared between `mcpp build` and `mcpp run` // so the latter doesn't call prepare_build twice (and re-print the toolchain // resolution banner). -int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) { +int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache, + std::string_view targetOverride = "") { if (no_cache) { std::error_code ec; std::filesystem::remove_all(ctx.outputDir, ec); @@ -2106,7 +2109,8 @@ int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) { // P0: save build cache for fast-path on next invocation. if (!no_cache && !r->ninjaProgram.empty()) { - write_build_cache(ctx.projectRoot, ctx.outputDir, r->ninjaProgram); + write_build_cache(ctx.projectRoot, ctx.outputDir, r->ninjaProgram, + std::string(targetOverride)); } mcpp::ui::finished("release", r->elapsed); @@ -2125,7 +2129,8 @@ int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) { // Try to fast-path: if build.ninja is newer than all inputs, just run ninja. // Returns exit code on fast-path, or nullopt if full rebuild needed. std::optional try_fast_build(const std::filesystem::path& projectRoot, - bool verbose, bool no_cache) { + bool verbose, bool no_cache, + std::string_view currentTarget = "") { if (no_cache) return std::nullopt; auto cachePath = projectRoot / kBuildCacheFile; @@ -2133,9 +2138,15 @@ std::optional try_fast_build(const std::filesystem::path& projectRoot, if (!std::filesystem::exists(cachePath, ec)) return std::nullopt; std::ifstream f(cachePath); - std::string outputDirStr, ninjaProgram; + std::string outputDirStr, ninjaProgram, cachedTarget; if (!std::getline(f, outputDirStr) || outputDirStr.empty()) return std::nullopt; if (!std::getline(f, ninjaProgram) || ninjaProgram.empty()) return std::nullopt; + std::getline(f, cachedTarget); // may be empty for old cache files + + // Reject cache if target triple changed (e.g. previous build used + // --target x86_64-linux-musl but this one is a default build). + if (cachedTarget != currentTarget) return std::nullopt; + std::filesystem::path outputDir(outputDirStr); auto ninjaPath = outputDir / "build.ninja"; @@ -2214,7 +2225,7 @@ int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) { auto ctx = prepare_build(print_fp, /*includeDevDeps=*/false, /*extraTargets=*/{}, ov); if (!ctx) { std::println(stderr, "error: {}", ctx.error()); return 2; } - return run_build_plan(*ctx, verbose, no_cache); + return run_build_plan(*ctx, verbose, no_cache, ov.target_triple); } int cmd_run(const mcpplibs::cmdline::ParsedArgs& parsed, diff --git a/tests/e2e/28_target_static.sh b/tests/e2e/28_target_static.sh index a808a20..dca96d5 100755 --- a/tests/e2e/28_target_static.sh +++ b/tests/e2e/28_target_static.sh @@ -19,6 +19,15 @@ then exit 0 fi +# Detect installed GNU gcc version for the default-build regression check. +# Without a known GNU toolchain the default build would auto-install +# musl-gcc and produce another musl binary, making the assertion meaningless. +XPKGS="${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs" +GNU_GCC_VER="" +if [[ -d "$XPKGS/xim-x-gcc" ]]; then + GNU_GCC_VER=$(ls "$XPKGS/xim-x-gcc" 2>/dev/null | sort -V | tail -1) +fi + TMP=$(mktemp -d) trap "rm -rf $TMP" EXIT @@ -27,12 +36,26 @@ cd "$TMP" cd staticapp # Add a [target.x86_64-linux-musl] override that tells mcpp to use musl-gcc. +# If a GNU gcc is available, also pin [toolchain].linux so the default build +# uses it (preventing fallback to auto-install musl-gcc as default). +if [[ -n "$GNU_GCC_VER" ]]; then +cat >> mcpp.toml <> mcpp.toml <<'EOF' [target.x86_64-linux-musl] toolchain = "gcc@15.1.0-musl" linkage = "static" EOF +fi "$MCPP" build --target x86_64-linux-musl > build.log 2>&1 || { cat build.log; echo "build failed"; exit 1; } @@ -58,9 +81,15 @@ fi # Default GNU build still works (regression: --target should not have # clobbered the default codepath for follow-up commands). -"$MCPP" build > build-gnu.log 2>&1 || { - cat build-gnu.log; echo "default GNU build broke after musl build"; exit 1; } -gnu_binary=$(find target -type d -name x86_64-linux-musl -prune -o -type f -name staticapp -print | head -1) -[[ -n "$gnu_binary" ]] || { echo "default GNU binary missing"; exit 1; } +# Skip this check if no GNU gcc is available — without it the default +# build would also use musl-gcc, making the assertion meaningless. +if [[ -n "$GNU_GCC_VER" ]]; then + "$MCPP" build > build-gnu.log 2>&1 || { + cat build-gnu.log; echo "default GNU build broke after musl build"; exit 1; } + gnu_binary=$(find target -type d -name x86_64-linux-musl -prune -o -type f -name staticapp -print | head -1) + [[ -n "$gnu_binary" ]] || { echo "default GNU binary missing"; exit 1; } +else + echo "SKIP: no GNU gcc installed — skipping default-build regression check" +fi echo "OK"