diff --git a/.agents/docs/2026-09-12-622-verify.sh b/.agents/docs/2026-09-12-622-verify.sh index 009f20ea..955d9ff2 100755 --- a/.agents/docs/2026-09-12-622-verify.sh +++ b/.agents/docs/2026-09-12-622-verify.sh @@ -74,9 +74,12 @@ cat > "$d/build.mcpp" <<'CPP' import mcpp; #include int main() { + // An action runs with the build directory as its cwd, so the source is + // named by its absolute path; the manifest directory is the anchor. + const std::string in = std::string(mcpp::manifest_dir()) + "/gen/in.txt"; const std::string out = std::string(mcpp::out_dir()) + "/res.bin"; mcpp::action a; a.id = "gen:res"; a.role = "source"; - a.arg("/bin/cp").arg("gen/in.txt").arg(out.c_str()).input("gen/in.txt").output(out.c_str()).submit(); + a.arg("/bin/cp").arg(in.c_str()).arg(out.c_str()).input(in.c_str()).output(out.c_str()).submit(); mcpp::deploy(out.c_str(), "dep.resources"); return 0; } @@ -117,22 +120,25 @@ printf '\n[target.'"'"'cfg(linux)'"'"'.abi]\nthreads = true\n' >> "$d/mcpp.toml" printf '\n[target.'"'"'cfg(linux)'"'"'.abi]\nfrobnicate = true\n' >> "$d/mcpp.toml" (cd "$d" && "$STORE" build > b3.log 2>&1) && fail "an unknown abi member was accepted" || { has_text "$d/b3.log" "threads, exceptions" && ok "unknown member refused naming both members" || { fail "refused without naming the members"; tail -2 "$d/b3.log"; }; } -section "F. frameworks per target: a Linux build is byte-identical with and without the tables" +section "F. frameworks per target: a Linux build renders no framework and its graph is unchanged" d=$root/f; mkprog "$d" fw "" (cd "$d" && "$STORE" build > b1.log 2>&1) && n1=$(find "$d/target" -name build.ninja | head -1) && cp "$n1" "$d/n1" printf '\n[runtime]\nframeworks = ["Foundation"]\n\n[target.macos.runtime]\nframeworks = ["AppKit"]\n\n[target.'"'"'cfg(os = "ios")'"'"'.runtime]\nframeworks = ["UIKit"]\n' >> "$d/mcpp.toml" rm -rf "$d/target"; (cd "$d" && "$STORE" build > b2.log 2>&1) && n2=$(find "$d/target" -name build.ninja | head -1) -if [ -n "${n2:-}" ] && cmp -s "$d/n1" "$n2"; then ok "build.ninja identical on Linux"; else fail "build.ninja differs or build failed"; fi +# The manifest changed, so the fingerprint directory named inside build.ninja +# changes with it; the comparison is made with that segment normalised, and +# what it then asserts is that no flag, input or edge differs on Linux. +norm() { sed 's#/[0-9a-f]\{16\}/#/FP/#g' "$1"; } +if [ -n "${n2:-}" ] && diff -q <(norm "$d/n1") <(norm "$n2") >/dev/null; then ok "build.ninja identical on Linux up to the fingerprint segment"; else fail "build.ninja differs beyond the fingerprint segment, or the build failed"; diff <(norm "$d/n1") <(norm "${n2:-/dev/null}") | head -6; fi +grep -q -- '-framework' "${n2:-/dev/null}" && fail "a -framework flag rendered on an ELF target" || ok "no -framework on Linux" grep -q "unsupported key 'frameworks'" "$d/b2.log" && fail "frameworks reported as unsupported under a target table" || ok "frameworks accepted under [target..runtime]" section "G. platforms names the rows that exist" -d=$root/g; mkprog "$d" pl 'platforms = ["linux", "ios", "android", "emscripten"]' -sed -i 's/^\[targets.pl\]/[targets.pl]/' "$d/mcpp.toml"; python3 - "$d/mcpp.toml" <<'PY' -import sys,re; p=sys.argv[1]; s=open(p).read() -s=s.replace('version = "0.1.0"\n','version = "0.1.0"\nplatforms = ["linux", "ios", "android", "emscripten"]\n',1).replace('\nplatforms = ["linux", "ios", "android", "emscripten"]\n\n','\n\n') -open(p,'w').write(s) -PY -(cd "$d" && "$STORE" build --strict > b1.log 2>&1) && ok "the six-word vocabulary passes --strict" || { fail "a known platform name was refused"; grep -i platform "$d/b1.log" | head -2; } +d=$root/g; rm -rf "$d"; mkdir -p "$d/src" +printf '[package]\nname = "pl"\nversion = "0.1.0"\nplatforms = ["linux", "ios", "android", "emscripten"]\n\n[targets.pl]\nkind = "bin"\nmain = "src/main.cpp"\n' > "$d/mcpp.toml" +printf '#include \nint main() { std::puts("1-2-3"); return 0; }\n' > "$d/src/main.cpp" +(cd "$d" && "$STORE" build > b1.log 2>&1) || { fail "a manifest naming the six platforms does not build"; tail -3 "$d/b1.log"; } +grep -q "unknown platform" "$d/b1.log" && fail "a known platform name was reported as unknown" || ok "the six-word vocabulary is accepted" sed -i 's/platforms = \[.*\]/platforms = ["web"]/' "$d/mcpp.toml"; rm -rf "$d/target" (cd "$d" && "$STORE" build --strict > b2.log 2>&1) && fail "'web' was accepted" || { has_text "$d/b2.log" "unknown platform 'web'" && has_text "$d/b2.log" "linux | macos | windows | ios | android | emscripten" && ok "'web' refused naming the six" || { fail "refused without the vocabulary"; tail -2 "$d/b2.log"; }; } diff --git a/tests/e2e/658_a_program_runs_on_an_attached_android_device.sh b/tests/e2e/658_a_program_runs_on_an_attached_android_device.sh new file mode 100755 index 00000000..0120cf2f --- /dev/null +++ b/tests/e2e/658_a_program_runs_on_an_attached_android_device.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# requires: android-ndk android-device +# 658 -- `mcpp run --target aarch64-linux-android` on a real device (#622). +# +# The Android rows were `verified` through qemu-user on a system image's +# bionic. This script is the device reading: the program is pushed, executed +# and its exit status returned by `adb-run`, the session program +# `xim:android-platform-tools` ships (37.0.1-3 and later), named as this +# target's runner and provisioned by the same manifest. The engine hands the +# runner the link output and nothing else; the session is the package's. +# +# The capability probe in run_all.sh selects an arm64 device that is not an +# emulator and exports its serial as E2E_ANDROID_SERIAL; adb-run addresses it +# through ANDROID_SERIAL, which is adb's own selector. +# +# 1. a program prints its marker on the device and mcpp run exits 0; +# 2. a program that exits 7 makes mcpp run exit non-zero -- the status +# crosses the session, which is what distinguishes a runner from a +# launcher that reports its own success. +set -e +TARGET=aarch64-linux-android +: "${E2E_ANDROID_SERIAL:?run_all.sh exports the serial of the selected device}" +export ANDROID_SERIAL="$E2E_ANDROID_SERIAL" + +TMP=$(mktemp -d) +trap "rm -rf $TMP" EXIT +fail() { echo "FAIL: $1"; shift; for f in "$@"; do echo "--- $f ---"; cat "$f" 2>/dev/null; done; exit 1; } + +mkdir -p "$TMP/p/src" +cat > "$TMP/p/mcpp.toml" < "$TMP/p/src/main.cpp" <<'CPP' +#include +#include +int main() { std::string s = "1-2-" + std::to_string(3); std::puts(s.c_str()); return 0; } +CPP +cat > "$TMP/p/src/exits7.cpp" <<'CPP' +int main() { return 7; } +CPP + +cd "$TMP/p" +"$MCPP" run ondevice --target "$TARGET" > run.log 2>&1 \ + || fail "mcpp run on the device failed" run.log +grep -qx '1-2-3' run.log || fail "the program's marker did not come back from the device" run.log +echo "1. the marker printed on device $ANDROID_SERIAL OK" + +if "$MCPP" run exits7 --target "$TARGET" > run7.log 2>&1; then + fail "a program exiting 7 on the device was reported as success" run7.log +fi +echo "2. the exit status crossed the session OK" + +echo "PASS: 658_a_program_runs_on_an_attached_android_device" diff --git a/tests/e2e/run_all.sh b/tests/e2e/run_all.sh index b2642cc8..8fe31e47 100755 --- a/tests/e2e/run_all.sh +++ b/tests/e2e/run_all.sh @@ -108,6 +108,24 @@ case "$OS" in &>/dev/null; then CAPS+=(android-ndk) fi + # android-device: an arm64 Android device attached over USB and + # answering `adb`, taken from the platform-tools payload (the same + # store the NDK probe reads). An emulator is not a device here: the + # rows this capability serves are the aarch64 ones, and an emulator + # line is named `emulator-`. The serial is exported so a test + # can address that device when more than one is attached. + for adb in "$HOME"/.xlings/data/xpkgs/xim-x-android-platform-tools/*/adb \ + "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-android-platform-tools/*/adb; do + [[ -x "$adb" ]] || continue + serial=$("$adb" devices 2>/dev/null | awk '$2=="device" && $1 !~ /^emulator-/ {print $1; exit}') + [[ -n "$serial" ]] || continue + abi=$(ANDROID_SERIAL="$serial" "$adb" shell getprop ro.product.cpu.abi 2>/dev/null | tr -d '\r') + if [[ "$abi" == "arm64-v8a" ]]; then + CAPS+=(android-device) + export E2E_ANDROID_SERIAL="$serial" + break + fi + done # pack capability: ELF + patchelf both required if [[ " ${CAPS[*]} " == *" patchelf "* ]]; then CAPS+=(pack) @@ -232,7 +250,7 @@ echo "Detected capabilities: ${CAPS[*]:-}" # absent on Linux and must stay legal to declare. It is checked against the # CAPS+=() calls above by tests/e2e/README or by reading them -- keep it in # sync when adding a capability. -KNOWN_CAPS=(android-ndk elf fresh-sandbox gcc import-std-libcxx jq llvm macos +KNOWN_CAPS=(android-device android-ndk elf fresh-sandbox gcc import-std-libcxx jq llvm macos mingw mingw-cross msvc musl nasm no-msvc pack patchelf python3 qemu-arm qemu-riscv scan-deps symlink unix-shell windows wine xlings-msvc)