Skip to content

Commit a5fbcc0

Browse files
authored
fix: build cache invalidation on --target change + 28_target_static test (#29)
* fix: 28_target_static.sh uses isolated MCPP_HOME + detects GNU gcc Root cause: without an explicit [toolchain] in the scaffolded project and without a global defaultToolchain, mcpp's first-run logic auto-installs gcc@15.1.0-musl as the default. The second build (intended to exercise the default GNU path) then also produced a musl binary under target/x86_64-linux-musl/, which the find command pruned → "default GNU binary missing". Fix: - Use an isolated MCPP_HOME via _inherit_toolchain.sh (symlinks the global xpkgs/config so installed toolchains are visible) - Auto-detect the highest installed GNU gcc version and pin it in the project [toolchain].linux - Skip the GNU regression check when no GNU gcc is available rather than failing * debug: add diagnostic output to 28_target_static.sh * fix: build cache must invalidate when --target changes The P0 fast-path build cache (target/.build_cache) only checked source file and mcpp.toml timestamps. When `mcpp build --target x86_64-linux-musl` was followed by a plain `mcpp build`, the fast-path reused the musl build.ninja — producing no GNU binary. Fix: store the user's --target value as a third line in .build_cache. try_fast_build() now compares the cached target against the current request and falls back to a full rebuild on mismatch. Also fix the test: detect installed GNU gcc dynamically and pin it in [toolchain].linux so the default build uses GNU, not musl auto-install.
1 parent 00b7423 commit a5fbcc0

2 files changed

Lines changed: 50 additions & 10 deletions

File tree

src/cli.cppm

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,21 +2039,24 @@ constexpr std::string_view kBuildCacheFile = "target/.build_cache";
20392039

20402040
void write_build_cache(const std::filesystem::path& projectRoot,
20412041
const std::filesystem::path& outputDir,
2042-
const std::string& ninjaProgram) {
2042+
const std::string& ninjaProgram,
2043+
const std::string& targetTriple) {
20432044
auto path = projectRoot / kBuildCacheFile;
20442045
std::error_code ec;
20452046
std::filesystem::create_directories(path.parent_path(), ec);
20462047
std::ofstream f(path, std::ios::trunc);
20472048
if (f) {
20482049
f << outputDir.string() << '\n';
20492050
f << ninjaProgram << '\n';
2051+
f << targetTriple << '\n';
20502052
}
20512053
}
20522054

20532055
// Compile a prepared BuildContext. Shared between `mcpp build` and `mcpp run`
20542056
// so the latter doesn't call prepare_build twice (and re-print the toolchain
20552057
// resolution banner).
2056-
int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) {
2058+
int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache,
2059+
std::string_view targetOverride = "") {
20572060
if (no_cache) {
20582061
std::error_code ec;
20592062
std::filesystem::remove_all(ctx.outputDir, ec);
@@ -2106,7 +2109,8 @@ int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) {
21062109

21072110
// P0: save build cache for fast-path on next invocation.
21082111
if (!no_cache && !r->ninjaProgram.empty()) {
2109-
write_build_cache(ctx.projectRoot, ctx.outputDir, r->ninjaProgram);
2112+
write_build_cache(ctx.projectRoot, ctx.outputDir, r->ninjaProgram,
2113+
std::string(targetOverride));
21102114
}
21112115

21122116
mcpp::ui::finished("release", r->elapsed);
@@ -2125,17 +2129,24 @@ int run_build_plan(BuildContext& ctx, bool verbose, bool no_cache) {
21252129
// Try to fast-path: if build.ninja is newer than all inputs, just run ninja.
21262130
// Returns exit code on fast-path, or nullopt if full rebuild needed.
21272131
std::optional<int> try_fast_build(const std::filesystem::path& projectRoot,
2128-
bool verbose, bool no_cache) {
2132+
bool verbose, bool no_cache,
2133+
std::string_view currentTarget = "") {
21292134
if (no_cache) return std::nullopt;
21302135

21312136
auto cachePath = projectRoot / kBuildCacheFile;
21322137
std::error_code ec;
21332138
if (!std::filesystem::exists(cachePath, ec)) return std::nullopt;
21342139

21352140
std::ifstream f(cachePath);
2136-
std::string outputDirStr, ninjaProgram;
2141+
std::string outputDirStr, ninjaProgram, cachedTarget;
21372142
if (!std::getline(f, outputDirStr) || outputDirStr.empty()) return std::nullopt;
21382143
if (!std::getline(f, ninjaProgram) || ninjaProgram.empty()) return std::nullopt;
2144+
std::getline(f, cachedTarget); // may be empty for old cache files
2145+
2146+
// Reject cache if target triple changed (e.g. previous build used
2147+
// --target x86_64-linux-musl but this one is a default build).
2148+
if (cachedTarget != currentTarget) return std::nullopt;
2149+
21392150
std::filesystem::path outputDir(outputDirStr);
21402151

21412152
auto ninjaPath = outputDir / "build.ninja";
@@ -2214,7 +2225,7 @@ int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) {
22142225
auto ctx = prepare_build(print_fp, /*includeDevDeps=*/false, /*extraTargets=*/{}, ov);
22152226
if (!ctx) { std::println(stderr, "error: {}", ctx.error()); return 2; }
22162227

2217-
return run_build_plan(*ctx, verbose, no_cache);
2228+
return run_build_plan(*ctx, verbose, no_cache, ov.target_triple);
22182229
}
22192230

22202231
int cmd_run(const mcpplibs::cmdline::ParsedArgs& parsed,

tests/e2e/28_target_static.sh

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ then
1919
exit 0
2020
fi
2121

22+
# Detect installed GNU gcc version for the default-build regression check.
23+
# Without a known GNU toolchain the default build would auto-install
24+
# musl-gcc and produce another musl binary, making the assertion meaningless.
25+
XPKGS="${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs"
26+
GNU_GCC_VER=""
27+
if [[ -d "$XPKGS/xim-x-gcc" ]]; then
28+
GNU_GCC_VER=$(ls "$XPKGS/xim-x-gcc" 2>/dev/null | sort -V | tail -1)
29+
fi
30+
2231
TMP=$(mktemp -d)
2332
trap "rm -rf $TMP" EXIT
2433

@@ -27,12 +36,26 @@ cd "$TMP"
2736
cd staticapp
2837

2938
# Add a [target.x86_64-linux-musl] override that tells mcpp to use musl-gcc.
39+
# If a GNU gcc is available, also pin [toolchain].linux so the default build
40+
# uses it (preventing fallback to auto-install musl-gcc as default).
41+
if [[ -n "$GNU_GCC_VER" ]]; then
42+
cat >> mcpp.toml <<EOF
43+
44+
[toolchain]
45+
linux = "gcc@${GNU_GCC_VER}"
46+
47+
[target.x86_64-linux-musl]
48+
toolchain = "gcc@15.1.0-musl"
49+
linkage = "static"
50+
EOF
51+
else
3052
cat >> mcpp.toml <<'EOF'
3153
3254
[target.x86_64-linux-musl]
3355
toolchain = "gcc@15.1.0-musl"
3456
linkage = "static"
3557
EOF
58+
fi
3659

3760
"$MCPP" build --target x86_64-linux-musl > build.log 2>&1 || {
3861
cat build.log; echo "build failed"; exit 1; }
@@ -58,9 +81,15 @@ fi
5881

5982
# Default GNU build still works (regression: --target should not have
6083
# clobbered the default codepath for follow-up commands).
61-
"$MCPP" build > build-gnu.log 2>&1 || {
62-
cat build-gnu.log; echo "default GNU build broke after musl build"; exit 1; }
63-
gnu_binary=$(find target -type d -name x86_64-linux-musl -prune -o -type f -name staticapp -print | head -1)
64-
[[ -n "$gnu_binary" ]] || { echo "default GNU binary missing"; exit 1; }
84+
# Skip this check if no GNU gcc is available — without it the default
85+
# build would also use musl-gcc, making the assertion meaningless.
86+
if [[ -n "$GNU_GCC_VER" ]]; then
87+
"$MCPP" build > build-gnu.log 2>&1 || {
88+
cat build-gnu.log; echo "default GNU build broke after musl build"; exit 1; }
89+
gnu_binary=$(find target -type d -name x86_64-linux-musl -prune -o -type f -name staticapp -print | head -1)
90+
[[ -n "$gnu_binary" ]] || { echo "default GNU binary missing"; exit 1; }
91+
else
92+
echo "SKIP: no GNU gcc installed — skipping default-build regression check"
93+
fi
6594

6695
echo "OK"

0 commit comments

Comments
 (0)