Skip to content

Commit fa9e185

Browse files
committed
A synthesised program is written where a project takes it over; the run record names the contract keys that moved (#797)
- `build_program_dir`: one definition of where a package's build program keeps its record, graph document and synthesised program (the root's and a workspace member's below their own target/, a dependency's below the build's). A synthesised rule program is written there again, as target/.build-mcpp/build.mcpp, the file a project copies to take it over (mcpp-plugins spirv-zero-config; e2e 798 back to its original assertion). - The run record keeps each contract key with the digest of its value (`build.mcpp.ctx`); a run that is not replayed because the contract moved names the keys in the verbose log. - e2e 97 finds the program in the build programs' graph; 913 compares arguments up to the directory above the graph, with one separator.
1 parent ea05a87 commit fa9e185

8 files changed

Lines changed: 84 additions & 18 deletions

‎src/build/build_program.cppm‎

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,40 @@ std::string contract_hash(const std::vector<std::pair<std::string, std::string>>
818818
return mcpp::toolchain::hash_string(s);
819819
}
820820

821+
// THE CONTRACT, KEY BY KEY, beside the run record: `contract_hash` keeps one
822+
// digest, and "the environment contract changed" does not say which variable
823+
// moved. The record names each key with the digest of its value, and the log
824+
// names the keys that differ when a run is not replayed.
825+
std::string contract_keys_path(const fs::path& bdir) {
826+
return (bdir / "build.mcpp.ctx").string();
827+
}
828+
829+
void write_contract_keys(const fs::path& bdir,
830+
const std::vector<std::pair<std::string, std::string>>& e) {
831+
std::ofstream os(contract_keys_path(bdir), std::ios::trunc);
832+
for (auto const& [k, v] : e) os << k << '\t' << mcpp::toolchain::hash_string(v) << '\n';
833+
}
834+
835+
std::string changed_contract_keys(const fs::path& bdir,
836+
const std::vector<std::pair<std::string, std::string>>& e) {
837+
std::map<std::string, std::string> before;
838+
std::ifstream in(contract_keys_path(bdir));
839+
for (std::string line; std::getline(in, line);)
840+
if (auto tab = line.find('\t'); tab != std::string::npos)
841+
before[line.substr(0, tab)] = line.substr(tab + 1);
842+
std::string out;
843+
std::set<std::string> seen;
844+
for (auto const& [k, v] : e) {
845+
seen.insert(k);
846+
auto it = before.find(k);
847+
if (it == before.end() || it->second != mcpp::toolchain::hash_string(v))
848+
out += (out.empty() ? "" : ", ") + k;
849+
}
850+
for (auto const& [k, h] : before)
851+
if (!seen.contains(k)) out += (out.empty() ? "" : ", ") + k;
852+
return out;
853+
}
854+
821855
// The project-relative name of the build output tree ("target" by default), so
822856
// a glob input never walks into what a previous run produced. Empty when the
823857
// output lives outside the project, in which case nothing needs excluding.
@@ -1029,6 +1063,9 @@ std::expected<void, std::string> run_build_program_impl(
10291063
fs::path outDir = env.outDir.empty() ? bdir / "out" : env.outDir;
10301064
auto childEnv = contract_env(root, outDir, env);
10311065
std::string ctxHash = contract_hash(childEnv);
1066+
// What ctxHash is the digest of: the environment and the graph document.
1067+
auto contractEnv = childEnv;
1068+
contractEnv.emplace_back("(graph document)", env.graphDigest);
10321069
// THE GRAPH DOCUMENT'S CONTENT, NOT ITS PATH. The path is the same on
10331070
// every run; what the document says is what the program's answer depends
10341071
// on (#647 E1).
@@ -1060,8 +1097,12 @@ std::expected<void, std::string> run_build_program_impl(
10601097
// directives, no run.
10611098
CacheRecord cache = read_cache(bdir);
10621099
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));
1100+
if (!stale.empty()) {
1101+
std::string keys;
1102+
if (stale == "environment contract") keys = changed_contract_keys(bdir, contractEnv);
1103+
mcpp::log::verbose("build-program", std::format("{}: runs ({}{}{})", who, stale,
1104+
keys.empty() ? "" : ": ", keys));
1105+
}
10651106
if (stale.empty()) {
10661107
if (auto terr = dirs::target_directive_error(m, cache.directives); !terr.empty())
10671108
return std::unexpected(terr);
@@ -1303,7 +1344,10 @@ std::expected<void, std::string> run_build_program_impl(
13031344
report_stated_diagnostics(m.package.name, d);
13041345
// A run kept although it asked for a payload (`plan_only`) is never
13051346
// cached: replaying it would keep the build from ever asking again.
1306-
if (cacheThisRun) write_cache(bdir, root, programHash, compilerHash, ctxHash, d);
1347+
if (cacheThisRun) {
1348+
write_cache(bdir, root, programHash, compilerHash, ctxHash, d);
1349+
write_contract_keys(bdir, contractEnv);
1350+
}
13071351
return {};
13081352
}
13091353

‎src/build/prepare/features.cpp‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1751,10 +1751,6 @@ static std::expected<void, std::string> step6_dependency_build_programs(PrepareS
17511751
// dep build.mcpp sees the SAME active feature set the dep is built
17521752
// with (incl. transitive requests / default-features opt-out).
17531753
auto [req, depDefaultFeatures] = aggregatedRequest(state, i, Instance::Target);
1754-
auto dirSafe = [](std::string s) {
1755-
for (auto& c : s) if (c == '/' || c == '\\' || c == ':') c = '_';
1756-
return s;
1757-
};
17581754
mcpp::build::BuildProgramEnv bpEnv;
17591755
bpEnv.targetTriple = state.resolvedTargetCanonical;
17601756
// Everything the engine already knows and a build program would
@@ -1791,8 +1787,7 @@ static std::expected<void, std::string> step6_dependency_build_programs(PrepareS
17911787
if (auto dit = state.deviceSourcesByPackage.find(pkg.root.string()); dit != state.deviceSourcesByPackage.end())
17921788
bpEnv.deviceSources = dit->second;
17931789
bpEnv.features = feature_closure(pkg.manifest, req, depDefaultFeatures);
1794-
bpEnv.artifactsDir = state.workRoot / "target" / ".build-mcpp" / "deps"
1795-
/ (dirSafe(pkg.manifest.package.name) + "@" + pkg.manifest.package.version);
1790+
bpEnv.artifactsDir = build_program_dir(state, i);
17961791
bpEnv.outDir = state.programOutDir(state.workRoot, pkg.manifest, bpEnv.features);
17971792
bpEnv.genBase = bpEnv.outDir;
17981793
// mcpp#241: this package's resolved dependencies as

‎src/build/prepare/host_graph.cpp‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,21 @@ void write_if_changed(const fs::path& file, const std::string& text) {
492492
// The manifests of one group: the workspace and a member per program.
493493
std::map<std::string, std::string> resolution_pins(const PrepareState& state);
494494

495+
} // namespace
496+
497+
std::filesystem::path build_program_dir(const PrepareState& state, std::size_t i) {
498+
if (i == 0) return state.workRoot / "target" / ".build-mcpp";
499+
auto const& pkg = state.packages[i];
500+
if (pkg.selectedMember || (state.isWorkspaceMemberPackage && state.isWorkspaceMemberPackage(i)))
501+
return pkg.root / "target" / ".build-mcpp";
502+
auto safe = pkg.manifest.package.name;
503+
for (auto& c : safe) if (c == '/' || c == '\\' || c == ':') c = '_';
504+
return state.workRoot / "target" / ".build-mcpp" / "deps"
505+
/ (safe + "@" + pkg.manifest.package.version);
506+
}
507+
508+
namespace {
509+
495510
void write_group(PrepareState& state, const fs::path& dir, const std::vector<Program>& programs,
496511
const std::string& standard, const fs::path& enginePackage,
497512
const std::string& toolchainSpec) {
@@ -568,9 +583,11 @@ void write_group(PrepareState& state, const fs::path& dir, const std::vector<Pro
568583
const auto memberDir = dir / p.member;
569584
fs::path main = p.source.file;
570585
if (main.empty()) {
571-
// Not `build.mcpp` at the member's root: that file would be the
572-
// member's OWN build program.
573-
main = memberDir / "synthesised.mcpp";
586+
// Where it has always been written, beside the package's program
587+
// cache: the file a project copies to its root to take it over.
588+
main = build_program_dir(state, p.index) / "build.mcpp";
589+
std::error_code ec;
590+
fs::create_directories(main.parent_path(), ec);
574591
write_if_changed(main, p.source.synthesised);
575592
}
576593
std::string m = std::format(

‎src/build/prepare/state.cppm‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,11 @@ std::expected<void, std::string> phase4a_graph_load(PrepareState& state);
836836
std::expected<void, std::string> phase4b_graph_worklist(PrepareState& state);
837837
// The lookups the later phases ask of the graph (graph_closures.cpp).
838838
void step4b_define_lookup_closures(PrepareState& state);
839+
// Where the build program of package `i` keeps its files: its cache, its
840+
// graph document, and the program mcpp synthesises for it. The root's and a
841+
// workspace member's are below their own target/; a dependency's is below the
842+
// build's, by name and version (host_graph.cpp).
843+
std::filesystem::path build_program_dir(const PrepareState& state, std::size_t i);
839844
std::expected<void, std::string> phase5_toolchain_after_graph(PrepareState& state);
840845
std::expected<void, std::string> phase6_features_and_host_tools(PrepareState& state);
841846
std::expected<void, std::string> phase9_target_side(PrepareState& state);

‎src/build/prepare/target_side.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,7 +1625,7 @@ static std::expected<void, std::string> step9_root_build_program(PrepareState& s
16251625
// and may be read-only, and the default would write the compiled
16261626
// helper straight into it. Same value as the default when work_dir is
16271627
// unset, so an ordinary build is unchanged.
1628-
bpEnv.artifactsDir = state.workRoot / "target" / ".build-mcpp";
1628+
bpEnv.artifactsDir = build_program_dir(state, 0);
16291629
// Root mode keeps genBase empty: a relative `generated=` from the ROOT
16301630
// package resolves against the package root (the documented contract),
16311631
// not against OUT_DIR.
@@ -2035,7 +2035,7 @@ static std::expected<void, std::string> step9_member_build_programs(PrepareState
20352035
if (auto dit = state.deviceSourcesByPackage.find(pkg.root.string());
20362036
dit != state.deviceSourcesByPackage.end())
20372037
bpEnv.deviceSources = dit->second;
2038-
bpEnv.artifactsDir = pkg.root / "target" / ".build-mcpp";
2038+
bpEnv.artifactsDir = build_program_dir(state, i);
20392039
if (i < state.activeFeaturesByPackage.size())
20402040
bpEnv.features = state.activeFeaturesByPackage[i];
20412041
// Beside the cache above, which is in the project under a plan's

‎tests/e2e/798_a_rule_applies_through_a_source_it_claims.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ rm -f build.mcpp
3333
"$MCPP" build > b1.log 2>&1 || { cat b1.log; echo "FAIL: leg 1 did not build"; exit 1; }
3434
grep -q 'Rules.*example.rules.toy' b1.log || {
3535
cat b1.log; echo "FAIL: leg 1 did not report the rule it applied"; exit 1; }
36-
find target -name synthesised.mcpp | grep -q . || {
36+
find target -name build.mcpp | grep -q . || {
3737
echo "FAIL: leg 1 synthesised no build program"; exit 1; }
3838
out="$("$MCPP" run 2>&1 | tail -1)"
3939
[[ "$out" == *"= 42"* ]] || {

‎tests/e2e/913_the_build_database_describes_the_build_programs.sh‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ assert "app" in sets and sets["app"]["ide"]["configuration"] == "dev", sets.get(
5555
# B
5656
unit = next(u for u in sets["app:build"]["translation-units"] if u["source"].endswith("build.mcpp"))
5757
built = next(e for e in host_cc if e["file"].endswith("build.mcpp"))
58-
norm = lambda args, wd: [a.replace(wd, "<work>") for a in args]
58+
def norm(args, wd):
59+
# One separator, and the directory above the graph -- the planning
60+
# directory of the database, the project of the build -- named alike.
61+
root = wd.replace("\\", "/").split("/target/.build-mcpp/host/")[0]
62+
return [a.replace("\\", "/").replace(root + "/target/", "<root>/target/") for a in args]
5963
assert norm(unit["arguments"], unit["work-directory"]) == norm(built["arguments"], built["directory"]), \
6064
("B", unit["arguments"], built["arguments"])
6165

‎tests/e2e/97_mingw_toolchain.sh‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ int main() {
106106
EOF
107107
out=$("$MCPP" build 2>&1) || { echo "FAIL: build with build.mcpp: $out"; exit 1; }
108108
[[ -f helper-ran ]] || { echo "FAIL: build.mcpp did not run: $out"; exit 1; }
109-
HELPER="target/.build-mcpp/build.mcpp.exe"
109+
# The program as the build programs' graph linked it (mcpp#797).
110+
HELPER=$(find target/.build-mcpp/host -path '*/bin/*' -name 'build-program.exe' -type f | head -1)
110111
[[ -f "$HELPER" ]] || { echo "FAIL: no helper binary at $HELPER"; exit 1; }
111112
if [[ -x "$OBJDUMP" ]]; then
112113
h_imports=$("$OBJDUMP" -p "$HELPER" 2>/dev/null | grep -i "DLL Name" || true)
@@ -117,7 +118,7 @@ fi
117118
HISO="$TMP/hiso"; mkdir -p "$HISO"
118119
cp "$HELPER" "$HISO/"
119120
h_rc=0
120-
(cd "$HISO" && PATH="/usr/bin:/c/Windows/System32" ./build.mcpp.exe >/dev/null 2>&1) || h_rc=$?
121+
(cd "$HISO" && PATH="/usr/bin:/c/Windows/System32" ./build-program.exe >/dev/null 2>&1) || h_rc=$?
121122
[[ $h_rc -eq 0 ]] || {
122123
echo "FAIL: build.mcpp helper does not run without the toolchain bin on PATH (rc=$h_rc)"
123124
exit 1

0 commit comments

Comments
 (0)