Skip to content

Commit b9fb9e0

Browse files
committed
dist-apple: the staged tree's deployed files land at the bundle's resource destination, and the iOS fixture declares its runtime packages (mcpp#630)
1 parent 95cbc36 commit b9fb9e0

6 files changed

Lines changed: 89 additions & 2 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1805,7 +1805,16 @@ jobs:
18051805
# hand and got subtly wrong would otherwise fail at launch time on a
18061806
# user's machine and nowhere here.
18071807
plutil -lint "$app/Contents/Info.plist"
1808-
echo "ok: one bundle, a valid plist, and it launches"
1808+
# THE DEPLOYED FILE, AT THE RESOURCE DESTINATION AND NOT BESIDE THE
1809+
# PROGRAM (mcpp#630, item 3a): the engine stages `bin/data/...` and
1810+
# this member places it under `Contents/Resources/`.
1811+
test -f "$app/Contents/Resources/data/greeting.txt" \
1812+
|| { echo "FAIL: the deployed file is not under Contents/Resources"; find "$app" -type f; exit 1; }
1813+
if [ -e "$app/Contents/MacOS/bin" ]; then
1814+
echo "FAIL: the staged tree was copied whole into Contents/MacOS"; find "$app" -type f; exit 1
1815+
fi
1816+
grep -q 'closure = not-walked' pack.log || echo "note: the stage manifest's closure line was not echoed by pack"
1817+
echo "ok: one bundle, a valid plist, it launches, and the resource is where NSBundle looks"
18091818
18101819
# THE iOS ROW, FOR REAL (#622 B1's other half). `tests/ios-app-consumer`
18111820
# is the same fixture the Linux `consumers` job checks at the plan
@@ -1868,6 +1877,12 @@ jobs:
18681877
[ "$plat" = "iPhoneSimulator" ] || {
18691878
echo "FAIL: CFBundleSupportedPlatforms is '$plat', expected iPhoneSimulator"
18701879
exit 1; }
1880+
# THE DEPLOYED FILE, AT THE BUNDLE ROOT (the flat layout), and the
1881+
# executable at the root by its bare name -- not under `bin/`.
1882+
test -f "$app/data/greeting.txt" \
1883+
|| { echo "FAIL: the deployed file is not at the bundle root"; find "$app" -type f; exit 1; }
1884+
test -x "$app/ios-app-consumer" \
1885+
|| { echo "FAIL: the executable is not at the bundle root"; find "$app" -type f; exit 1; }
18711886
18721887
exe=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$plist")
18731888
test -x "$app/$exe" \

‎dist/apple.cppm‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,16 +713,66 @@ inline plan plan_for(options opt = {}) {
713713
// one and the program when there is not, and the `inputs` entry is the
714714
// same either way: the program is what this bundle is FOR, and naming it
715715
// is what orders this action after the link.
716+
//
717+
// WITH A STAGED TREE IT COPIES THE PROGRAM, NOT THE TREE. The engine
718+
// stages a Mach-O program as `bin/<name>` with every deployed file under
719+
// `bin/<to>/...` beside it (mcpp#630, item 3a: staging precedes the
720+
// closure walk, and the tree is handed over with `closure = not-walked`
721+
// in its manifest). Copying that tree whole into `Contents/MacOS/` put
722+
// the executable at `Contents/MacOS/bin/<name>`, which is not where
723+
// `CFBundleExecutable` says it is, and the resources beside a program
724+
// rather than in `Contents/Resources/`, where `NSBundle` looks. So the
725+
// launcher goes to the executable directory by itself, and the staged
726+
// `bin/` entries that are not the launcher go to the bundle's resource
727+
// destination below, at the relative path `mcpp::deploy`'s `to` gave
728+
// them. `${mcpp.stage_dir}` is still named as an input of the launcher
729+
// copy, which is what orders every step here after staging and gives
730+
// the action its dependency on the tree's manifest.
716731
layout.argv = stage.empty()
717732
? std::vector<std::string>{ "ditto",
718733
std::format("${{mcpp.target_file:{}}}", target),
719734
execDir + "/" + executableName }
720-
: std::vector<std::string>{ "ditto", "${mcpp.stage_dir}", execDir };
735+
: std::vector<std::string>{ "ditto", launcher, execDir + "/" + executableName };
721736
layout.inputs = { std::format("${{mcpp.target_file:{}}}", target) };
737+
if (!stage.empty()) layout.inputs.push_back("${mcpp.stage_dir}");
722738
layout.outputs = { execDir + "/" + executableName };
723739
p.steps.push_back(layout);
724740
assembled.push_back(execDir + "/" + executableName);
725741

742+
// THE DEPLOYED FILES, AT THE BUNDLE'S RESOURCE DESTINATION.
743+
//
744+
// Read at plan time against the staged tree, the way `dist-apk` reads
745+
// its assets: the second pass of `mcpp pack --format app` runs this
746+
// program after the tree is staged, so the entries exist on disk here.
747+
// One action per top-level entry under `bin/` that is not the launcher,
748+
// a file or a whole directory, each named by its own path so that the
749+
// graph carries an edge per deployed thing rather than one edge for
750+
// "everything". `ditto <dir> <dir>` copies the source's CONTENTS into
751+
// the destination, which is the flat layout iOS wants at the bundle
752+
// root and the `Contents/Resources/<to>/` layout macOS wants.
753+
if (!stage.empty()) {
754+
const std::filesystem::path stageBin = std::filesystem::path(stage) / "bin";
755+
const std::filesystem::path launcherPath = std::filesystem::path(launcher);
756+
std::error_code ec;
757+
std::vector<std::filesystem::path> entries;
758+
if (std::filesystem::is_directory(stageBin, ec))
759+
for (auto const& e : std::filesystem::directory_iterator(stageBin, ec))
760+
if (!ec && e.path() != launcherPath) entries.push_back(e.path());
761+
std::ranges::sort(entries);
762+
for (auto const& e : entries) {
763+
const std::string rel = e.filename().string();
764+
step res;
765+
res.id = "mcpp.dist.apple.resource." + rel;
766+
res.role = "artifact";
767+
res.description = "APP RESOURCE " + rel;
768+
res.argv = { "ditto", e.string(), resourceDir + "/" + rel };
769+
res.inputs = { e.string(), "${mcpp.stage_dir}" };
770+
res.outputs = { resourceDir + "/" + rel };
771+
p.steps.push_back(res);
772+
assembled.push_back(resourceDir + "/" + rel);
773+
}
774+
}
775+
726776
if (!opt.icon.empty()) {
727777
step icon;
728778
icon.id = "mcpp.dist.apple.icon";

‎tests/app-consumer/mcpp.toml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,10 @@ plugins = { path = "../..", features = ["dist-apple"], host-module = true }
2323
[targets.app-consumer]
2424
kind = "bin"
2525
main = "src/main.cpp"
26+
27+
# A deployed file, which the bundle must carry at its resource destination
28+
# (`Contents/Resources/data/greeting.txt` on macOS). The engine stages it at
29+
# `bin/data/greeting.txt` beside the program (mcpp#630, item 3a), and
30+
# `dist-apple` places every such entry under the resource directory.
31+
[runtime]
32+
deploy = [ { from = "share/greeting.txt", to = "data" } ]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bundled resource

‎tests/ios-app-consumer/mcpp.toml‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ ios_deployment_target = "17.0"
5959
kind = "bin"
6060
main = "src/main.cpp"
6161

62+
# THE C++ STANDARD LIBRARY AND THE COMPILER RUNTIME ARE PACKAGES ON THE iOS
63+
# ROWS (mcpp#630, item 4). The payload's static libc++ is a macOS object and
64+
# its resource directory carries no iOS builtins archive; without these two
65+
# lines the engine links the SDK's libc++ under the payload's headers and
66+
# reports the pairing once. This fixture imports `std`, so it declares them.
67+
[target.'cfg(os = "ios")'.dependencies]
68+
llvm.libcxx = "22.1.8.1"
69+
llvm.compiler-rt-builtins = "22.1.8.3"
70+
71+
# A deployed file, which the iOS bundle carries at its root (the flat layout).
72+
[runtime]
73+
deploy = [ { from = "share/greeting.txt", to = "data" } ]
74+
6275
# THE SIMULATOR ROW ONLY -- the device row (`aarch64-ios`) keeps `runner`
6376
# unset, as `examples/13-platform-targets/mcpp.toml` does: an artifact cannot
6477
# be run off an iOS device without a signature the developer owns.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bundled resource

0 commit comments

Comments
 (0)