Skip to content

Commit 8e511f5

Browse files
committed
dist-apk: read deployed assets from the staged tree, at plan time
Per docs/30 ("Producing a distributable") and e2e 651/649 in the mcpp tree, `mcpp::deploy`'s `to` lands at `<staged tree>/bin/<to>/...`, and the second pass of `mcpp pack --format apk` runs `plan_for` after the tree is staged -- so, like every other member's own reads against `pack_stage_dir()`, this member now enumerates `<stage>/bin` with `std::filesystem::recursive_directory_iterator` in `plan_for` itself, declares each file as an individual action input, and maps `bin/<rel>` to `assets/<rel>`. The command-time helper that used to scan `${mcpp.bin_dir}` for this is gone; `copy-then-jar.sh` now only copies the archive and hands it to `jar`. MEASURED REGRESSION, NOT HIDDEN: on `*-linux-android`, with this exact engine build, the staged tree carries `lib/<name>.so` and NOTHING ELSE -- `run_shared_program` (mcpp.pack) never calls `stage_runtime_files`, unlike every other row's `run()` (see this member's header). So the loop above is correct against the documented contract and is a no-op on THIS row today: `apk-consumer`'s deployed `assets/generated/res.bin` no longer reaches the signed apk (verified: `jar tf` now lists `assets/mcpp-run.json` only, where it previously also listed `assets/generated/res.bin`). This is a gap in `run_shared_program`, not something this member can read around from its own vantage point -- see this member's report for the two staged trees compared side by side. `zipalign -f` and the `d8` `.class` glob are unchanged (both remain measured facts, independent of this change).
1 parent 764a9c8 commit 8e511f5

1 file changed

Lines changed: 49 additions & 39 deletions

File tree

‎dist/apk.cppm‎

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -620,25 +620,30 @@ inline plan plan_for(options opt = {}) {
620620

621621
// ── the temporary staging tree: lib/<abi>/, assets/ ─────────────────
622622
//
623-
// ASSETS ARE NOT COLLECTED HERE, AND THAT IS A MEASURED LIMIT OF THIS
624-
// PROGRAM'S OWN VANTAGE POINT, NOT A CHOICE. `mcpp::out_dir()` is
625-
// `target/.build-mcpp/out/` -- one path per PACKAGE, the generator
626-
// scratch directory `mcpp::deploy`'s own `from` argument is read out of
627-
// (`build_program.cppm`'s `build_dir`, and 652b's own comment: "as
628-
// opposed to `${mcpp.out_dir}` inside an action, which is per-triple").
629-
// The real per-triple `bin/` this member needs to enumerate -- where
630-
// `mcpp::deploy`'s `to` actually lands -- is `${mcpp.bin_dir}`, and that
631-
// placeholder is substituted by the ENGINE only inside an action's own
632-
// argv, never into a string this program can read. So the enumeration
633-
// that would happen here in every other member's `plan_for` (`dist-
634-
// appimage`'s `is_file`, `dist-apple`'s identical reads against
635-
// `pack_stage_dir()`) instead happens inside `collect-assets.sh` below,
636-
// at COMMAND time, against the real path the engine hands it. The
637-
// consequence, stated rather than hidden: this action's `inputs` cannot
638-
// name each deployed file individually (their absolute paths are not
639-
// obtainable here), so a build that changes ONLY a deployed file's
640-
// bytes, with nothing else in the link changing, is not guaranteed to
641-
// re-run this step. Unmeasured; see this member's report.
623+
// ASSETS, READ AT PLAN TIME AGAINST `pack_stage_dir()`, THE SAME WAY
624+
// EVERY OTHER MEMBER READS ITS STAGED TREE (`dist-appimage`'s `is_file`,
625+
// `dist-apple`'s identical reads). docs/30 ("Producing a distributable")
626+
// and e2e 651/649 in the mcpp tree both show `mcpp::deploy`'s `to`
627+
// landing at `<staged tree>/bin/<to>/...`, beside the packed executable
628+
// -- the second pass of `mcpp pack --format apk` runs `plan_for` AFTER
629+
// the tree is staged, so those files already exist on disk when this
630+
// program reads them, exactly as `dist-appimage` reads `${mcpp.stage_
631+
// dir}`'s `AppRun` candidates.
632+
//
633+
// MEASURED ON THIS ROW, 2026-09-12, AGAINST THE ENGINE REVISION THIS
634+
// MEMBER IS BUILT AGAINST: `*-linux-android`'s own staged tree carries
635+
// `lib/<name>.so` and NOTHING ELSE -- `run_shared_program` (mcpp.pack)
636+
// stages the app's own object and stops, calling neither the ELF
637+
// closure walk nor `stage_runtime_files` the way every other row's
638+
// `run()` does (see this member's header, "WHAT `mcpp pack`'S OWN
639+
// CLOSURE DOES NOT DO FOR THIS ROW"). So the loop below is written to
640+
// the documented, cross-row contract and DOES fire wherever the engine
641+
// actually stages `bin/<to>/...` beside the executable; on THIS row,
642+
// today, `bin/` does not exist in the staged tree at all, and the loop
643+
// is a no-op -- a deployed file is declared correctly and staged
644+
// nowhere, which is the honest report of a gap in `run_shared_program`
645+
// rather than in this member. See this member's own report for the
646+
// measurement that found it.
642647
const fs::path work = fs::path(opt.out_dir) / "dist-apk" / "stage";
643648
{ std::error_code ec; fs::remove_all(work, ec); }
644649
std::vector<std::string> libInputs;
@@ -673,38 +678,43 @@ inline plan plan_for(options opt = {}) {
673678
}
674679

675680
const fs::path assetsDir = work / "assets";
681+
std::vector<std::string> assetInputs;
682+
{ // every deploy'd file, `<stage>/bin/<rel>` -> `assets/<rel>` -- see the
683+
// long comment above for what this loop finds on this row today.
684+
const fs::path stageBin = fs::path(stage) / "bin";
685+
std::error_code ec;
686+
if (fs::is_directory(stageBin, ec)) {
687+
for (auto& e : fs::recursive_directory_iterator(stageBin, ec)) {
688+
if (ec) break;
689+
if (!e.is_regular_file(ec)) continue;
690+
auto rel = fs::relative(e.path(), stageBin, ec);
691+
collect_tree(e.path(), assetsDir / rel, assetInputs);
692+
}
693+
}
694+
}
676695
const std::string runJsonPath = (assetsDir / "mcpp-run.json").string();
677696
if (!write_if_different(runJsonPath, run_json(appId, activityName))) {
678697
std::cerr << std::format("mcpp.dist.apk: cannot write {}", runJsonPath) << '\n';
679698
p.reason = "cannot write mcpp-run.json";
680699
return p;
681700
}
701+
assetInputs.push_back(runJsonPath);
682702

683-
// ── the small helper scripts this pipeline needs (argv only, no
684-
// shell): one that copies the archive, folds in whatever
685-
// `mcpp::deploy` placed beside the link output, and hands the result to
686-
// `jar`; one that stamps a directory-shaped tool's success (docs/30:
687-
// "you must name the output files", and javac's own output set -- one
688-
// .class per top-level AND per inner class -- is not knowable at plan
689-
// time) ─────────────────────────────────────────────────────────────
703+
// ── the small helper script this pipeline needs (argv only, no
704+
// shell): copies the archive and hands the result to `jar`. The
705+
// staging tree it copies from (`work`) is fully populated by the time
706+
// this runs -- native libraries and deployed assets alike are read
707+
// above, at plan time, not discovered by this script -- so, unlike an
708+
// earlier revision of this member, it takes no `bindir` argument at
709+
// all ─────────────────────────────────────────────────────────────
690710
const fs::path helpersDir = fs::path(opt.out_dir) / "dist-apk";
691711
const std::string copyThenJar = (helpersDir / "copy-then-jar.sh").string();
692712
write_if_different(copyThenJar,
693713
"#!/bin/sh\n"
694714
"# mcpp.dist.apk helper. Do not edit.\n"
695715
"set -e\n"
696-
"src=\"$1\"; dst=\"$2\"; jar=\"$3\"; work=\"$4\"; bindir=\"$5\"; shift 5\n"
716+
"src=\"$1\"; dst=\"$2\"; jar=\"$3\"; shift 3\n"
697717
"cp \"$src\" \"$dst\"\n"
698-
"mkdir -p \"$work/assets\"\n"
699-
"# Every deploy'd subdirectory beside the link output, folded into\n"
700-
"# assets/ under its own name -- see the header comment on why this\n"
701-
"# happens here, at command time, and not in plan_for's own C++.\n"
702-
"if [ -d \"$bindir\" ]; then\n"
703-
" for d in \"$bindir\"/*/; do\n"
704-
" [ -d \"$d\" ] || continue\n"
705-
" cp -R \"${d%/}\" \"$work/assets/\"\n"
706-
" done\n"
707-
"fi\n"
708718
"\"$jar\" uf \"$dst\" \"$@\"\n");
709719
{ std::error_code ec; fs::permissions(copyThenJar,
710720
fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec,
@@ -840,16 +850,16 @@ inline plan plan_for(options opt = {}) {
840850
libs.description = "APK LIBS+ASSETS";
841851
libs.output = (outDir / "withlibs.apk").string();
842852
libs.argv = { copyThenJar, apkPath, libs.output, jar,
843-
work.string(), "${mcpp.bin_dir}",
844853
"-C", work.string(), "lib",
845854
"-C", work.string(), "assets" };
846855
if (!javaOutputs.empty()) {
847856
libs.argv.push_back("-C");
848857
libs.argv.push_back((outDir / "dex").string());
849858
libs.argv.push_back("classes.dex");
850859
}
851-
libs.inputs = { apkPath, runJsonPath };
860+
libs.inputs = { apkPath };
852861
for (auto const& f : libInputs) libs.inputs.push_back(f);
862+
for (auto const& f : assetInputs) libs.inputs.push_back(f);
853863
for (auto const& f : javaOutputs) libs.inputs.push_back(f);
854864
p.steps.push_back(libs);
855865

0 commit comments

Comments
 (0)