Skip to content

Commit 01e3e2a

Browse files
committed
fix(build): a program's out dir is beside its cache; emit build-database reuses a member's run
A workspace member's build program keeps its cache in the member's own target/.build-mcpp, also under a plan's work directory. Its out dir now has the same base, so MCPP_OUT_DIR, and the re-run key, match between a build and emit build-database (e2e 839 B4). e2e 652b, 810, 867, 868, 870 read the per-configuration out dir; resolution.json's outDir is not part of a program's graph document.
1 parent 58a3d02 commit 01e3e2a

9 files changed

Lines changed: 64 additions & 42 deletions

‎src/build/prepare/features.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@ static std::expected<void, std::string> step6_dependency_build_programs(PrepareS
19411941
bpEnv.features = feature_closure(pkg.manifest, req, depDefaultFeatures);
19421942
bpEnv.artifactsDir = state.workRoot / "target" / ".build-mcpp" / "deps"
19431943
/ (dirSafe(pkg.manifest.package.name) + "@" + pkg.manifest.package.version);
1944-
bpEnv.outDir = state.programOutDir(pkg.manifest, bpEnv.features);
1944+
bpEnv.outDir = state.programOutDir(state.workRoot, pkg.manifest, bpEnv.features);
19451945
bpEnv.genBase = bpEnv.outDir;
19461946
// What the program imports is kept once for the workspace, and in the
19471947
// global cache where it comes from the engine or the index (#748).

‎src/build/prepare/graph.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,8 @@ step4b_define_provisioning_closures(PrepareState& state) {
21592159
// (the scanner walks the legacy modules.sources mirror). ninja overwrites
21602160
// the placeholder before the compile edge runs, because that compile
21612161
// depends on the action's output.
2162-
state.programOutDir = [&state](const mcpp::manifest::Manifest& pm,
2162+
state.programOutDir = [&state](const std::filesystem::path& base,
2163+
const mcpp::manifest::Manifest& pm,
21632164
const std::vector<std::string>& features) {
21642165
// What changes the program's environment and is known before it runs.
21652166
std::string id = std::format("target={}\nprofile={}\naccel={}\n",
@@ -2174,7 +2175,7 @@ step4b_define_provisioning_closures(PrepareState& state) {
21742175
? pm.package.name : pm.package.namespace_ + "." + pm.package.name;
21752176
for (auto& c : name)
21762177
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '-' && c != '_') c = '_';
2177-
return state.workRoot / "target" / ".build-mcpp" / "out"
2178+
return base / "target" / ".build-mcpp" / "out"
21782179
/ mcpp::toolchain::hash_string(id) / (name.empty() ? std::string("root") : name);
21792180
};
21802181

‎src/build/prepare/state.cppm‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -622,12 +622,16 @@ struct PrepareState {
622622
std::function<void(mcpp::manifest::Manifest&, const std::filesystem::path&,
623623
std::size_t)> adoptActionOutputs;
624624
// A package's build-program output directory (`mcpp::out_dir()`,
625-
// MCPP_OUT_DIR) for this build's configuration (D7, D25): below the plan
626-
// root, one directory per configuration -- the target, the profile, the
625+
// MCPP_OUT_DIR) for this build's configuration (D7, D25): below `base`,
626+
// one directory per configuration -- the target, the profile, the
627627
// toolchain, the accelerators and the features the package is built
628-
// with -- and per package. The program binary and its cache stay in the
629-
// configuration-independent `.build-mcpp`.
630-
std::function<std::filesystem::path(const mcpp::manifest::Manifest&,
628+
// with -- and per package. `base` is the root that holds the program's
629+
// cache, so a plan under `BuildOverrides::work_dir` gives a program whose
630+
// cache is in the project the same directory a build gives it. The
631+
// program binary and its cache stay in the configuration-independent
632+
// `.build-mcpp`.
633+
std::function<std::filesystem::path(const std::filesystem::path&,
634+
const mcpp::manifest::Manifest&,
631635
const std::vector<std::string>&)> programOutDir;
632636
std::function<void(mcpp::build::BuildProgramEnv&, const mcpp::manifest::Manifest&,
633637
std::size_t)> fillXpkgDirs;

‎src/build/prepare/target_side.cpp‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,13 +1521,19 @@ static void step9_define_graph_package_entry_closure(PrepareState& state) {
15211521
// D7: where this package's build program writes in this
15221522
// configuration (`mcpp::out_dir()`), for a reader outside the build
15231523
// -- a test script -- that needs a generated file and must not guess.
1524-
{
1524+
// resolution.json only: the graph document a build program reads is
1525+
// part of its re-run key, and must not change with when it is asked.
1526+
if (!forBuildProgram) {
15251527
static const std::vector<std::string> none;
1526-
entry["outDir"] = state.programOutDir(pm, i < state.activeFeaturesByPackage.size()
1527-
? state.activeFeaturesByPackage[i] : none)
1528+
// A member's program keeps its cache in the project (step 9).
1529+
const bool member = state.m->package.virtualRoot && i != 0
1530+
&& state.isWorkspaceMemberPackage(i);
1531+
entry["outDir"] = state.programOutDir(member ? *state.root : state.workRoot, pm,
1532+
i < state.activeFeaturesByPackage.size()
1533+
? state.activeFeaturesByPackage[i] : none)
15281534
.generic_string();
1535+
return entry;
15291536
}
1530-
if (!forBuildProgram) return entry;
15311537

15321538
std::error_code ec;
15331539
auto dir = std::filesystem::absolute(state.packages[i].root, ec).lexically_normal();
@@ -1623,7 +1629,7 @@ static std::expected<void, std::string> step9_root_build_program(PrepareState& s
16231629
// contract hash — and therefore the build.mcpp cache — is unchanged
16241630
// across the move for feature-identical builds.
16251631
bpEnv.features = feature_closure(*state.m, parse_feature_request(state.overrides.features));
1626-
bpEnv.outDir = state.programOutDir(*state.m, bpEnv.features);
1632+
bpEnv.outDir = state.programOutDir(state.workRoot, *state.m, bpEnv.features);
16271633
// mcpp#241 (root): consumer index 0, same owner as the dep loop.
16281634
//
16291635
// AND THE LINK FORM OF EACH DEPENDENCY (#642 E2), to this program only.
@@ -2024,7 +2030,9 @@ static std::expected<void, std::string> step9_member_build_programs(PrepareState
20242030
bpEnv.artifactsDir = pkg.root / "target" / ".build-mcpp";
20252031
if (i < state.activeFeaturesByPackage.size())
20262032
bpEnv.features = state.activeFeaturesByPackage[i];
2027-
bpEnv.outDir = state.programOutDir(pkg.manifest, bpEnv.features);
2033+
// Beside the cache above, which is in the project under a plan's
2034+
// work directory too: `emit build-database` reuses the build's run.
2035+
bpEnv.outDir = state.programOutDir(*state.root, pkg.manifest, bpEnv.features);
20282036
{
20292037
std::map<std::size_t, std::string> linkForms;
20302038
for (auto const& [idx, form] : state.dependencyLinkForms)

‎tests/e2e/652b_an_application_on_android_is_a_shared_library.sh‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ staged=$(ls -d target/dist/myapp-0.1.0-*/ 2>/dev/null | head -1)
157157
echo "mcpp pack --format blob on Android stages lib/libmyapp.so OK"
158158

159159
# ── 5. MCPP_TARGET_MIN_PLATFORM_VERSION: numeric on Android, empty on host ─
160-
# `mcpp::out_dir()` for a build PROGRAM (as opposed to `${mcpp.out_dir}` inside
161-
# an action, which is per-triple) is one path per package,
162-
# `target/.build-mcpp/out/` -- read the value RIGHT AFTER the Android pack
163-
# above, before the host build below writes the SAME file with its own answer.
164-
minplatFile="target/.build-mcpp/out/minplat.txt"
160+
# `mcpp::out_dir()` for a build PROGRAM is one directory per configuration and
161+
# package (mcpp 2026.10.10.1+), so the Android pack and the host build below
162+
# write different files: the newest one is the build that just ran.
163+
newest_minplat() { ls -t target/.build-mcpp/out/*/*/minplat.txt 2>/dev/null | head -1; }
164+
minplatFile=$(newest_minplat)
165165
[ -f "$minplatFile" ] || fail "build.mcpp did not write $minplatFile" pack.log
166166
androidMinplat=$(cat "$minplatFile")
167167
case "$androidMinplat" in
@@ -170,7 +170,9 @@ esac
170170
echo "MCPP_TARGET_MIN_PLATFORM_VERSION is numeric on $TARGET ($androidMinplat) OK"
171171

172172
"$MCPP" build > buildhost.log 2>&1 || fail "the host build failed" buildhost.log
173-
hostMinplat=$(cat "$minplatFile" 2>/dev/null || true)
173+
hostFile=$(newest_minplat)
174+
[ "$hostFile" != "$minplatFile" ] || fail "the host build wrote into the Android configuration's out_dir" buildhost.log
175+
hostMinplat=$(cat "$hostFile" 2>/dev/null || true)
174176
[ -z "$hostMinplat" ] \
175177
|| fail "MCPP_TARGET_MIN_PLATFORM_VERSION was '$hostMinplat', not empty, on the host" buildhost.log
176178
echo "MCPP_TARGET_MIN_PLATFORM_VERSION is empty on the host OK"

‎tests/e2e/810_two_packages_deploy_the_same_bytes_to_one_name.sh‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,17 @@ STAGE_LINES=$(grep -c "^build .*shared/shared\.bin : stage_file" "$G" || true)
137137
[ "$STAGE_LINES" -eq 1 ] \
138138
|| fail "expected exactly one stage_file edge for shared/shared.bin, found $STAGE_LINES" "$G"
139139
STAGE_LINE=$(grep "^build .*shared/shared\.bin : stage_file" "$G")
140-
# The dependency's own build.mcpp runs in the CONSUMING project's tree
141-
# (`target/.build-mcpp/deps/dep@<version>/...`, 111_dep_build_mcpp.sh), so its
142-
# source is distinguished by that path segment; the app's own source is the
143-
# sibling under `target/.build-mcpp/out/...` with no `deps/` segment.
140+
# Each package's build program writes in the CONSUMING project's tree, in
141+
# its own directory of this configuration's output
142+
# (`target/.build-mcpp/out/<configuration>/<package>/...`, mcpp 2026.10.10.1+,
143+
# 111_dep_build_mcpp.sh), so the two sources are told apart by the package
144+
# segment.
144145
INPUTS=$(echo "$STAGE_LINE" | sed 's/^.*: stage_file //')
145146
[ "$(echo "$INPUTS" | wc -w)" -eq 2 ] \
146147
|| fail "expected exactly two inputs on the stage_file edge, got: $INPUTS" "$G"
147-
echo "$INPUTS" | tr ' ' '\n' | grep -qF "deps/dep@" \
148+
echo "$INPUTS" | tr ' ' '\n' | grep -q "\.build-mcpp/out/[0-9a-f]*/[a-z.]*dep/gen/shared\.bin$" \
148149
|| fail "the edge does not list the dependency's source" "$G"
149-
echo "$INPUTS" | tr ' ' '\n' | grep -v "deps/dep@" | grep -q "\.build-mcpp/out/gen/shared\.bin$" \
150+
echo "$INPUTS" | tr ' ' '\n' | grep -q "\.build-mcpp/out/[0-9a-f]*/app/gen/shared\.bin$" \
150151
|| fail "the edge does not also list the app's own source" "$G"
151152
echo "PASS: one stage_file edge lists both sources"
152153

@@ -163,9 +164,9 @@ set -e
163164
[ "$rc" -ne 0 ] || fail "different bytes for one destination were accepted" collision.log
164165
grep -q "disagree" collision.log \
165166
|| fail "the refusal does not say the sources disagree" collision.log
166-
grep -qF "deps/dep@" collision.log \
167+
grep -q '\.build-mcpp/out/[0-9a-f]*/[a-z.]*dep/gen/shared\.bin' collision.log \
167168
|| fail "the refusal does not name the dependency's source" collision.log
168-
grep -q '\.build-mcpp/out/gen/shared\.bin' collision.log \
169+
grep -q '\.build-mcpp/out/[0-9a-f]*/app/gen/shared\.bin' collision.log \
169170
|| fail "the refusal does not name the app's own source" collision.log
170171
grep -q "shared.bin" collision.log || fail "the refusal does not name the destination" collision.log
171172
echo "PASS: different bytes are refused at build time, naming both sources"

‎tests/e2e/867_a_pack_over_several_members_plans_builds_and_runs_programs_once.sh‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,12 @@ int main() {
132132
EOF
133133
done
134134

135+
# A member's build-program output directory: one per configuration and
136+
# package, below the workspace root (mcpp 2026.10.10.1+). Every command here
137+
# builds the one configuration the pack asks for.
138+
outdir() { ls -d target/.build-mcpp/out/*/"$1" 2>/dev/null | head -1; }
135139
# The lines a member's program appended to its log, all of them.
136-
runs() { cat "$1/target/.build-mcpp/out/runs.log" 2>/dev/null; }
140+
runs() { cat "$(outdir "$1")/runs.log" 2>/dev/null; }
137141
count() { runs "$1" | grep -c "$2" || true; }
138142

139143
stage_of() { ls -d "$1"/target/dist/"$1"-0.1.0-* | grep -v -e '\.stage-manifest$' -e '\.tar\.gz$' | head -1; }
@@ -144,11 +148,11 @@ objects() { awk -F'\t' '$4 ~ /\.o$/' "$1/.ninja_log" | wc -l | tr -d ' '; }
144148
# `core` is not a program, so it is not packed, and its program ran once in all:
145149
# in the pass that asks for no format, and never again for a member whose answer
146150
# does not depend on the request.
147-
[ "$(count core '')" = 1 ] || fail "A: core's program ran $(count core '') times, not once" core/target/.build-mcpp/out/runs.log
151+
[ "$(count core '')" = 1 ] || fail "A: core's program ran $(count core '') times, not once" $(outdir core)/runs.log
148152
for m in cli gui; do
149-
[ "$(count $m '')" = 2 ] || fail "A: $m's program ran $(count $m '') times, not once for each pass" $m/target/.build-mcpp/out/runs.log
150-
[ "$(count $m '^format= stage=$')" = 1 ] || fail "A: $m's first pass was told a request" $m/target/.build-mcpp/out/runs.log
151-
[ "$(count $m '^format=zap stage=/')" = 1 ] || fail "A: $m's second pass was not told its format and stage" $m/target/.build-mcpp/out/runs.log
153+
[ "$(count $m '')" = 2 ] || fail "A: $m's program ran $(count $m '') times, not once for each pass" $(outdir $m)/runs.log
154+
[ "$(count $m '^format= stage=$')" = 1 ] || fail "A: $m's first pass was told a request" $(outdir $m)/runs.log
155+
[ "$(count $m '^format=zap stage=/')" = 1 ] || fail "A: $m's second pass was not told its format and stage" $(outdir $m)/runs.log
152156
done
153157
grep -q "Packed .*cli.zap" a.log || fail "A: cli's distributable was not reported" a.log
154158
grep -q "Packed .*gui.zap" a.log || fail "A: gui's distributable was not reported" a.log
@@ -159,15 +163,15 @@ for m in cli gui; do
159163
# The two spellings of one directory are compared as directories: a
160164
# temporary directory may be reached through a symbolic link.
161165
want="$(cd "$(stage_of $m)" && pwd -P)"
162-
saw="$(cat $m/target/.build-mcpp/out/seen-stage.txt)"
166+
saw="$(cat $(outdir $m)/seen-stage.txt)"
163167
[ -d "$saw" ] && [ "$(cd "$saw" && pwd -P)" = "$want" ] \
164168
|| fail "D: $m's program read '$saw', and its tree is '$want'" a.log
165-
grep -qx "member=$m" $m/target/.build-mcpp/out/$m.zap || fail "D: $m's distributable was made for another member" $m/target/.build-mcpp/out/$m.zap
166-
grep -qx "./bin/$m" $m/target/.build-mcpp/out/$m.zap || fail "D: $m's tree did not hold $m's program" $m/target/.build-mcpp/out/$m.zap
169+
grep -qx "member=$m" $(outdir $m)/$m.zap || fail "D: $m's distributable was made for another member" $(outdir $m)/$m.zap
170+
grep -qx "./bin/$m" $(outdir $m)/$m.zap || fail "D: $m's tree did not hold $m's program" $(outdir $m)/$m.zap
167171
done
168-
[ "$(cat cli/target/.build-mcpp/out/seen-stage.txt)" != "$(cat gui/target/.build-mcpp/out/seen-stage.txt)" ] \
172+
[ "$(cat $(outdir cli)/seen-stage.txt)" != "$(cat $(outdir gui)/seen-stage.txt)" ] \
169173
|| fail "D: two members were told one staged tree"
170-
grep -q "gui" cli/target/.build-mcpp/out/cli.zap && fail "D: cli's tree holds gui's program" cli/target/.build-mcpp/out/cli.zap
174+
grep -q "gui" $(outdir cli)/cli.zap && fail "D: cli's tree holds gui's program" $(outdir cli)/cli.zap
171175
echo "ok: D, each member's program reads its own staged tree"
172176

173177
# ── B ────────────────────────────────────────────────────────────────────────
@@ -176,11 +180,11 @@ for m in cli gui; do
176180
mkdir multi/$m single/$m
177181
cp -R "$(stage_of $m)" multi/$m/tree
178182
cp "$(stage_of $m).stage-manifest" multi/$m/stage-manifest
179-
cp $m/target/.build-mcpp/out/$m.zap multi/$m/dist
183+
cp $(outdir $m)/$m.zap multi/$m/dist
180184
"$MCPP" pack -p $m --format zap > b-$m.log 2>&1 || fail "B: mcpp pack -p $m failed" b-$m.log
181185
cp -R "$(stage_of $m)" single/$m/tree
182186
cp "$(stage_of $m).stage-manifest" single/$m/stage-manifest
183-
cp $m/target/.build-mcpp/out/$m.zap single/$m/dist
187+
cp $(outdir $m)/$m.zap single/$m/dist
184188
diff -r multi/$m single/$m > b-$m.diff || fail "B: $m's packed tree differs from the one mcpp pack -p $m makes" b-$m.diff
185189
done
186190
echo "ok: B, each member's tree and distributable are those of mcpp pack -p <member>"

‎tests/e2e/868_a_pack_over_several_members_is_refused_before_anything_is_compiled.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ set -e
287287
[ "$rc" -ne 0 ] || fail "K: a member that claimed nothing was reported as packed" k.log
288288
grep -q "member 'mute': no action claimed --format 'zap'" k.log \
289289
|| fail "K: the failure does not name the member" k.log
290-
[ -n "$(find good -name good.zap | head -1)" ] || fail "K: the member that claimed the format was not packed" k.log
290+
[ -n "$(find target/.build-mcpp/out -path '*/good/good.zap' | head -1)" ] || fail "K: the member that claimed the format was not packed" k.log
291291
grep -q "Packed .*good.zap" k.log || fail "K: the member that was packed was not reported" k.log
292292
echo "ok: K, a member that claimed nothing fails alone, by name, and the others are packed"
293293

‎tests/e2e/870_a_pack_over_several_members_resolves_and_fails_per_member.sh‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ member a tool
9494
member b tool
9595
member c ctool c++20
9696

97-
zap() { echo "$1/target/.build-mcpp/out/$1.zap"; }
97+
# A member's distributable, in its build-program output directory: one per
98+
# configuration and package, below the workspace root (mcpp 2026.10.10.1+).
99+
zap() { ls -d target/.build-mcpp/out/*/"$1"/"$1".zap 2>/dev/null | head -1; }
98100

99101
# ── A ────────────────────────────────────────────────────────────────────────
100102
"$MCPP" pack --workspace --format zap > a.log 2>&1 || fail "A: the pack failed" a.log

0 commit comments

Comments
 (0)