Skip to content

Commit 1922a46

Browse files
committed
dist-web: copy through ${mcpp.self} stage instead of cp
The two copy steps become argv { "${mcpp.self}", "stage", "--verify", "content", "--output", dst, src }, the same copier every stage_file edge in build.ninja already runs: it creates the destination's parent, compares content and writes only on a difference. Plan-time create_directories is removed with it. The POSIX-only note leaves the header; this member's floor rises to the mcpp release that carries ${mcpp.self} and mcpp stage's argument shape as an engine contract. Design record: .agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md, §4.
1 parent f419584 commit 1922a46

1 file changed

Lines changed: 40 additions & 23 deletions

File tree

‎dist/web.cppm‎

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,36 @@
4545
// inside a directory named for a browser, which is a worse failure than a
4646
// refusal naming the one target this format serves.
4747
//
48-
// `cp` PER FILE, ARGV ONLY, NO SHELL -- AND THAT IS WHY THIS MEMBER IS
49-
// POSIX-HOST ONLY FOR NOW. `dist/appimage.cppm`'s own copies are single
48+
// THE ENGINE COPIES, NOT `cp`. Each staged file was one `cp SRC DST` action,
49+
// argv only, no shell -- exactly the shape `mcpp::action` is built for (a
50+
// graph edge per file, skippable on a cache hit) -- and that made this
51+
// member POSIX-host only, because neither precedent (`dist/appimage.cppm`'s
5052
// files handed straight to `appimagetool`'s own argument list; `dist/
51-
// apple.cppm` copies whole directories with `ditto`, which exists only on
52-
// macOS. Neither precedent is a portable multi-file copier this member
53-
// could reuse on Windows, so it declares one `cp SRC DST` action per file in
54-
// the discovered set instead of shelling out to a recursive copy -- exactly
55-
// the shape `mcpp::action` is built for (a graph edge per file, skippable on
56-
// a cache hit), and the one the design record's implementation notes accept
57-
// ("one cp/copy per file is acceptable"). The README states the POSIX-host
58-
// limitation; lifting it needs either a `copy`-argv branch on the host OS or
59-
// a small copier this member carries itself, and neither is written here
60-
// because nothing in this collection has needed one yet.
53+
// apple.cppm`'s directories copied with `ditto`, macOS only) is a portable
54+
// multi-file copier. Lifting that needed either a `copy`-argv branch on the
55+
// host OS or a small copier this member carried itself, and both are the
56+
// wrong shape: `cmd /c copy` is a shell, is the 8191-character limit, and is
57+
// the switch-quoting this repository has already been bitten by twice; a
58+
// copier carried by the member is a host tool sub-build (#355) for one `cp`.
59+
//
60+
// The engine already has the copier. `mcpp stage --output <dst> <src>` is
61+
// the subcommand every `stage_file` edge in `build.ninja` already runs: it
62+
// creates the destination's parent, compares content and writes only on
63+
// difference. `${mcpp.self}`, an action argv substitution for the engine's
64+
// own absolute path, is what lets an action NAME it, so each copy step is
65+
// `{ "${mcpp.self}", "stage", "--verify", "content", "--output", dst, src }`
66+
// -- `--verify content` spelled out rather than defaulted, because a
67+
// contract must not depend on which of "the help text's default" (`size`)
68+
// and "the code's default" (`content`) a reader believes. Plan-time
69+
// `create_directories` is gone with it: `stage` creates the destination's
70+
// parent itself.
71+
//
72+
// `${mcpp.self}` AND `mcpp stage`'S ARGUMENT SHAPE ARE THE ENGINE CONTRACT
73+
// SINCE 2026.9.13.1, not a convenience this member happens to use -- see
74+
// docs/30's substitution table in that release. This member's floor is that
75+
// release for exactly this reason: an older engine leaves `${mcpp.self}`
76+
// literal in the command, and the action fails at run time with a
77+
// not-found for a path that reads as a token.
6178
//
6279
// WHY `index.html` IS WRITTEN AT PLAN TIME TO A SIDE FILE AND COPIED, RATHER
6380
// THAN WRITTEN DIRECTLY TO ITS FINAL PATH. `dist/apple.cppm`'s own header
@@ -317,14 +334,9 @@ inline plan plan_for(options opt = {}) {
317334

318335
for (auto const& rel : relFiles) {
319336
const std::string dst = webDir + "/" + rel;
320-
// Directories are created here, at plan time -- cheap (a handful of
321-
// path segments, never hundreds of megabytes) and the same trade
322-
// `write_if_different` already makes for `index.html`'s own parent.
323-
// The CONTENT copy is the action; an empty directory existing a
324-
// build early is not a cache-visible effect.
325-
std::error_code ec;
326-
std::filesystem::create_directories(
327-
std::filesystem::path(dst).parent_path(), ec);
337+
// No `create_directories` here: `mcpp stage` creates the
338+
// destination's parent itself, which is the part of this step the
339+
// engine now does that the member used to.
328340

329341
step s;
330342
s.id = "mcpp.dist.web.file";
@@ -334,9 +346,13 @@ inline plan plan_for(options opt = {}) {
334346
// program just read `stageBin` from -- the same reasoning every
335347
// other member gives: the path in the graph and the path here
336348
// cannot disagree, and naming it earns this action the engine's
337-
// automatic dependency on the staged tree's manifest.
349+
// automatic dependency on the staged tree's manifest. `${mcpp.self}`
350+
// is the same substitution family, naming the engine's own
351+
// executable so this action's command is an argv the engine
352+
// interprets on every host, with no shell and no host-specific copy
353+
// tool.
338354
const std::string src = "${mcpp.stage_dir}/bin/" + rel;
339-
s.argv = { "cp", src, dst };
355+
s.argv = { "${mcpp.self}", "stage", "--verify", "content", "--output", dst, src };
340356
s.inputs = { src };
341357
s.outputs = { dst };
342358
p.steps.push_back(std::move(s));
@@ -346,7 +362,8 @@ inline plan plan_for(options opt = {}) {
346362
page.id = "mcpp.dist.web.index";
347363
page.role = "artifact";
348364
page.description = "INDEX.HTML";
349-
page.argv = { "cp", indexSrc, webDir + "/index.html" };
365+
page.argv = { "${mcpp.self}", "stage", "--verify", "content", "--output",
366+
webDir + "/index.html", indexSrc };
350367
page.inputs = { indexSrc };
351368
page.outputs = { webDir + "/index.html" };
352369
p.steps.push_back(std::move(page));

0 commit comments

Comments
 (0)