Skip to content

Commit 5fde78e

Browse files
committed
E5: the fast path serves on macOS, Windows and SDK-sysroot targets; three e2e made portable (#734)
The runtime validation holds ELF/glibc rules and returned before writing its record on every other target, so the project fast path found no validated artifact snapshot there and every build was planned in full (read on the macOS row: e2e 645, 831 and 832). Where no rule applies, each artifact's stamp is now recorded with a Pass verdict, which is the module's definition of Pass; a relinked artifact still sends the fast path to the full build. e2e 827 no longer uses GNU stat, 828 no longer uses GNU sed -i, and 646 accepts the destination in the placement list when the program has two or more placements (a Windows program places its C++ runtime as well).
1 parent e7a9b4b commit 5fde78e

6 files changed

Lines changed: 74 additions & 12 deletions

‎.agents/docs/2026-09-28-build-cost-foreign-toolsets-and-library-surface-design.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,13 @@ Each item has an e2e:
12351235
graph's text changed. The backend now moves the file's time when it
12361236
confirms the graph (e2e 832).
12371237
Under `-v` each refusal is a sentence rather than the code of its condition.
1238+
11. **E5's reading, and its fix.** On the macOS row every build declined with
1239+
"no validated artifact snapshot is recorded for this build". The runtime
1240+
validation holds ELF/glibc rules only and returned before writing its
1241+
record on every other target, so the fast path never found a snapshot to
1242+
compare. Where no rule applies the record is now written with a Pass verdict
1243+
(the module's own definition: every rule that applies held); a relinked
1244+
artifact still sends the fast path to the full build.
12381245
12391246
**mcpp-plugins 0.17.0.** U1 to U4 on one branch (`feat/734-plugins-0.17.0`).
12401247

‎CHANGELOG.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ SPEC-007 §9 与新的 SPEC-008。配套的 mcpp-plugins 0.17.0 以本版本为
5757
- **包的版本下限(E9)。** `[package] mcpp = ">=<release>"` 与 `[workspace.package] mcpp`;
5858
低于下限的引擎在其他工作之前停止,写出升级命令;只接受 `>=`。
5959
- **`[lib]` 报告未知键(E12)。** 此前拼错的 `path` 被静默接受。
60-
- **快路径说明拒绝原因(E5 的测量部分)。** `-v` 下每个拒绝点以一句话写出其条件。
60+
- **快路径在 macOS、Windows 与自带 sysroot 的目标上生效(E5)。** 运行期校验只含 ELF/glibc 规则,在这些目标上
61+
提前返回而不写校验记录,快路径因此找不到已校验的产物快照,每次构建都走完整路径(macOS 上 e2e 645、831、
62+
832 读出 `no validated artifact snapshot is recorded`)。现在这些目标记录产物的戳记,判定为通过(没有适用的
63+
规则);产物被重新链接时快路径仍交回完整路径。`-v` 下每个拒绝点以一句话写出其条件。
6164

6265
### 兼容性
6366

‎src/build/runtime_validation.cppm‎

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,18 +600,57 @@ ArtifactSnapshot snapshot_link_artifacts(const mcpp::build::BuildPlan& plan) {
600600
return out;
601601
}
602602

603+
// THE RECORD WITHOUT THE RULES (#734 E5). The rules below are ELF/glibc
604+
// rules, and on every other target none of them applies: that is a Pass by
605+
// this module's own definition ("every rule that applies was checked and
606+
// held"). The record is still written, because the project fast path replays
607+
// a build only against a validated snapshot of its artifacts; before this,
608+
// no record was written on macOS, Windows or an SDK-sysroot target, and every
609+
// build there was planned in full (e2e 645, 831 and 832 on the macOS row: "no
610+
// validated artifact snapshot is recorded for this build").
611+
void record_without_rules(const mcpp::build::BuildPlan& plan,
612+
const ArtifactSnapshot& before) {
613+
auto doc = read_cache(plan.outputDir);
614+
bool changed = false;
615+
if (doc.value("schema", 0) != 1
616+
|| doc.value("contract_hash", "") != plan.runtimeBinding.contractHash) {
617+
doc = nlohmann::json::object();
618+
changed = true;
619+
}
620+
doc["schema"] = 1;
621+
doc["contract_hash"] = plan.runtimeBinding.contractHash;
622+
for (auto const& [artifact, oldStamp] : before) {
623+
auto now = stamp(artifact);
624+
if (!now.exists) continue;
625+
const auto key = cache_key(plan, artifact);
626+
const auto fp = fingerprint(artifact, now, plan.runtimeBinding.contractHash);
627+
if (auto a = doc.find("artifacts"); a != doc.end() && a->is_object())
628+
if (auto e = a->find(key); e != a->end() && e->is_object()
629+
&& e->value("fingerprint", "") == fp && e->value("status", "") == "pass")
630+
continue;
631+
ValidatedArtifact v;
632+
v.artifact = artifact;
633+
v.verdict.status = mcpp::platform::elf::RuntimeVerdict::Status::Pass;
634+
store_artifact(doc, key, fp, v);
635+
changed = true;
636+
}
637+
if (changed) write_cache(plan.outputDir, doc);
638+
}
639+
603640
ValidationReport validate_changed_artifacts(
604641
const mcpp::build::BuildPlan& plan,
605642
const ArtifactSnapshot& before) {
606643
ValidationReport report;
607-
if constexpr (!mcpp::platform::is_linux) return report;
644+
if constexpr (!mcpp::platform::is_linux) { record_without_rules(plan, before); return report; }
608645
// Provider dispatch (see mcpp.runtime.binding): what follows is
609646
// ELF/glibc physics, and an identity from another provider — `ucrt@…` on
610647
// Windows — has no rules here rather than a missing glibc.
611648
if (plan.runtimeBinding.platform != "linux"
612649
|| mcpp::platform::runtime::runtime_provider(
613-
plan.runtimeBinding.runtimeId) != "glibc")
650+
plan.runtimeBinding.runtimeId) != "glibc") {
651+
record_without_rules(plan, before);
614652
return report;
653+
}
615654

616655
// AND THE ARTIFACT HAS TO BE ONE THAT COULD LOAD ON THIS MACHINE.
617656
//
@@ -638,8 +677,10 @@ ValidationReport validate_changed_artifacts(
638677
// decision in the tree is right about it except the ones that mean "this
639678
// machine".
640679
if (auto tt = mcpp::toolchain::triple::parse(plan.toolchain.targetTriple);
641-
tt && tt->has_own_sysroot())
680+
tt && tt->has_own_sysroot()) {
681+
record_without_rules(plan, before);
642682
return report;
683+
}
643684

644685
auto doc = read_cache(plan.outputDir);
645686
bool changedCache = false;

‎tests/e2e/646_runtime_deploy_places_files_in_a_directory.sh‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,22 @@ write_manifest '{ from = "assets/icd2/lvp_icd.json", to = "vulkan/icd.d" }'
125125
grep -q 'library_path' "$bin/vulkan/icd.d/lvp_icd.json" 2>/dev/null \
126126
|| fail "the merged destination does not carry the shared bytes" merge.log
127127
G=$(find target -name build.ninja | head -1)
128-
STAGE_LINES=$(grep -c "^build .*vulkan/icd\.d/lvp_icd\.json : stage_file" "$G" 2>/dev/null || true)
129-
[ "$STAGE_LINES" -eq 1 ] \
130-
|| fail "expected exactly one stage_file edge, found $STAGE_LINES" "$G"
131-
grep "^build .*vulkan/icd\.d/lvp_icd\.json : stage_file" "$G" | grep -qF "icd2/lvp_icd.json" \
132-
|| fail "the merged edge does not list the root's second source" "$G"
128+
# One placement is its own `stage_file` edge; two or more (a Windows program
129+
# also places its C++ runtime DLLs) are one `stage_list` edge reading
130+
# placements.list (#734 E4). Either way the destination has both sources.
131+
if grep -q "^build .*vulkan/icd\.d/lvp_icd\.json.* : stage_list" "$G"; then
132+
LIST="$(dirname "$G")/placements.list"
133+
n=$(grep -c "bin/vulkan/icd\.d/lvp_icd\.json$" "$LIST" || true)
134+
[ "$n" -eq 2 ] || fail "expected the destination twice in the placement list, found $n" "$LIST"
135+
grep "bin/vulkan/icd\.d/lvp_icd\.json$" "$LIST" | grep -qF "icd2/lvp_icd.json" \
136+
|| fail "the placement list does not name the root's second source" "$LIST"
137+
else
138+
STAGE_LINES=$(grep -c "^build .*vulkan/icd\.d/lvp_icd\.json : stage_file" "$G" 2>/dev/null || true)
139+
[ "$STAGE_LINES" -eq 1 ] \
140+
|| fail "expected exactly one stage_file edge, found $STAGE_LINES" "$G"
141+
grep "^build .*vulkan/icd\.d/lvp_icd\.json : stage_file" "$G" | grep -qF "icd2/lvp_icd.json" \
142+
|| fail "the merged edge does not list the root's second source" "$G"
143+
fi
133144
echo "identical-bytes merge OK"
134145

135146
# ── 4. A destination outside the executable's directory ───────────────────

‎tests/e2e/827_many_placements_run_as_one_process.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ after=$(grep -c "placements\|data/f" "$OUT/.ninja_log" || true)
6262
[ "$before" = "$after" ] || fail "S2: a build with no placement change ran the placement edge" "$OUT/.ninja_log"
6363

6464
# S3
65-
t2=$(stat -c %Y "$DEST2")
65+
touch -r "$DEST2" s3.ref # a reference file, since the stat options differ between GNU and BSD
6666
sleep 1.1
6767
echo changed > data/f1.txt
6868
touch src/main.cpp
6969
"$MCPP" build > s3.log 2>&1 || fail "S3: the build failed" s3.log
7070
grep -qx changed "$DEST1" || fail "S3: the changed source did not arrive" "$DEST1"
71-
[ "$(stat -c %Y "$DEST2")" = "$t2" ] || fail "S3: an unchanged destination was rewritten" s3.log
71+
[ -z "$(find "$DEST2" -newer s3.ref)" ] || fail "S3: an unchanged destination was rewritten" s3.log
7272

7373
# S4
7474
program 1

‎tests/e2e/828_a_library_states_its_interface.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ cat >> mcpp.toml <<'EOF'
5858
[lib]
5959
path = "src/flatlib.cppm"
6060
EOF
61-
sed -i 's|sources = \["src/alpha.cppm", "src/beta.cppm"\]|sources = ["src/flatlib.cppm", "src/alpha.cppm", "src/beta.cppm"]|' mcpp.toml
61+
sed 's|sources = \["src/alpha.cppm", "src/beta.cppm"\]|sources = ["src/flatlib.cppm", "src/alpha.cppm", "src/beta.cppm"]|' mcpp.toml > mcpp.toml.new && mv mcpp.toml.new mcpp.toml # BSD sed has no bare -i
6262
printf 'export module probe.flatlib;\nexport import Alpha;\nexport import Beta;\n' > src/flatlib.cppm
6363
"$MCPP" pack flatlib > i2.log 2>&1 || fail "I2: the pack failed" i2.log
6464
grep -q "does not ship" i2.log && fail "I2: W2 appeared with a facade" i2.log

0 commit comments

Comments
 (0)