Skip to content

Commit ea05a87

Browse files
committed
A host sub-build targets the host; one directory, every spelling; tests find the target's build directory (#797)
- `BuildOverrides::host_target`: the build programs' graph and a host tool's sub-build target the machine doing the build; `[build] target` and the configured default target name the target of the build that asked (e2e 97 on a Windows host whose default target is x86_64-windows-gnu). - The engine package and a workspace member are recognised through any spelling of their directory (macOS /var and /private/var, Windows 8.3 and long names): a member reached through another spelling was named with the default namespace and its program was not found (e2e 857, 858, 913). - The verbose log states why a build program's cached run is not used. - The error for a missing program names the programs the plan has. - Tests that look up a build directory under target/ exclude the build programs' graph under target/.build-mcpp/host; 92 and 857 read the new object names and either path separator.
1 parent 16cc555 commit ea05a87

111 files changed

Lines changed: 218 additions & 173 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/build/build_program.cppm‎

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -919,37 +919,44 @@ CacheRecord read_cache(const fs::path& bdir) {
919919
return r;
920920
}
921921

922-
// Decide whether the cached run is still valid (so we can skip recompiling/running).
923-
bool cache_fresh(const fs::path& root, const fs::path& bdir, const CacheRecord& c,
924-
const std::string& programHash, const std::string& compilerHash,
925-
const std::string& ctxHash) {
926-
if (!c.loaded) return false;
927-
if (c.epoch != dirs::kCacheEpoch) return false; // pre-epoch entries rerun once
928-
if (c.unknownRecord) return false;
929-
if (c.programHash != programHash) return false;
930-
if (c.compilerHash != compilerHash) return false;
931-
if (c.ctxHash != ctxHash) return false; // pre-G3 caches (no ctx line) rerun once
922+
// Why the cached run is not valid, for the log; empty when it is.
923+
std::string cache_stale_reason(const fs::path& root, const fs::path& bdir, const CacheRecord& c,
924+
const std::string& programHash, const std::string& compilerHash,
925+
const std::string& ctxHash) {
926+
if (!c.loaded) return "no record";
927+
if (c.epoch != dirs::kCacheEpoch) return "record epoch"; // pre-epoch entries rerun once
928+
if (c.unknownRecord) return "unknown record line";
929+
if (c.programHash != programHash) return "program identity " + c.programHash + " -> " + programHash;
930+
if (c.compilerHash != compilerHash) return "record shape";
931+
if (c.ctxHash != ctxHash) return "environment contract"; // pre-G3 caches rerun once
932932
for (auto const& [h, path] : c.inputs)
933-
if (mcpp::toolchain::hash_file(abs_against_root(root, path)) != h) return false;
933+
if (mcpp::toolchain::hash_file(abs_against_root(root, path)) != h) return "input " + path;
934934
for (auto const& [h, name] : c.envs)
935-
if (mcpp::toolchain::hash_string(env_value(name)) != h) return false;
935+
if (mcpp::toolchain::hash_string(env_value(name)) != h) return "environment variable " + name;
936936
// #359: the path SET behind each declared glob. A file appearing or
937937
// disappearing changes it; editing one does not (that is what the `in`
938938
// entries above are for).
939939
if (!c.globs.empty()) {
940940
auto outName = output_dir_name(root, bdir);
941941
for (auto const& [h, pattern] : c.globs)
942-
if (dirs::glob_fingerprint(root, pattern, outName) != h) return false;
942+
if (dirs::glob_fingerprint(root, pattern, outName) != h) return "glob " + pattern;
943943
}
944944
// A declared output that vanished invalidates the cache. Driven off the
945945
// table's mustExistAfterRun so a future output-shaped directive is covered
946946
// without editing this function.
947947
for (auto const& def : dirs::kTable) {
948948
if (!def.mustExistAfterRun) continue;
949949
for (auto const& p : c.directives.at(def.slot))
950-
if (!fs::exists(abs_against_root(root, p))) return false;
950+
if (!fs::exists(abs_against_root(root, p))) return "output " + p;
951951
}
952-
return true;
952+
return {};
953+
}
954+
955+
// Decide whether the cached run is still valid (so we can skip recompiling/running).
956+
bool cache_fresh(const fs::path& root, const fs::path& bdir, const CacheRecord& c,
957+
const std::string& programHash, const std::string& compilerHash,
958+
const std::string& ctxHash) {
959+
return cache_stale_reason(root, bdir, c, programHash, compilerHash, ctxHash).empty();
953960
}
954961

955962

@@ -1052,7 +1059,10 @@ std::expected<void, std::string> run_build_program_impl(
10521059
// Fast path: declared inputs + contract unchanged → reapply cached
10531060
// directives, no run.
10541061
CacheRecord cache = read_cache(bdir);
1055-
if (cache_fresh(root, bdir, cache, programHash, compilerHash, ctxHash)) {
1062+
const auto stale = cache_stale_reason(root, bdir, cache, programHash, compilerHash, ctxHash);
1063+
if (!stale.empty())
1064+
mcpp::log::verbose("build-program", std::format("{}: runs ({})", who, stale));
1065+
if (stale.empty()) {
10561066
if (auto terr = dirs::target_directive_error(m, cache.directives); !terr.empty())
10571067
return std::unexpected(terr);
10581068
// #622 A4. Checked on the cache-hit path too, so a `deploy` a fresh

‎src/build/engine_package.cppm‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,17 @@ fs::path root() { return mcpp::home::root() / "engine"; }
105105
fs::path package_dir() { return root() / std::string(mcpp::MCPP_VERSION) / "modules"; }
106106

107107
bool is_engine_package(const fs::path& dir) {
108-
auto rel = dir.lexically_normal().lexically_relative(root().lexically_normal());
109-
return !rel.empty() && *rel.begin() != "..";
108+
auto inside = [](const fs::path& a, const fs::path& b) {
109+
auto rel = a.lexically_normal().lexically_relative(b.lexically_normal());
110+
return !rel.empty() && *rel.begin() != "..";
111+
};
112+
if (inside(dir, root())) return true;
113+
// One directory has more than one spelling: `/var` is `/private/var` on
114+
// macOS, and Windows has HOME and USERPROFILE, drive-letter case, 8.3 names.
115+
std::error_code e1, e2;
116+
const auto cd = fs::weakly_canonical(dir, e1);
117+
const auto cr = fs::weakly_canonical(root(), e2);
118+
return !e1 && !e2 && inside(cd, cr);
110119
}
111120

112121
std::pair<std::string, std::string> module_texts() {

‎src/build/prepare.cppm‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,10 @@ export struct BuildOverrides {
811811
// not apply to them.
812812
std::map<std::string, std::string> resolution_pins;
813813
bool build_program_graph = false;
814+
// The build targets the machine doing it: a sub-build of build programs
815+
// or of a host tool. Neither `[build] target` nor the configured default
816+
// target applies, since both name the target of the build that asked.
817+
bool host_target = false;
814818
// The project whose `.mcpp/` holds the packages of its custom indices.
815819
// Empty = the project being built. The build programs' graph names the
816820
// build that asked, whose indices it shares and whose store already holds

‎src/build/prepare/driver.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ void read_build_programs_marker(BuildOverrides& ov) {
112112
std::ifstream in(root / ".mcpp-build-programs");
113113
if (!in) return;
114114
ov.build_program_graph = true;
115+
ov.host_target = true;
115116
std::string line;
116117
while (std::getline(in, line)) {
117118
const auto eq = line.find('=');

‎src/build/prepare/features.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,7 @@ step6_build_tool(PrepareState& state, HostToolCtx& ctx) {
14801480
sub.work_dir = mcpp::build::tool_store::scratch_dir(
14811481
ctx.cacheRoot, ctx.entry, state.workRoot);
14821482
sub.target_triple = ""; // HOST — the whole point
1483+
sub.host_target = true;
14831484
sub.toolchain = ctx.toolTcSpec;
14841485
sub.profile = "release";
14851486
sub.cache_mode = state.overrides.cache_mode;

‎src/build/prepare/host_graph.cpp‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ build_group(PrepareState& state, const fs::path& dir, const std::vector<Program>
652652
sub.project_root = dir;
653653
sub.work_dir = dir;
654654
sub.target_triple = "";
655+
sub.host_target = true;
655656
sub.toolchain = tc.spec;
656657
sub.profile = "build-program";
657658
sub.cache_mode = state.overrides.cache_mode;
@@ -711,10 +712,17 @@ build_group(PrepareState& state, const fs::path& dir, const std::vector<Program>
711712
}
712713
}
713714
for (auto const* g : goals)
714-
if (!binaries.contains(g->member))
715+
if (!binaries.contains(g->member)) {
716+
std::string planned;
717+
for (auto const& lu : ctx->plan.linkUnits)
718+
planned += std::format("{}{}:{}", planned.empty() ? "" : ", ",
719+
lu.memberOf.empty() ? lu.package : lu.memberOf,
720+
lu.targetName);
715721
return std::unexpected(std::format(
716-
"the build programs' plan has no program for '{}'{}",
717-
qualified(manifest_of(state, g->index)), reproduce()));
722+
"the build programs' plan has no program for '{}' (member {}; planned: {}){}",
723+
qualified(manifest_of(state, g->index)), g->member,
724+
planned.empty() ? std::string("nothing") : planned, reproduce()));
725+
}
718726

719727
auto be = mcpp::build::make_ninja_backend();
720728
mcpp::build::BuildOptions bopt;

‎src/build/prepare/toolchain.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,12 +1010,13 @@ static std::expected<void, std::string> step1_target_and_static_overrides(Prepar
10101010
// Target-axis default resolution when no --target flag was passed:
10111011
// [build] target (project default, ≙ cargo build.target) >
10121012
// [toolchain] default_target (global config) > host.
1013-
if (state.overrides.target_triple.empty() && !state.m->buildConfig.target.empty())
1013+
if (state.overrides.target_triple.empty() && !state.m->buildConfig.target.empty()
1014+
&& !state.overrides.host_target)
10141015
state.overrides.target_triple = state.m->buildConfig.target;
10151016
// Remembered, not requested: this one came out of the global config, so
10161017
// it must not outrank anything the user wrote down (see the pin below).
10171018
bool targetFromGlobalDefault = false;
1018-
if (state.overrides.target_triple.empty()) {
1019+
if (state.overrides.target_triple.empty() && !state.overrides.host_target) {
10191020
if (auto cfg = state.get_cfg(true); cfg && !(*cfg)->defaultTarget.empty()) {
10201021
state.overrides.target_triple = (*cfg)->defaultTarget;
10211022
targetFromGlobalDefault = true;

‎src/build/prepare/windows_resources.cpp‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -526,9 +526,19 @@ std::expected<void, std::string> step13_windows_resources(PrepareState& state, B
526526
// Read by W3 (a member's non-public modules).
527527
std::string workspace_member_of(const PrepareState& state, const std::filesystem::path& root) {
528528
if (!state.wsManifest || state.runtimeWorkspaceRoot.empty()) return {};
529-
const auto rel = root.lexically_normal()
530-
.lexically_relative(state.runtimeWorkspaceRoot.lexically_normal())
531-
.generic_string();
529+
auto relative = [](const std::filesystem::path& a, const std::filesystem::path& b) {
530+
return a.lexically_normal().lexically_relative(b.lexically_normal()).generic_string();
531+
};
532+
auto rel = relative(root, state.runtimeWorkspaceRoot);
533+
// One directory has more than one spelling (`/var` is `/private/var` on
534+
// macOS; a symlinked home; HOME and USERPROFILE on Windows): a member
535+
// reached through the other spelling is the same member.
536+
if (rel.empty() || rel.starts_with("..")) {
537+
std::error_code e1, e2;
538+
const auto cr = std::filesystem::weakly_canonical(root, e1);
539+
const auto cw = std::filesystem::weakly_canonical(state.runtimeWorkspaceRoot, e2);
540+
if (!e1 && !e2) rel = relative(cr, cw);
541+
}
532542
if (rel.empty() || rel == "." || rel.starts_with("..")) return {};
533543
for (auto const& m : state.wsManifest->workspace.members) {
534544
if (m == rel) return rel;

‎tests/e2e/03_multi_module.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ out=$("$MCPP" run 2>&1)
3636
[[ "$out" == *"multi-module build OK"* ]] || { echo "main println missing: $out"; exit 1; }
3737

3838
# Verify build.ninja contents
39-
build_ninja="$(find target -name build.ninja | head -1)"
39+
build_ninja="$(find target -name build.ninja -not -path '*/.build-mcpp/*' | head -1)"
4040
grep -q "gcm.cache/multi.greeter.gcm" "$build_ninja" || { echo "ninja missing greeter BMI"; exit 1; }
4141
grep -q "gcm.cache/std.gcm" "$build_ninja" || { echo "ninja missing std BMI"; exit 1; }
4242

‎tests/e2e/07_static_library.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ AR="${SANDBOX_AR:-ar}"
4545
"$AR" t "$archive" | grep -q '\.o$' || { echo ".a contains no objects"; exit 1; }
4646

4747
# build.ninja must use cxx_archive rule for the lib target
48-
ninja_file="$(find target -name build.ninja)"
48+
ninja_file="$(find target -name build.ninja -not -path '*/.build-mcpp/*')"
4949
grep -q 'bin/libmathlib.a : cxx_archive' "$ninja_file" || {
5050
echo "build.ninja did not use cxx_archive for the lib target"; exit 1; }
5151

0 commit comments

Comments
 (0)