Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ Emscripten 自己的 CMake 工具链(`CMAKE_EXECUTABLE_SUFFIX ".js"`)与 Rust
的宿主 spec(用户写的 `[toolchain]` 或机器默认),构建程序按它解析;行 pin 没有替换任何
东西时行为不变。`tests/e2e/657`。

### 修复:发布物是终端产物;库形态的应用也带上运行期文件(#622)

- `mcpp pack --format <name>` 与 `mcpp run --format <name>` 报告的产物改为请求引入的
artifact 动作中**没有被其他引入动作当作输入**的输出(终端产物)。此前取第一个输出:
`dist-apk` 提交的是一条链(link、加库、对齐、签名),`adb-run` 拿到的是未签名的 `base.apk`,
`adb install` 拒绝安装。中间产物仍逐个核验存在,只是不再以 `Packed` 报告;`mcpp run` 在终端
产物不止一个时按句拒绝并列出它们。`tests/e2e/656`。
- 在应用形态为共享库的行上(`*-linux-android` 的 `kind = "app"`),`mcpp pack` 此前只暂存
`lib/<name>.so`,`deploy` 放置的运行期文件没有进暂存树,于是 dist-apk 的 `assets/` 为空。
现在与其他行一致,按 `bin/<to>/...` 的相对路径暂存。`tests/e2e/652b`。

## [2026.9.12.2] - 2026-09-12

2026.9.12.1 未单独发布,其条目并入本版本。
Expand Down
8 changes: 8 additions & 0 deletions docs/10-pack-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ then runs the artifact the pack reported, through the runner resolved for a
program: the project's `[target.<triple>] runner`, then a dependency's
`mcpp::runner(...)`, then the payload descriptor's.

The artifact a pack reports is the request's **terminal** one: among the
`artifact` actions the request introduced, the output no other introduced
action consumes. A provider is often a chain (`dist-apk`: link, add the
libraries, align, sign), and every output in it is verified to exist, but only
the last is the distributable and only it is printed as `Packed`. A format
whose chain ends in two files is refused by `mcpp run --format`, naming both,
because a runner takes one operand.

An unknown `<name>` is refused naming the format set the resolved graph
provides, the same set `mcpp pack --format bogus` reports. `--format` together
with `--no-runner` is refused — an `.apk` or an installed `.app` cannot be
Expand Down
6 changes: 6 additions & 0 deletions docs/zh/10-pack-and-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ mcpp run --target aarch64-ios-sim --format app
报出的那个产物,经由为一个程序解析出的 runner:项目的 `[target.<triple>] runner`,
其次依赖的 `mcpp::runner(...)`,再次载荷描述文件的。

打包报出的产物是这次请求的**终端**产物:在请求引入的 `artifact` 动作中,没有被其他
引入动作当作输入的那个输出。提供者常常是一条链(`dist-apk`:链接、加库、对齐、签名),
链上每个输出都会被核验存在,但只有最后一个是发布物,也只有它以 `Packed` 报出。链的
末端有两个文件的格式会被 `mcpp run --format` 拒绝并点名两者,因为 runner 只接受一个
操作数。

未知的 `<name>` 会被拒绝,点名已解析图提供的格式集合,与 `mcpp pack --format
bogus` 报出的是同一个集合。`--format` 与 `--no-runner` 同时出现会被拒绝——一个
`.apk` 或已安装的 `.app` 无法被直接执行。在 `kind = "app"` 的形态是一个库的那一行上,
Expand Down
15 changes: 15 additions & 0 deletions src/build/execute.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1886,6 +1886,21 @@ export int build_run_target(const std::optional<std::string>& targetName,
if (auto rc = run_build_plan(*ctx2, /*verbose=*/false, no_cache, target_triple);
rc != 0)
return rc;
// ONE DISTRIBUTABLE, OR A SENTENCE. The pack pipeline reports the
// terminal artifacts of the request (outputs no other introduced
// action consumes); a format that ends in two files has no single
// operand a runner can take.
if (outcome.artifacts.size() != 1) {
std::string names;
for (auto const& a : outcome.artifacts) {
if (!names.empty()) names += ", ";
names += a.string();
}
std::println(stderr,
"error: --format {} produced {} distributables ({}); mcpp run needs "
"exactly one to hand to the runner", format, outcome.artifacts.size(), names);
return 1;
}
return run_artifact_via_runner(*ctx2, outcome.artifacts.front(),
passthrough, no_runner, runner_name);
}
Expand Down
9 changes: 9 additions & 0 deletions src/pack/pack.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,15 @@ run_shared_program(const Plan& plan)
if (ec) return std::unexpected(Error{std::format(
"copy binary failed: {}", ec.message())});

// THE RUNTIME FILES TRAVEL AS ON EVERY OTHER ROW. `deploy` placed them
// under `bin/<to>/` beside the built library; they are staged at the same
// relative path under `bin/`, which is where a provider that maps them
// into its own layout (`dist-apk`: `assets/`) reads them. Measured
// 2026-09-12: without this the Android staged tree carried the library
// alone and a deploy'd resource never reached the APK.
if (!plan.opts.runtimeFiles.empty())
if (auto r = stage_runtime_files(plan, plan.stagingRoot / "bin"); !r) return r;

copy_if_exists(plan.projectRoot / "README.md", plan.stagingRoot);
copy_if_exists(plan.projectRoot / "LICENSE", plan.stagingRoot);

Expand Down
24 changes: 22 additions & 2 deletions src/pack/pipeline.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,27 @@ export PackOutcome build_and_pack(Options opts, bool modeFromUser,
// Identity is (package, id): an id is unique within the package that
// declared it and nothing more.
std::vector<std::string> distOutputs;
// THE DISTRIBUTABLE IS THE TERMINAL ARTIFACT. A provider may submit a
// chain (`dist-apk`: link, add libraries, align, sign); every output
// is verified below, but the thing a user installs, and the operand
// `mcpp run --format` hands the runner, is an output no other
// introduced action consumes. Measured 2026-09-12: with the first
// output taken as the operand, `adb-run` received the unsigned
// `base.apk` and `adb install` refused it.
std::vector<std::string> distInputs;
for (auto const& a : distCtx->plan.actions) {
if (a.role != mcpp::manifest::BuildAction::Role::Artifact) continue;
if (preexistingArtifacts.contains({a.packageName, a.id})) continue;
for (auto const& o : a.outputs) distOutputs.push_back(o);
for (auto const& i : a.inputs) distInputs.push_back(i);
}
auto absolute_of = [&](std::string const& p) {
auto q = std::filesystem::path(p).is_absolute()
? std::filesystem::path(p) : distCtx->plan.outputDir / p;
return q.lexically_normal();
};
std::set<std::filesystem::path> consumed;
for (auto const& i : distInputs) consumed.insert(absolute_of(i));
// DECLARED AND THEN SUBMITTED NOTHING. The half of the contract a
// member is most likely to get wrong is the gate, and a member whose
// gate never opens leaves a pass that succeeds and produces no
Expand Down Expand Up @@ -484,19 +500,23 @@ export PackOutcome build_and_pack(Options opts, bool modeFromUser,
// nothing.
std::error_code ec;
std::vector<std::filesystem::path> reported;
std::vector<std::filesystem::path> intermediate;
for (auto const& o : distOutputs) {
auto abs = std::filesystem::path(o).is_absolute()
? std::filesystem::path(o) : distCtx->plan.outputDir / o;
auto abs = absolute_of(o);
if (!std::filesystem::is_regular_file(abs, ec)
&& !std::filesystem::is_directory(abs, ec)) {
mcpp::ui::error(std::format(
"--format {} reported success and produced nothing at {}",
opts.formatName, abs.string()));
return PackOutcome{1};
}
if (consumed.contains(abs)) { intermediate.push_back(std::move(abs)); continue; }
mcpp::ui::status("Packed", mcpp::ui::shorten_path(abs, pathCtx));
reported.push_back(std::move(abs));
}
// Every output consumed by another: a cycle a provider should not
// write, reported as all outputs rather than as nothing.
if (reported.empty()) reported = std::move(intermediate);
return PackOutcome{0, std::move(reported)};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ echo "the refusal does not fire for a bin target OK"
# adding it earlier would change what check 3's refusal lists (it asserts
# "one of: none declared", which is only true while this package provides
# no format at all).
printf "resource-1\n" > res.txt
cat > copy.sh <<'EOF'
#!/usr/bin/env bash
set -e
Expand All @@ -116,6 +117,11 @@ int main() {
std::fclose(f);
}

// A deploy'd file (#622 A4) travels with the library: staged under
// `bin/<to>/` on this row as on every other, where a provider that maps
// it into its own layout (dist-apk: assets/) reads it.
mcpp::deploy((std::string(mcpp::manifest_dir()) + "/res.txt").c_str(), "myres");

mcpp::provides_pack_format("blob");
if (std::string_view(mcpp::pack_format()) != "blob") return 0;

Expand Down Expand Up @@ -144,6 +150,8 @@ staged=$(ls -d target/dist/myapp-0.1.0-*/ 2>/dev/null | head -1)
[ -n "$staged" ] || fail "no staged tree under target/dist for the Android pack" pack.log
[ -f "${staged}lib/libmyapp.so" ] \
|| fail "the staged tree has no lib/libmyapp.so" pack.log
[ -f "${staged}bin/myres/res.txt" ] \
|| fail "the deploy'd file was not staged under bin/myres/ on the Android row" pack.log
[ -n "$(find target -name 'myapp.blob' 2>/dev/null)" ] \
|| fail "the reported artifact myapp.blob does not exist" pack.log
echo "mcpp pack --format blob on Android stages lib/libmyapp.so OK"
Expand Down
23 changes: 22 additions & 1 deletion tests/e2e/656_run_hands_the_distributable_to_the_runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,21 @@ int main() {
.input("${mcpp.target_file:app}")
.output(out.c_str())
.submit();
// A second step that consumes the first: the format's distributable is
// the TERMINAL artifact (the output no other introduced action consumes),
// which is what the runner must receive. A provider such as dist-apk is
// a chain of this shape (link, add libraries, align, sign).
const std::string fin = std::string(mcpp::out_dir()) + "/app.final";
mcpp::action b;
b.id = "final";
b.role = "artifact";
b.description = "final";
b.arg((root + "/copy.sh").c_str())
.arg(out.c_str())
.arg(fin.c_str())
.input(out.c_str())
.output(fin.c_str())
.submit();
return 0;
}
EOF
Expand Down Expand Up @@ -130,8 +145,14 @@ printf '\n[target.%s]\nrunner = ["%s"]\n' "$HOST" "$TMP/runner.sh" >> mcpp.toml

# ── 1. `mcpp run --format blob` hands the runner the distributable ────────
out=$("$MCPP" run --format blob 2>&1) || fail "mcpp run --format blob failed" <(echo "$out")
grep -q "RUNNER: .*/app\.final$" <<<"$out" \
|| fail "the runner's operand was not the terminal artifact app.final" <(echo "$out")
grep -q "RUNNER: .*/app\.blob$" <<<"$out" \
|| fail "the runner's operand was not app.blob" <(echo "$out")
&& fail "the runner received the intermediate app.blob" <(echo "$out")
grep -q "Packed .*app\.final" <<<"$out" \
|| fail "the Packed line does not name the terminal artifact" <(echo "$out")
grep -q "Packed .*app\.blob" <<<"$out" \
&& fail "the intermediate app.blob was reported as Packed" <(echo "$out")
grep -q "1-2-3" <<<"$out" \
|| fail "the program's marker did not print through the runner" <(echo "$out")
echo "mcpp run --format blob hands the runner the distributable OK"
Expand Down
Loading