Skip to content

Commit 94dba4f

Browse files
committed
dist-web: a deploy'd file lands at its staged path
tests/web-consumer/check-web-plan.sh now runs the real one-command form, `mcpp pack --format web --target wasm32-emscripten`, in place of the split/workaround this fixture needed while every build.mcpp failed to compile under that target. That defect is fixed on mcpp (commit 23974c5e, "a build program under a target row's pin resolves the host's compiler, not the row's", #622 e2e 657); verified here with the fix binary. Root cause of the nested `assets/greeting.txt/greeting.txt`, found while re-running under the real command: `tests/web-consumer/ build.mcpp` called `mcpp::deploy(generated, "assets/greeting.txt")`, treating `to` as the full destination FILE path. `to` names a DIRECTORY, executable-relative -- `types.cppm`'s own `DeployEntry` comment ("a file placed in a directory relative to the executable") and the design record's own criterion (`to = "myapp.resources/ huxerui"` lands the file at `bin/myapp.resources/huxerui/ resources.bin`, keeping the source's own basename) -- so the file was deployed INTO a directory literally named `greeting.txt`, nesting it under its own basename a second time. Confirmed independent of dist-web: `mcpp pack --format dir --target wasm32-emscripten` on the unmodified fixture produces the identical nested staged tree with no dist-web code involved at all. The fix is the fixture's deploy call (`to = "assets"`, not `to = "assets/greeting.txt"`); dist/web.cppm's own copy needed no change -- it already creates each destination's PARENT directory and mirrors the staged tree's actual relative paths rather than reconstructing them from a rule (this member has no accessor for a deploy directive's `to` and cannot see a caller's mistake from here). Its header comment gains a paragraph stating the `to` contract and pointing at this fixture as the worked example, so the next reader does not repeat it. check-web-plan.sh's own assertion is now the exact one asked for: `assets/greeting.txt` is a regular file at exactly that relative path, and no directory of that name exists.
1 parent fa9ab6e commit 94dba4f

4 files changed

Lines changed: 83 additions & 131 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,13 +1069,14 @@ jobs:
10691069
working-directory: tests/ios-app-consumer
10701070
run: MCPP="$MCPP" ./check-ios-plan.sh
10711071

1072-
# dist-web (#622 B3), end to end, worked around one engine defect --
1073-
# see `tests/web-consumer/check-web-plan.sh`'s own header for the
1074-
# measured cause (every `build.mcpp` fails to compile under `--target
1075-
# wasm32-emscripten` on this engine floor) and exactly what is real
1076-
# versus simulated in what follows. `mcpp toolchain install` first,
1077-
# because this job's `~/.mcpp` cache does not carry the emsdk payload
1078-
# the way the local sandbox this member was developed against does.
1072+
# dist-web (#622 B3), end to end, the real `mcpp pack --format web
1073+
# --target wasm32-emscripten` -- see `tests/web-consumer/
1074+
# check-web-plan.sh`'s own header for the host-toolchain defect this
1075+
# needed fixed first (mcpp commit 23974c5e, #622 e2e 657) and the
1076+
# MCPP_VERSION this step therefore needs at least. `mcpp toolchain
1077+
# install` first, because this job's `~/.mcpp` cache does not carry
1078+
# the emsdk payload the way the local sandbox this member was
1079+
# developed against does.
10791080
- name: Install the emsdk payload for dist-web
10801081
run: "$MCPP" toolchain install emsdk 6.0.9
10811082

‎dist/web.cppm‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@
2222
// no use for, and to write the one file neither the link nor `mcpp::deploy`
2323
// produces: a page that loads the script.
2424
//
25+
// A DEPLOY'D FILE'S RELATIVE PATH IS TAKEN FROM THE STAGED TREE, AND NOT
26+
// RECOMPUTED FROM `to`, ON PURPOSE. `mcpp::deploy(from, to)`'s `to` names a
27+
// DIRECTORY, executable-relative -- `types.cppm`'s own `DeployEntry` comment
28+
// ("a file placed in a directory relative to the executable") and the design
29+
// record's own criterion (`to = "myapp.resources/huxerui"` with `from =
30+
// ".../resources.bin"` lands at `bin/myapp.resources/huxerui/resources.bin`,
31+
// keeping `from`'s own basename) -- so a caller who passes a full destination
32+
// FILE path as `to` (as `tests/web-consumer/build.mcpp` once did, by mistake)
33+
// gets exactly that: the file deployed INTO a directory of that name,
34+
// nested under its own basename a second time. This member has no accessor
35+
// for a deploy directive's `to` and cannot see that mistake from here; what
36+
// it CAN do, and does, is mirror the staged tree's actual relative paths
37+
// faithfully rather than reconstructing them from a rule it would then have
38+
// to get right twice. A caller's `to` is therefore the whole of the
39+
// contract, and this member's own fixture is the worked example of getting
40+
// it right: `to = "assets"`, not `to = "assets/greeting.txt"`.
41+
//
2542
// REFUSED BY NAME ON EVERY OTHER TARGET, deliberately, and not merely
2643
// because no other row happens to produce a `.js` launcher: `--format web`
2744
// on an ELF or Mach-O row would otherwise silently ship a native binary

‎tests/web-consumer/build.mcpp‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,22 @@ import mcpp.dist.web;
55
int main() {
66
// A file this build program itself produces, deployed beside the
77
// launcher (#622 A4) so the fixture's own listing check has something
8-
// past the stem family to find. `mcpp::deploy`'s `to` is
9-
// executable-relative on every format; on this one that is `bin/`, and
10-
// `mcpp.dist.web` maps it to the same relative path at the web
11-
// directory's root (see dist/web.cppm's header comment).
8+
// past the stem family to find. `mcpp::deploy`'s `to` NAMES A
9+
// DIRECTORY, executable-relative -- the same rule `[[runtime.deploy]]`
10+
// enforces (`types.cppm`'s own `DeployEntry` comment: "a file placed in
11+
// a directory relative to the executable"), and the design record's own
12+
// criterion for this directive: `to = "myapp.resources/huxerui"` with
13+
// `from = ".../resources.bin"` lands at
14+
// `bin/myapp.resources/huxerui/resources.bin`, keeping `from`'s own
15+
// basename rather than being renamed to `to`'s last component. So `to`
16+
// here is `"assets"`, not `"assets/greeting.txt"` -- the latter would
17+
// deploy the file INTO a directory literally named `greeting.txt`,
18+
// landing it at `bin/assets/greeting.txt/greeting.txt`, which is what an
19+
// earlier revision of this fixture did and what first exposed the
20+
// shape of this rule.
1221
std::string generated = std::string(mcpp::out_dir()) + "/greeting.txt";
1322
if (std::ofstream out(generated); out) out << "hello from build.mcpp\n";
14-
mcpp::deploy(generated.c_str(), "assets/greeting.txt");
23+
mcpp::deploy(generated.c_str(), "assets");
1524

1625
mcpp::dist::web::options opt;
1726
opt.target = "web-consumer";
Lines changed: 44 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,43 @@
11
#!/usr/bin/env bash
2-
# End-to-end check for `dist-web` (#622 B3), worked around one engine defect.
2+
# End-to-end check for `dist-web` (#622 B3): the one-command form.
33
#
4-
# THE INTENDED FORM IS ONE COMMAND, AND IT IS NOT WHAT THIS SCRIPT RUNS:
4+
# mcpp pack --format web --target wasm32-emscripten
55
#
6-
# mcpp pack --target wasm32-emscripten --format web
6+
# An earlier revision of this script worked around a host-toolchain defect
7+
# that made every `build.mcpp` -- not only this member's -- fail to compile
8+
# under `--target wasm32-emscripten` (prepare.cppm's `host_tc_for_build_program`
9+
# resolved the row's own default pin as if it were a host-native compiler).
10+
# That is fixed (mcpp commit 23974c5e, "a build program under a target row's
11+
# pin resolves the host's compiler, not the row's", #622 e2e 657), so this
12+
# script now runs the real command directly and asserts its real output.
713
#
8-
# On the engine this member was developed and reviewed against
9-
# (mcpp 2026.9.12.2, built from `feat/622-ui-framework-on-three-rows`),
10-
# EVERY `build.mcpp` -- not only this member's -- fails to compile under
11-
# `--target wasm32-emscripten`:
12-
#
13-
# error: build.mcpp failed to compile (exit 1):
14-
# ... AssertionError (emcc.py, compile_source_file, phase_compile_inputs)
15-
#
16-
# Measured cause: `prepare.cppm`'s `host_tc_for_build_program` resolves the
17-
# HOST toolchain for `build.mcpp` from the closure-captured `tcSpec`, read
18-
# LAZILY at the point a `build.mcpp` is found to exist -- but the row's own
19-
# default toolchain pin (`tcSpec = targetPinCandidate;`, prepare.cppm
20-
# around line 7233, the same assignment the "target default for
21-
# wasm32-emscripten, replacing your gcc@16.1.0" message reports) has ALREADY
22-
# overwritten that variable by the time this runs, for every target whose
23-
# row carries one. `em++` cannot compile a host-native binary under any
24-
# invocation, so `build.mcpp` is asked to compile itself with a compiler
25-
# that structurally cannot produce a program the host can run: this is not
26-
# specific to `dist-web`, `mcpp.plugins`, or this fixture -- a project whose
27-
# ENTIRE `build.mcpp` is `import std; int main(){}` fails identically.
28-
# `riscv64`/Android rows are not obviously affected, because their default
29-
# pins are cross-CONFIGURATIONS of a compiler family (gcc, clang) that also
30-
# has a native mode; `emsdk`'s em++ has none. Reported rather than worked
31-
# around in the engine: see this task's final report for the full
32-
# repro and the suggested fix site.
33-
#
34-
# WHAT THIS SCRIPT MEASURES INSTEAD, split across the one seam the defect
35-
# does not reach:
36-
#
37-
# 1. A REAL wasm32-emscripten build, of the SAME `src/main.cpp`, through a
38-
# throwaway package with NO `build.mcpp` at all -- unaffected, because
39-
# the defect is specific to a build.mcpp existing. `mcpp pack --format
40-
# dir` stages the real `.js`/`.wasm` pair the emsdk payload produced.
41-
# 2. This fixture's OWN `build.mcpp`, compiled for the HOST (an ordinary
42-
# `mcpp build`, no `--target`) -- also unaffected, because the defect
43-
# is specific to a CROSS target. This compiles to an ordinary ELF at
44-
# `target/.build-mcpp/build.mcpp.bin`.
45-
# 3. That compiled binary, run DIRECTLY (bypassing mcpp's own
46-
# orchestration) with the environment contract `--target
47-
# wasm32-emscripten --format web` would have set -- `mcpp::target_os()`
48-
# and its siblings are `std::getenv` and nothing else
49-
# (`hostprogram.cppm`), so this exercises exactly the code `plan_for`
50-
# and `submit` run, against the REAL staged tree from step 1 plus one
51-
# hand-placed file standing in for `mcpp::deploy` (which, like every
52-
# `build.mcpp` directive, needs the orchestrator this defect removes).
53-
# 4. Every `mcpp:action=` line step 3 printed, parsed with `jq` and
54-
# EXECUTED by this script -- standing in for ninja, which never ran.
55-
#
56-
# The resulting `target/web/` directory, and the fact that `node` runs the
57-
# `.js` inside it and prints `1-2-3`, are real; only the orchestration that
58-
# would normally produce them is simulated here.
59-
#
60-
# Usage: MCPP=<fresh mcpp> ./check-web-plan.sh (run from this directory)
14+
# Usage: MCPP=<mcpp with the 23974c5e fix> ./check-web-plan.sh (run from
15+
# this directory)
6116
set -e
6217

6318
MCPP="${MCPP:-mcpp}"
64-
HERE="$PWD"
6519
fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; }
6620

67-
command -v jq >/dev/null || fail "jq is required to parse the planned mcpp:action= lines" /dev/null
6821
command -v node >/dev/null || fail "node is required to run the produced launcher" /dev/null
6922

70-
echo "== step 1: a real wasm32-emscripten build, with no build.mcpp =="
71-
REALDIR=$(mktemp -d)
72-
mkdir -p "$REALDIR/src"
73-
cat > "$REALDIR/mcpp.toml" <<EOF
74-
[package]
75-
name = "web-consumer"
76-
version = "0.1.0"
77-
78-
[targets.web-consumer]
79-
kind = "bin"
80-
main = "src/main.cpp"
81-
EOF
82-
cp src/main.cpp "$REALDIR/src/main.cpp"
83-
( cd "$REALDIR" && "$MCPP" pack --target wasm32-emscripten --format dir > pack.log 2>&1 ) \
84-
|| fail "the real wasm32-emscripten build failed" "$REALDIR/pack.log"
85-
STAGE=$(find "$REALDIR/target/dist" -maxdepth 1 -type d -name 'web-consumer-*-wasm32-emscripten')
86-
[ -n "$STAGE" ] || fail "no staged directory from the real build" "$REALDIR/pack.log"
87-
[ -f "$STAGE/bin/web-consumer.js" ] || fail "the real build produced no bin/web-consumer.js"
88-
[ -f "$STAGE/bin/web-consumer.wasm" ] || fail "the real build produced no bin/web-consumer.wasm"
89-
echo "ok: real bin/web-consumer.js and bin/web-consumer.wasm, from the emsdk payload"
90-
91-
# Stand in for `mcpp::deploy(generated, "assets/greeting.txt")`, which
92-
# build.mcpp.bin below calls but which nothing here can act on, per this
93-
# script's header comment.
94-
mkdir -p "$STAGE/bin/assets"
95-
printf 'hello from build.mcpp\n' > "$STAGE/bin/assets/greeting.txt"
96-
97-
echo "== step 2: this fixture's build.mcpp, compiled for the HOST =="
9823
rm -rf target
99-
"$MCPP" build > build.log 2>&1 || fail "the host build of this fixture failed" build.log
100-
BIN=target/.build-mcpp/build.mcpp.bin
101-
[ -x "$BIN" ] || fail "no compiled build.mcpp.bin at $BIN" build.log
102-
echo "ok: build.mcpp compiled to $BIN"
10324

104-
echo "== step 3: run it directly under the wasm32-emscripten + --format web contract =="
105-
OUT=$(mktemp -d)
106-
PLANLOG=$(mktemp)
107-
env -i PATH="$PATH" \
108-
MCPP_PACK_FORMAT=web \
109-
MCPP_TARGET_OS=emscripten \
110-
MCPP_PACK_STAGE_DIR="$STAGE" \
111-
MCPP_PKG_NAME=web-consumer \
112-
MCPP_PKG_VERSION=0.1.0 \
113-
MCPP_MANIFEST_DIR="$HERE" \
114-
MCPP_OUT_DIR="$OUT" \
115-
"$BIN" > "$PLANLOG" 2>&1
116-
grep -q '"id":"mcpp.dist.web.index"' "$PLANLOG" || fail "no index.html action was planned" "$PLANLOG"
117-
n=$(grep -c '"id":"mcpp.dist.web.file"' "$PLANLOG")
118-
[ "$n" -eq 3 ] || fail "expected 3 file actions (js, wasm, the deployed asset), got $n" "$PLANLOG"
119-
echo "ok: 3 file actions and 1 index.html action planned"
25+
echo "== a plain build has no distribution edge =="
26+
"$MCPP" build --target wasm32-emscripten > build.log 2>&1 \
27+
|| fail "mcpp build --target wasm32-emscripten failed" build.log
28+
if find target -type d -name web | grep -q .; then
29+
echo "FAIL: a plain build produced a web/ directory"; exit 1
30+
fi
31+
echo "ok: no web/ directory before packaging"
12032

121-
echo "== step 4: execute the planned actions (standing in for ninja) =="
122-
grep '^mcpp:action=' "$PLANLOG" | sed 's/^mcpp:action=//' | while read -r line; do
123-
mapfile -t argv < <(echo "$line" | jq -r '.command[]')
124-
for i in "${!argv[@]}"; do
125-
argv[$i]="${argv[$i]//\$\{mcpp.stage_dir\}/$STAGE}"
126-
done
127-
"${argv[@]}"
128-
done
33+
echo "== mcpp pack --format web --target wasm32-emscripten =="
34+
"$MCPP" pack --format web --target wasm32-emscripten > pack.log 2>&1 \
35+
|| fail "mcpp pack --format web --target wasm32-emscripten failed" pack.log
36+
WEB=$(find target -type d -name web | head -1)
37+
[ -n "$WEB" ] || fail "no web/ directory was produced" pack.log
38+
echo "ok: $WEB"
12939

130-
echo "== assertions on target/web/ =="
131-
WEB="$OUT/web"
40+
echo "== the file list is exactly the stem family, the deployed asset, and index.html =="
13241
mapfile -t files < <(cd "$WEB" && find . -type f | sed 's#^\./##' | sort)
13342
want=(assets/greeting.txt index.html web-consumer.js web-consumer.wasm)
13443
[ "${files[*]}" = "${want[*]}" ] || {
@@ -138,15 +47,31 @@ want=(assets/greeting.txt index.html web-consumer.js web-consumer.wasm)
13847
exit 1; }
13948
echo "ok: exactly js, wasm, the deployed asset and index.html -- nothing else"
14049

50+
# THE DEPLOYED ASSET LANDS AT ITS STAGED PATH, NOT NESTED UNDER ITSELF.
51+
# `mcpp::deploy`'s `to` names a DIRECTORY (`assets`), and the file keeps its
52+
# own basename (`greeting.txt`) -- the same rule `[[runtime.deploy]]` and
53+
# `types.cppm`'s `DeployEntry` document. A `to` that names the full
54+
# destination path instead (`"assets/greeting.txt"`) deploys the file INTO a
55+
# directory of that name, landing it at `bin/assets/greeting.txt/greeting.txt`
56+
# -- a directory named after itself, holding the real file. Measured on this
57+
# fixture before `build.mcpp`'s deploy call was corrected. This step asserts
58+
# both directions: the file is a regular file at exactly its intended
59+
# relative path, and no such wrapper directory exists.
60+
[ -f "$WEB/assets/greeting.txt" ] \
61+
|| fail "assets/greeting.txt is not a regular file at its staged path" pack.log
62+
[ -d "$WEB/assets/greeting.txt" ] \
63+
&& fail "assets/greeting.txt is a directory -- the file landed inside a directory named after itself"
64+
grep -q 'hello from build.mcpp' "$WEB/assets/greeting.txt" \
65+
|| fail "the deployed asset's content did not survive the copy" "$WEB/assets/greeting.txt"
66+
echo "ok: assets/greeting.txt is the file, at exactly its staged relative path"
67+
14168
grep -q 'web-consumer.js' "$WEB/index.html" || fail "index.html does not load the launcher" "$WEB/index.html"
14269
grep -q '<title>web-consumer</title>' "$WEB/index.html" || fail "index.html's title is not the package name" "$WEB/index.html"
14370
echo "ok: index.html loads the launcher and titles the page from [package] name"
14471

145-
grep -q 'hello from build.mcpp' "$WEB/assets/greeting.txt" || fail "the deployed asset's content did not survive the copy" "$WEB/assets/greeting.txt"
146-
14772
out=$(node "$WEB/web-consumer.js")
14873
[ "$out" = "1-2-3" ] || fail "node did not print 1-2-3 (got: $out)"
14974
echo "ok: node $WEB/web-consumer.js prints 1-2-3"
15075

151-
rm -rf "$REALDIR" "$OUT" "$PLANLOG"
76+
rm -f build.log pack.log
15277
echo "PASS: dist-web produces a static directory, node runs it, 1-2-3"

0 commit comments

Comments
 (0)