Skip to content

Commit 4ee9b26

Browse files
authored
2026.10.10.1: the workspace prefix carries the meaning, one writer per action output, module roles from declarations, Termux (#792)
Release 2026.10.10.1. Design: `.agents/docs/2026-10-10-one-source-one-meaning-design.md` (revision 6). ## Workspace: the prefix carries the meaning (#785, #786) - `[workspace.toolchain]`, `[workspace.indices]`, `[workspace.profile.<name>]`, `[workspace.target.<selector>]` (with `.build`, `.abi`, `.runtime`, `.xlings.workspace`) and `[workspace.xlings]` mirror the tables of the same name and are read by the same reader (`parse_document` over the document under `workspace.`). A table without the prefix describes its own package. - Merge rule by key (W3): declared scalars stay the member's, lists put the workspace first, named tables merge key by key, conditional rows are inherited ahead of the member's. Package-only keys and unknown `[workspace]` tables are refused. - The root's own tables still reach members by position, unchanged, in `mcpp.pm.compat.workspace_position`, with a `manifest/workspace-position` warning naming the canonical spelling (removed in 1.0.0). W7 warns about package tables on a root without `[package]` (`--strict`: error). A note asks for the `mcpp` floor when the layer is used. - Members are grouped by their configuration as it evaluates for the target; the virtual root carries a member's conditional `dialect_cxxflags`/`.abi` rows (R1b: `DIAL=0` → `DIAL=2`). - `mcpp.manifest.key_registry`: the class of each key; the parser's `[build]` and conditional-build known keys, the shared conditional keys and W7's package tables are derived from it. - `mcpp publish` writes the layer into the member's own tables (W6). ## Action outputs have one writer (#778) - Prepare writes no placeholder; a generated unit enters the plan from its action's declaration. A generated module interface without `.provides()` is `degraded`. - `mcpp::out_dir()` is per configuration and package (`target/.build-mcpp/out/<configuration>/<package>`), recorded as `outDir` in `resolution.json`. ## Module role from the declaration (#790) - A C++ unit that provides a module takes the module rule whatever its extension; cl.exe implementation partitions get `/internalPartition` (measured in probe #791). ## Toolchains, Windows, Android - `--toolchain` replacing a declared toolchain warns (D11). - Windows SDK from `KitsRoot10` and `%ProgramFiles%`; `vswhere` from `%ProgramFiles(x86)%`; the MSVC probe names the missing half for the first-run notice and for the refusal of a specified toolchain (D15–D18). - Android host recognised (`self env` → `android`), same default toolchain (D21); argv shell resolved (D28); the managed toolchain's library path no longer reaches ninja's environment (D29); install hints by failure class and xlings extract/hook progress shown (D22, D23). - xlings pinned to 2026.10.10.2 (openxlings/xlings#651: hard-link fallback, error classes, progress); bootstrap mcpp 2026.10.8.1. - Termux fresh-install jobs (termux-docker aarch64, Android 14 emulator app sandbox) in `ci-fresh-install.yml`. ## Late fixes from CI - A workspace member's build program keeps its out dir beside its cache, so `emit build-database` reuses the build's run (e2e 839 B4). - `mcpp index list` reads the same effective indices as resolution (e2e 44). - A failed install names the first address's failure ahead of the `compat` retries. - Tests read a tool binding from `build.ninja` as a command line (`env LD_LIBRARY_PATH=...` prefix, D29); the graphics checks read `outDir` (`.github/tools/out_dir.sh`). - The Linux and macOS e2e jobs install the `mcpplibs.cmdline` payloads e2e 32/33/40 link, which a warm cache used to hold. ## Tests - e2e 893–901, each failing on 2026.10.8.1: workspace layer, compat reading, conditional rows, one writer, per-configuration out_dir, module units in `.cpp` (llvm and gcc), `--toolchain` note, publish write-back, the tools' library path. - Unit: `test_key_registry` (registry vs `kWorkspaceBuildKeys` and the grouping key, layer parsing and refusals, W7, W3 merges, grouping), `MsvcProbe` description. - e2e 12, 44, 210 moved to `[workspace.indices]`; 120 and 773 keep the position reading as its coverage; 111 reads the new output place. ## Ecosystem - openxlings/xlings#651 (released 2026.10.10.2), openxlings/xim-pkgindex#947 (glibc 2.44.3 r4, payload hard-link check), mcpp-community/mcpp-plugins#45 (tests read `outDir`). Closes #785, closes #786, closes #778, closes #790.
1 parent b165218 commit 4ee9b26

94 files changed

Lines changed: 4398 additions & 472 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/docs/2026-10-10-one-source-one-meaning-design.md‎

Lines changed: 800 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env bash
2+
# Reproductions for .agents/docs/2026-10-10-one-source-one-meaning-design.md.
3+
# Prints what each case observes; asserts nothing.
4+
#
5+
# MCPP=<path to mcpp> bash 2026-10-10-one-source-one-meaning-repro.sh
6+
#
7+
# R1 conditional dialect_cxxflags in three positions (#786, #785)
8+
# R2 action output shared by two profiles (out_dir has no configuration)
9+
# R3 #778 on Linux: member target cleared, workspace ninja log kept
10+
# R4 #790: implementation partition in a .cpp, rebuilt every time
11+
# R5 interface unit (`export module`) in a .cpp
12+
# R6 implementation partition in a .cpp imported by the interface
13+
set -u
14+
MCPP="${MCPP:-mcpp}"
15+
LLVM="${LLVM:-llvm@23.1.3}"
16+
T=$(mktemp -d)
17+
echo "mcpp: $("$MCPP" --version 2>&1 | head -1) work: $T"
18+
binrun() { local b; b=$(find target -path '*/bin/*' -name "$1" -type f -newer "$2" 2>/dev/null | xargs -r ls -t | head -1); [ -n "$b" ] && "$b" || echo "(no binary)"; }
19+
20+
# ── R1 ────────────────────────────────────────────────────────────────
21+
main_dial() { cat > "$1" <<'EOF'
22+
#include <cstdio>
23+
#ifndef DIAL
24+
#define DIAL 0
25+
#endif
26+
int main() { std::printf("DIAL=%d\n", DIAL); }
27+
EOF
28+
}
29+
app_toml() { cat <<EOF
30+
[package]
31+
name = "app"
32+
version = "0.1.0"
33+
[build]
34+
sources = ["main.cpp"]
35+
[targets.app]
36+
kind = "bin"
37+
main = "main.cpp"
38+
$1
39+
EOF
40+
}
41+
COND1='[target.'"'"'cfg(os = "linux")'"'"'.build]
42+
dialect_cxxflags = ["-DDIAL=1"]'
43+
COND2='[target.'"'"'cfg(os = "linux")'"'"'.build]
44+
dialect_cxxflags = ["-DDIAL=2"]'
45+
mkdir -p "$T/r1/ws-root/app" "$T/r1/member/app" "$T/r1/standalone"
46+
printf '[workspace]\nmembers = ["app"]\n%s\n' "$COND1" > "$T/r1/ws-root/mcpp.toml"
47+
app_toml "" > "$T/r1/ws-root/app/mcpp.toml"; main_dial "$T/r1/ws-root/app/main.cpp"
48+
printf '[workspace]\nmembers = ["app"]\n' > "$T/r1/member/mcpp.toml"
49+
app_toml "$COND2" > "$T/r1/member/app/mcpp.toml"; main_dial "$T/r1/member/app/main.cpp"
50+
app_toml "$COND2" > "$T/r1/standalone/mcpp.toml"; main_dial "$T/r1/standalone/main.cpp"
51+
for c in ws-root member; do
52+
(cd "$T/r1/$c" && "$MCPP" build -p app > build.log 2>&1; echo "R1 $c: $(binrun app mcpp.toml) warnings: $(grep -ci warn build.log)")
53+
done
54+
(cd "$T/r1/standalone" && "$MCPP" build > build.log 2>&1; echo "R1 standalone: $(binrun app mcpp.toml)")
55+
56+
# ── R2 ────────────────────────────────────────────────────────────────
57+
mkdir -p "$T/r2/src"; cd "$T/r2"
58+
cat > mcpp.toml <<'EOF'
59+
[package]
60+
name = "gp"
61+
version = "0.1.0"
62+
[build]
63+
sources = ["src/main.cpp"]
64+
[targets.gp]
65+
kind = "bin"
66+
main = "src/main.cpp"
67+
EOF
68+
cat > src/main.cpp <<'EOF'
69+
#include <cstdio>
70+
const char* gen_profile();
71+
int main() { std::printf("GEN=%s\n", gen_profile()); }
72+
EOF
73+
printf '#!/bin/sh\nprintf '"'"'const char* gen_profile() { return "%%s"; }\\n'"'"' "$1" > "$2"\n' > gen.sh
74+
cat > build.mcpp <<'EOF'
75+
import std;
76+
import mcpp;
77+
int main() {
78+
const auto root = std::filesystem::path(mcpp::manifest_dir());
79+
const auto out = (std::filesystem::path(mcpp::out_dir()) / "gen.cpp").generic_string();
80+
const auto gen = (root / "gen.sh").generic_string();
81+
mcpp::action a;
82+
a.id = "gen";
83+
a.role = mcpp::roles::source;
84+
a.arg("sh").arg(gen.c_str()).arg(mcpp::profile()).arg(out.c_str())
85+
.input(gen.c_str()).output(out.c_str()).submit();
86+
return 0;
87+
}
88+
EOF
89+
for p in "" --release ""; do
90+
"$MCPP" build $p > build.log 2>&1 || tail -5 build.log
91+
echo "R2 build ${p:-(dev)}: $(binrun gp gen.sh)"
92+
done
93+
echo "R2 out_dir: $(find target -name gen.cpp | head -3 | tr '\n' ' ')"
94+
95+
# ── R3 ────────────────────────────────────────────────────────────────
96+
mkdir -p "$T/r3/app"; cd "$T/r3"
97+
printf '[workspace]\nmembers = ["app"]\n' > mcpp.toml
98+
cat > app/mcpp.toml <<'EOF'
99+
[package]
100+
namespace = "repro"
101+
name = "app"
102+
version = "0.0.1"
103+
[build]
104+
sources = []
105+
[targets.resource_test]
106+
kind = "bin"
107+
main = "main.cpp"
108+
EOF
109+
printf 'extern const char* resource();\nint main() { return resource()[0] == '"'"'R'"'"' ? 0 : 1; }\n' > app/main.cpp
110+
printf '#!/bin/sh\nprintf '"'"'const char* resource() { return "RESOURCE"; }\\n'"'"' > "$1"\n' > app/gen.sh
111+
cat > app/build.mcpp <<'EOF'
112+
import std;
113+
import mcpp;
114+
int main() {
115+
const auto root = std::filesystem::path(mcpp::manifest_dir());
116+
const auto out = (std::filesystem::path(mcpp::out_dir()) / "generated.cpp").generic_string();
117+
const auto gen = (root / "gen.sh").generic_string();
118+
mcpp::action a;
119+
a.id = "generate:resource";
120+
a.role = mcpp::roles::source;
121+
a.arg("sh").arg(gen.c_str()).arg(out.c_str()).input(gen.c_str()).output(out.c_str()).submit();
122+
return 0;
123+
}
124+
EOF
125+
"$MCPP" build -p app --release > b1.log 2>&1; echo "R3 first: rc=$? size=$(stat -c %s app/target/.build-mcpp/out/generated.cpp 2>/dev/null)"
126+
mv app/target saved-app-target
127+
"$MCPP" build -p app --release > b2.log 2>&1; echo "R3 after clearing member target: rc=$? size=$(stat -c %s app/target/.build-mcpp/out/generated.cpp 2>/dev/null) $(grep -m1 -o 'undefined reference[^)]*' b2.log)"
128+
129+
# ── R4–R6 ─────────────────────────────────────────────────────────────
130+
mod_pkg() { # dir, extra-for-repro.cppm
131+
mkdir -p "$1/src"
132+
cat > "$1/mcpp.toml" <<EOF
133+
[package]
134+
name = "repro"
135+
version = "0.1.0"
136+
[toolchain]
137+
default = "$LLVM"
138+
[targets.repro]
139+
kind = "bin"
140+
main = "src/main.cpp"
141+
EOF
142+
printf 'export module repro;\nexport import :api;\n%s\n' "$2" > "$1/src/repro.cppm"
143+
printf 'export module repro:api;\nexport auto answer() -> int;\n' > "$1/src/api.cppm"
144+
printf 'module repro:api_impl;\nimport :api;\nauto answer() -> int { return 42; }\n' > "$1/src/api_impl.cpp"
145+
printf 'import repro;\nimport std;\nauto main() -> int { std::println("{}", answer()); }\n' > "$1/src/main.cpp"
146+
}
147+
mod_pkg "$T/r4" ""; cd "$T/r4"
148+
for i in 1 2 3; do "$MCPP" build --verbose > b$i.log 2>&1; done
149+
echo "R4 build 2: $(grep -cE '^\[[0-9]+/' b2.log) steps; build 3: $(grep -cE '^\[[0-9]+/' b3.log) steps"
150+
echo "R4 pcm files: $(find target -name '*.pcm' -path '*pcm.cache*' -printf '%f ' 2>/dev/null)"
151+
152+
mod_pkg "$T/r6" "import :api_impl;"; cd "$T/r6"
153+
"$MCPP" build > b.log 2>&1; echo "R6 import of the .cpp partition: rc=$? $(grep -m1 -o "fatal error: .*" b.log)"
154+
155+
mkdir -p "$T/r5/src"; cd "$T/r5"
156+
cat > mcpp.toml <<EOF
157+
[package]
158+
name = "r5"
159+
version = "0.1.0"
160+
[toolchain]
161+
default = "$LLVM"
162+
[targets.r5]
163+
kind = "bin"
164+
main = "src/main.cpp"
165+
EOF
166+
printf 'export module r5;\nexport auto answer() -> int { return 42; }\n' > src/r5.cpp
167+
printf 'import r5;\nimport std;\nauto main() -> int { std::println("{}", answer()); }\n' > src/main.cpp
168+
"$MCPP" build > b1.log 2>&1; r1=$?; "$MCPP" build --verbose > b2.log 2>&1
169+
echo "R5 interface in .cpp: rc=$r1 $(grep -m1 -oE '(error|fatal error): .*' b1.log) second build: $(grep -cE '^\[[0-9]+/' b2.log) steps"

‎.agents/docs/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
1818
---
1919
```
2020

21-
329 records.
21+
330 records.
2222

2323
## By subject
2424

@@ -30,6 +30,7 @@ Records that declare one. Everything else is listed by date below.
3030

3131
### design
3232

33+
- [一处来源,一种含义:工作空间的传递语义、生成输出、模块角色与首次运行(#786、#785、#778、#790)](2026-10-10-one-source-one-meaning-design.md) — landed
3334
- [2026.10.5.3 发布方案:32 位 x86 的架构词汇、资源编译器的识别与增量(#776 后续)](2026-10-06-windows-x86-arch-vocabulary-and-rc-follow-ups-design.md) — active
3435
- [下一个版本的发布方案:标准库模块、原生 MSVC LTO 与导出发现、共享库的链接配置、Windows 参数引号(#768 后续、#770、#771)](2026-10-05-std-module-pair-msvc-lto-and-export-discovery-design.md) — active
3536
- [`mcpp run` hands the terminal to the program, and the follow-ups of #761, #763 and #765 (#766)](2026-10-05-run-terminal-handoff-and-766-follow-ups-design.md) — landed
@@ -123,6 +124,7 @@ Records that declare one. Everything else is listed by date below.
123124

124125
### 2026-10
125126

127+
- [一处来源,一种含义:工作空间的传递语义、生成输出、模块角色与首次运行(#786、#785、#778、#790)](2026-10-10-one-source-one-meaning-design.md) — landed
126128
- [LLVM 23.1.3 Part 2:Linux aarch64 默认工具链与 glibc 生态闭环](2026-10-08-llvm-2313-linux-aarch64-ecosystem-part2-design.md) — active
127129
- [LLVM 23.1.3 全平台统一默认:跨仓库联动方案(mcpp × xim-pkgindex)](2026-10-07-llvm-2313-unified-default-cross-repo-design.md) — active
128130
- [2026.10.5.3 发布方案:32 位 x86 的架构词汇、资源编译器的识别与增量(#776 后续)](2026-10-06-windows-x86-arch-vocabulary-and-rc-follow-ups-design.md) — active

‎.github/actions/bootstrap-mcpp/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ inputs:
3333
# `package.name`, so one of the two was simply unreachable — and which one
3434
# depended on the machine, which is why CI failed on `compat:lua` on
3535
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
36-
default: '2026.10.8.1'
36+
default: '2026.10.10.2'
3737

3838
outputs:
3939
sandbox-key:

‎.github/actions/setup-macos-llvm/action.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
# Floor imposed by the index, not a routine bump — see
1616
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
1717
# (two packages named `lua` in one repo need openxlings/xlings#381).
18-
default: '2026.10.8.1'
18+
default: '2026.10.10.2'
1919
image:
2020
description: >
2121
The runner label the job runs on (macos-15, xcode-27). It is part of the
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env python3
2+
"""The key registry and the documents that list its keys agree (SPEC-004 §2).
3+
4+
`modules/manifest/src/key_registry.cppm` states the class of every key once,
5+
and the parser reads its `[build]` keys from there. A key the registry adds
6+
and the field reference does not mention, or a shared table the workspace
7+
chapter does not list, is the drift this check stops:
8+
9+
- every `[build]` key of the registry is named in docs/04 and docs/zh/04;
10+
- every table the registry marks shared has a `[workspace.<table>]` row in
11+
docs/07 and docs/zh/07 (`profile.<name>` as `[workspace.profile.<name>]`,
12+
the `target.<sel>` rows as `[workspace.target.<selector>]`).
13+
"""
14+
import pathlib
15+
import re
16+
import sys
17+
18+
ROOT = pathlib.Path(__file__).resolve().parents[2]
19+
ROW = re.compile(r'\{"([^"]+)",\s*"([^"]*)",\s*KeyScope::(\w+),\s*KeyMerge::(\w+),\s*(true|false),\s*(true|false)\s*\}')
20+
21+
22+
def main() -> int:
23+
rows = ROW.findall((ROOT / "modules/manifest/src/key_registry.cppm").read_text())
24+
if not rows:
25+
print("FAIL: no rows read from key_registry.cppm")
26+
return 1
27+
problems = []
28+
for lang in ("", "zh/"):
29+
ref = (ROOT / f"docs/{lang}04-mcpp-toml.md").read_text()
30+
ws = (ROOT / f"docs/{lang}07-workspace.md").read_text()
31+
for table, key, _scope, _merge, shared, _cond in rows:
32+
if table == "build" and key and f"`{key}`" not in ref and f"{key} " not in ref:
33+
problems.append(f"docs/{lang}04-mcpp-toml.md does not name [build] {key}")
34+
if shared == "true" and not key:
35+
mirror = {
36+
"profile.<name>": "[workspace.profile.<name>]",
37+
"package": "[workspace.package]",
38+
}.get(table)
39+
if table.startswith("target.<sel>"):
40+
mirror = "[workspace.target.<selector>]"
41+
if mirror is None:
42+
mirror = f"[workspace.{table}"
43+
if mirror not in ws:
44+
problems.append(f"docs/{lang}07-workspace.md does not list {mirror}")
45+
for p in problems:
46+
print(f"FAIL: {p}")
47+
if problems:
48+
return 1
49+
print(f"OK: {len(rows)} registry rows agree with docs/04 and docs/07 (en, zh)")
50+
return 0
51+
52+
53+
if __name__ == "__main__":
54+
sys.exit(main())

‎.github/tools/out_dir.sh‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
# Prints where `mcpp::out_dir()` pointed for the root package in the newest
3+
# build of the package in the current directory: its `outDir` in
4+
# resolution.json. Each configuration has its own output directory, so a check
5+
# reads it instead of naming target/.build-mcpp/out.
6+
r=$(ls -t target/*/*/resolution.json 2>/dev/null | head -1)
7+
[ -n "$r" ] || { echo "out_dir.sh: no resolution.json under target/" >&2; exit 1; }
8+
py=$(command -v python3 || command -v python)
9+
d=$("$py" -c 'import json, sys
10+
for p in json.load(open(sys.argv[1])).get("graph", {}).get("packages", []):
11+
if p.get("root"):
12+
print(p.get("outDir", ""))
13+
break' "$r")
14+
[ -n "$d" ] || { echo "out_dir.sh: $r records no outDir for the root package" >&2; exit 1; }
15+
echo "$d"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env bash
2+
# A fresh mcpp in Termux: `mcpp new` and `mcpp run` with the default toolchain
3+
# (design 2026-10-10 §13, D27). Two places, because neither has both halves
4+
# of a phone:
5+
#
6+
# docker <tarball> termux/termux-docker (aarch64): Termux's bionic
7+
# userland and paths, on a native arm64 runner. It
8+
# has no `/bin`; Android 10 and later do
9+
# (`/bin -> /system/bin`), which is mcpp's baseline,
10+
# so the link is made first. No SELinux.
11+
# emulator-download <tarball>, then emulator-run
12+
# An Android 14 emulator (x86_64, KVM) with the
13+
# debuggable Termux APK; commands run through
14+
# `run-as com.termux`, in the app's SELinux domain,
15+
# where link(2) is refused -- the half that made
16+
# every install of the managed glibc fail.
17+
#
18+
# <tarball> is a release archive (`mcpp-<v>-linux-<arch>.tar.gz`) or a URL.
19+
# Exits non-zero unless the program built and printed its line.
20+
set -euo pipefail
21+
MODE="${1:?mode}"
22+
WORK="${RUNNER_TEMP:-/tmp}/termux-fresh"
23+
mkdir -p "$WORK/logs"
24+
S="${GITHUB_STEP_SUMMARY:-/dev/null}"
25+
WANT="hello from termux"
26+
27+
fetch() { # $1 = tarball path or URL, $2 = destination
28+
case "$1" in
29+
http*) curl -fsSL --retry 3 -o "$2" "$1" ;;
30+
*) cp "$1" "$2" ;;
31+
esac
32+
}
33+
34+
# The script that runs inside Termux. POSIX sh: under run-as there is only the
35+
# system shell, and in termux-docker it is the same file.
36+
cat > "$WORK/inside.sh" <<'SH'
37+
#!/bin/sh
38+
set -u
39+
TARBALL="$1"; HOME="$2"; export HOME
40+
export TMPDIR="$HOME/tmp"; mkdir -p "$TMPDIR" "$HOME/dist"
41+
tar -xzf "$TARBALL" -C "$HOME/dist"
42+
MCPP=$(find "$HOME/dist" -path '*/bin/mcpp' -type f | head -1)
43+
"$MCPP" --version
44+
"$MCPP" self env --format json | grep -o '"android":[a-z]*'
45+
cd "$HOME" && rm -rf hello && "$MCPP" new hello >/dev/null
46+
cd hello
47+
printf 'import std;\nint main() { std::println("hello from termux"); }\n' > src/main.cpp
48+
timeout 5400 "$MCPP" run
49+
SH
50+
chmod 755 "$WORK/inside.sh"
51+
52+
case "$MODE" in
53+
docker)
54+
fetch "${2:?tarball}" "$WORK/mcpp.tar.gz"
55+
chmod -R a+rX "$WORK"; chmod 777 "$WORK/logs"
56+
docker pull -q termux/termux-docker:aarch64
57+
set +e
58+
docker run --rm --privileged --entrypoint /system/bin/sh \
59+
-v "$WORK:/mnt/w" termux/termux-docker:aarch64 \
60+
-c 'ln -s /system/bin /bin && exec /entrypoint.sh sh /mnt/w/inside.sh /mnt/w/mcpp.tar.gz /data/data/com.termux/files/home' \
61+
2>&1 | tee "$WORK/logs/docker.log"
62+
set -e
63+
;;
64+
emulator-download)
65+
fetch "${2:?tarball}" "$WORK/mcpp.tar.gz"
66+
curl -fsSL --retry 3 -o "$WORK/termux.apk" \
67+
"https://github.com/termux/termux-app/releases/download/v0.118.3/termux-app_v0.118.3+github-debug_x86_64.apk"
68+
exit 0
69+
;;
70+
emulator-run)
71+
A=/data/data/com.termux/files
72+
adb wait-for-device
73+
adb install -r "$WORK/termux.apk" >/dev/null
74+
adb shell am start -n com.termux/com.termux.app.TermuxActivity >/dev/null
75+
for _ in $(seq 1 60); do
76+
adb shell run-as com.termux ls "$A/usr/bin/sh" >/dev/null 2>&1 && break
77+
sleep 5
78+
done
79+
for f in mcpp.tar.gz inside.sh; do
80+
adb push "$WORK/$f" /data/local/tmp/ >/dev/null
81+
adb shell chmod 644 "/data/local/tmp/$f"
82+
adb shell run-as com.termux cp "/data/local/tmp/$f" "$A/$f"
83+
done
84+
echo "SELinux: $(adb shell getenforce | tr -d '\r'); context: $(adb shell run-as com.termux cat /proc/self/attr/current | tr -d '\r\0')" \
85+
| tee -a "$S"
86+
set +e
87+
adb shell run-as com.termux "$A/usr/bin/env" PREFIX="$A/usr" PATH="$A/usr/bin" \
88+
LANG=en_US.UTF-8 sh "$A/inside.sh" "$A/mcpp.tar.gz" "$A/home" 2>&1 \
89+
| tr -d '\r' | tee "$WORK/logs/emulator.log"
90+
set -e
91+
;;
92+
*) echo "unknown mode $MODE"; exit 2 ;;
93+
esac
94+
95+
log=$(ls "$WORK"/logs/*.log | head -1)
96+
{ echo "### Termux ($MODE)"; echo '```'; tail -25 "$log"; echo '```'; } >> "$S"
97+
grep -qx "$WANT" "$log" || { echo "FAIL: the program did not print '$WANT'"; exit 1; }
98+
echo "PASS: mcpp new + run in Termux ($MODE)"

‎.github/workflows/bootstrap-macos.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# Dormant (workflow_dispatch only), but kept in step with the rest —
1818
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
1919
# index cannot resolve two packages that share a short name.
20-
XLINGS_VERSION: '2026.10.8.1'
20+
XLINGS_VERSION: '2026.10.10.2'
2121
steps:
2222
- uses: actions/checkout@v4
2323

0 commit comments

Comments
 (0)