Skip to content
Closed
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
57 changes: 57 additions & 0 deletions .github/probe/msvc_module_roles.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# O5 probe: which cl.exe options compile each C++20 module role, in .cpp and
# .cppm/.ixx files, and whether each writes the BMI an importer needs.
# Prints; asserts nothing.
$ErrorActionPreference = 'Continue'
$PSNativeCommandUseErrorActionPreference = $false
$summary = $env:GITHUB_STEP_SUMMARY
function Say($t) { Write-Host $t; Add-Content -Path $summary -Value $t }
$d = 'C:\roles'
New-Item -ItemType Directory -Force $d | Out-Null
Set-Location $d

Set-Content m.ixx "export module m;`nexport import :api;`nexport int twice(int);`n"
Set-Content api.cppm "export module m:api;`nexport int answer();`n"
Set-Content api_cpp.cpp "export module m2:api;`nexport int answer2();`n"
Set-Content impl_part.cpp "module m:impl;`nimport :api;`nint helper() { return answer(); }`n"
Set-Content impl_part.cppm "module m:impl2;`nimport :api;`nint helper2() { return answer(); }`n"
Set-Content iface.cpp "export module solo;`nexport int solo_value() { return 7; }`n"
Set-Content impl.cpp "module m;`nint twice(int x) { return 2 * x; }`n"

Say "## cl.exe module roles"
Say '```'
& cl 2>&1 | Select-Object -First 1 | ForEach-Object { Say $_ }
Say '```'

function Try-Cl($label, [string[]]$a) {
Remove-Item *.obj -ErrorAction SilentlyContinue
$t0 = Get-Date
Start-Sleep -Milliseconds 1100
$out = & cl /nologo /std:c++latest /EHsc /c @a 2>&1
$rc = $LASTEXITCODE
$ifc = (Get-ChildItem *.ifc -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -gt $t0 } | ForEach-Object Name) -join ' '
Say "### $label (exit $rc)"
Say "command: ``cl /std:c++latest /c $($a -join ' ')`` — .ifc written: ``$ifc``"
Say '```'
$out | Select-Object -First 8 | ForEach-Object { Say "$_" }
Say '```'
}

# Interface partition in .cppm: needs /interface (cl does not know .cppm).
Try-Cl 'interface partition, .cppm, no option' @('api.cppm')
Try-Cl 'interface partition, .cppm, /interface /TP' @('/interface', '/TP', 'api.cppm')
# Primary interface in .ixx.
Try-Cl 'primary interface, .ixx' @('m.ixx')
# Implementation partition in .cpp: plain, /internalPartition, /interface.
Try-Cl 'implementation partition, .cpp, no option' @('impl_part.cpp')
Try-Cl 'implementation partition, .cpp, /internalPartition' @('/internalPartition', 'impl_part.cpp')
Try-Cl 'implementation partition, .cpp, /interface' @('/interface', 'impl_part.cpp')
# Implementation partition in .cppm.
Try-Cl 'implementation partition, .cppm, /internalPartition /TP' @('/internalPartition', '/TP', 'impl_part.cppm')
Try-Cl 'implementation partition, .cppm, /interface /TP' @('/interface', '/TP', 'impl_part.cppm')
# Interface unit in .cpp.
Try-Cl 'interface unit, .cpp, no option' @('iface.cpp')
Try-Cl 'interface unit, .cpp, /interface' @('/interface', 'iface.cpp')
Try-Cl 'interface partition, .cpp, /interface' @('/interface', 'api_cpp.cpp')
# Implementation unit (`module m;`) in .cpp, with m.ifc present.
Try-Cl 'implementation unit, .cpp, no option' @('impl.cpp')
exit 0
60 changes: 60 additions & 0 deletions .github/probe/termux_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# O7 / O8 probe: the published aarch64 mcpp, first run, inside a Termux userland
# (termux/termux-docker on a native arm64 runner). The container has Termux's
# bionic userland and paths but not Android's SELinux, so it shows host
# recognition, the default toolchain and where Planning spends its time, not
# the app-sandbox hard-link rule (see termux_emulator.sh). Prints only.
set -u
V="${MCPP_VERSION}"
DL="$RUNNER_TEMP/termux-dl"
LOGS="$RUNNER_TEMP/termux-logs"
mkdir -p "$DL" "$LOGS"; chmod 777 "$LOGS"
S="$GITHUB_STEP_SUMMARY"
say() { echo "$*"; echo "$*" >> "$S"; }

curl -fsSL -o "$DL/mcpp.tar.gz" \
"https://github.com/mcpp-community/mcpp/releases/download/v$V/mcpp-$V-linux-aarch64.tar.gz"
cp "$GITHUB_WORKSPACE/.github/probe/termux_inner.sh" "$GITHUB_WORKSPACE/.github/probe/termux_feasibility.sh" "$DL/"
chmod -R a+rX "$DL"

say "## termux-docker aarch64 (mcpp $V)"
docker pull -q termux/termux-docker:aarch64 2>&1 | tail -1
docker image inspect termux/termux-docker:aarch64 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}} {{json .Config.User}}' | tee -a "$S"

start=$(date +%s)
docker run --rm --privileged \
-v "$DL:/mnt/dl:ro" -v "$LOGS:/mnt/logs" \
termux/termux-docker:aarch64 bash /mnt/dl/termux_inner.sh 2>&1 | tee "$LOGS/container.log"
say "- pass 1 (image as published) exit: ${PIPESTATUS[0]}, wall: $(( $(date +%s) - start )) s"
say "- /bin/sh in the image: $(docker run --rm --entrypoint /system/bin/sh termux/termux-docker:aarch64 -c 'ls -ld /bin /bin/sh 2>&1' | tr '\n' ' ')"

# Pass 2: Android 10+ devices have /bin -> /system/bin; the image does not.
mkdir -p "$LOGS/pass2"; chmod 777 "$LOGS/pass2"
start=$(date +%s)
docker run --rm --privileged --entrypoint /system/bin/sh \
-v "$DL:/mnt/dl:ro" -v "$LOGS/pass2:/mnt/logs" \
termux/termux-docker:aarch64 -c 'ln -s /system/bin /bin; ls -ld /bin; exec /entrypoint.sh bash -c "bash /mnt/dl/termux_inner.sh; sh /mnt/dl/termux_feasibility.sh \$HOME/dist/mcpp-*-linux-aarch64 \$HOME/feas"' \
2>&1 | tee "$LOGS/pass2/container.log"
say "- pass 2 (/bin -> /system/bin) exit: ${PIPESTATUS[0]}, wall: $(( $(date +%s) - start )) s"

# Pass 3: as pass 2, with the static musl gcc an Android default would use (D21).
mkdir -p "$LOGS/pass3"; chmod 777 "$LOGS/pass3"; echo "--toolchain gcc@15.1.0-musl" > "$LOGS/pass3/build-args"; chmod 666 "$LOGS/pass3/build-args"
start=$(date +%s)
docker run --rm --privileged --entrypoint /system/bin/sh \
-v "$DL:/mnt/dl:ro" -v "$LOGS/pass3:/mnt/logs" -e PROBE_BUILD_ARGS="--toolchain gcc@15.1.0-musl" \
termux/termux-docker:aarch64 -c 'ln -s /system/bin /bin; exec /entrypoint.sh bash /mnt/dl/termux_inner.sh' \
2>&1 | tee "$LOGS/pass3/container.log"
say "- pass 3 (/bin -> /system/bin, --toolchain gcc@15.1.0-musl) exit: ${PIPESTATUS[0]}, wall: $(( $(date +%s) - start )) s"

say '### environment'
say '```'
for f in "$LOGS/container.log" "$LOGS/pass2/container.log" "$LOGS/pass3/container.log"; do echo "-- $f" >> "$S"; grep -E "^(ENV|UNAME|ID|HARDLINK|XLINGS|MCPP|STAGE|TIMING|ELF|BUILD|FEAS)" "$f" | head -60 >> "$S"; done
say '```'
for pass in . pass2 pass3; do
say "### first run ($pass): key lines"
say '```'
grep -E 'First run|Resolving|Resolved|Installing|Downloading|Planning|error|warning|hint|Finished|TIMING|glibc|llvm|musl|Can.t create' \
"$LOGS/$pass/container.log" | grep -v '^\s*$' | head -70 >> "$S"
say '```'
done
exit 0
78 changes: 78 additions & 0 deletions .github/probe/termux_emulator.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# Android app sandbox probe (emulator, debuggable Termux APK, run-as).
# Round 6: can llvm@23.1.3 + the managed glibc run inside the sandbox once the
# payloads carry no hard links, does the latest xlings unpack hard links there,
# and is the static-binary exec failure an artifact of run-as? Prints only.
set -u
V="${MCPP_VERSION}"
XV="${XLINGS_LATEST:-2026.10.10.1}"
ARCH="${EMU_ARCH:-x86_64}"
EMU="$RUNNER_TEMP/termux-emu"
LOGS="$RUNNER_TEMP/termux-emu-logs"
mkdir -p "$EMU" "$LOGS"
S="$GITHUB_STEP_SUMMARY"
say() { echo "$*"; echo "$*" >> "$S"; }
A=/data/data/com.termux/files
case "$ARCH" in x86_64) APKARCH=x86_64; MARCH=x86_64;; *) APKARCH=arm64-v8a; MARCH=aarch64;; esac

case "${1:-}" in
download)
curl -fsSL -o "$EMU/termux.apk" \
"https://github.com/termux/termux-app/releases/download/v0.118.3/termux-app_v0.118.3+github-debug_${APKARCH}.apk"
curl -fsSL -o "$EMU/mcpp.tar.gz" \
"https://github.com/mcpp-community/mcpp/releases/download/v$V/mcpp-$V-linux-$MARCH.tar.gz"
curl -fsSL -o "$EMU/xlings.tar.gz" \
"https://github.com/openxlings/xlings/releases/download/v$XV/xlings-$XV-linux-$MARCH.tar.gz"
cp "$GITHUB_WORKSPACE"/.github/probe/termux_inner.sh "$GITHUB_WORKSPACE"/.github/probe/termux_feasibility.sh "$EMU/"
# Install llvm@23.1.3 + glibc on THIS host at the exact path the app sandbox
# uses, so every absolute path the install writes is the device's, then pack
# it with hard links dereferenced -- the state D19 would produce.
sudo mkdir -p "$A" && sudo chown -R "$(id -u):$(id -g)" /data/data/com.termux
mkdir -p "$A/homeP/dist" "$A/homeP/hello/src"
tar -xzf "$EMU/mcpp.tar.gz" -C "$A/homeP/dist"
D=$(ls -d "$A"/homeP/dist/mcpp-*)
printf '[package]\nname = "hello"\nversion = "0.1.0"\n\n[toolchain]\ndefault = "llvm@23.1.3"\n\n[targets.hello]\nkind = "bin"\nmain = "src/main.cpp"\n' > "$A/homeP/hello/mcpp.toml"
printf 'import std;\nint main() { std::println("host-built hello runs on android"); }\n' > "$A/homeP/hello/src/main.cpp"
(cd "$A/homeP/hello" && MCPP_HOME="$D" "$D/bin/mcpp" build 2>&1 | tail -4)
find "$A/homeP" -type f -links +1 | wc -l | sed 's/^/hard-linked files before packing: /'
tar --hard-dereference -czf "$EMU/prebuilt.tgz" -C "$A" homeP
ls -la "$EMU"
;;
run)
say "## Android app sandbox ($ARCH emulator), round 6"
adb wait-for-device
say "- getenforce: $(adb shell getenforce | tr -d '\r'); release: $(adb shell getprop ro.build.version.release | tr -d '\r')"
adb install -r "$EMU/termux.apk" 2>&1 | tail -1
adb shell am start -n com.termux/com.termux.app.TermuxActivity 2>&1 | tail -1
for i in $(seq 1 60); do adb shell run-as com.termux ls $A/usr/bin/bash >/dev/null 2>&1 && break; sleep 5; done
say "- Termux bootstrap: $(adb shell run-as com.termux ls $A/usr/bin/bash 2>&1 | tr -d '\r')"
for f in mcpp.tar.gz xlings.tar.gz prebuilt.tgz termux_inner.sh termux_feasibility.sh; do
adb push "$EMU/$f" /data/local/tmp/ >/dev/null; adb shell chmod 644 /data/local/tmp/$f
done
RA() { adb shell run-as com.termux "$@"; }
RA mkdir -p $A/probe
for f in mcpp.tar.gz xlings.tar.gz termux_inner.sh termux_feasibility.sh; do RA cp /data/local/tmp/$f $A/probe/$f; done
RA ls -la $A/probe
T="PREFIX=$A/usr TMPDIR=$A/usr/tmp PATH=$A/usr/bin LANG=en_US.UTF-8"

say '### F: llvm@23.1.3 + glibc inside the sandbox (payload without hard links)'
RA $A/usr/bin/env $T HOME=$A/homeP sh -c "'cd $A && tar -xzf /data/local/tmp/prebuilt.tgz && echo extracted'" 2>&1 | tail -2
D=$(RA sh -c "'ls -d $A/homeP/dist/mcpp-*'" | tr -d '\r')
RA $A/usr/bin/env $T HOME=$A/homeP sh $A/probe/termux_feasibility.sh "$D" "$A/homeP/work" 2>&1 | tee "$LOGS/feas.log"
say '```'; grep '^FEAS' "$LOGS/feas.log" >> "$S"; say '```'
say "- host-built hello inside the sandbox: $(RA sh -c "'$A/homeP/hello/target/*/*/bin/hello 2>&1'" | tr -d '\r' | head -2)"

say '### X: the latest xlings unpacking a payload with hard links'
RA $A/usr/bin/env $T HOME=$A/homeX sh -c "'mkdir -p $A/homeX/dist $A/homeX/xl && tar -xzf $A/probe/mcpp.tar.gz -C $A/homeX/dist && tar -xzf $A/probe/xlings.tar.gz -C $A/homeX/xl && XL=\$(find $A/homeX/xl -path \"*/bin/xlings\" -type f | head -1) && D=\$(ls -d $A/homeX/dist/mcpp-*) && cp \$XL \$D/registry/bin/xlings && \$D/registry/bin/xlings --version && cd $A/homeX && XLINGS_HOME=\$D/registry \$D/registry/bin/xlings install xim:glibc@2.44.3 -y'" 2>&1 | tee "$LOGS/xlings.log" | tail -15
say '```'; grep -E 'xlings [0-9]|Can.t create|error|installed|hard' "$LOGS/xlings.log" | head -12 >> "$S"; say '```'

say '### E: termux-exec, with and without its linker exec mode'
for mode in "" "TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE=disable"; do
out=$(RA $A/usr/bin/env $T HOME=$A/homeP LD_PRELOAD=$A/usr/lib/libtermux-exec.so $mode $D/bin/mcpp --version 2>&1 | tr -d '\r' | head -2)
say "- LD_PRELOAD=libtermux-exec.so ${mode:-<default mode>}: \`$out\`"
done
say "- termux-exec package: $(RA $A/usr/bin/env $T $A/usr/bin/dpkg -s termux-exec 2>&1 | grep -E '^Version' | tr -d '\r')"
say "- process context under run-as: $(RA cat /proc/self/attr/current | tr -d '\r\0')"
;;
esac
exit 0
36 changes: 36 additions & 0 deletions .github/probe/termux_feasibility.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh
# Can llvm@23.1.3 + the managed glibc compile and run programs on Android, and
# does running the toolchain's loader environment only on the tool (not on the
# shell) avoid the "/bin/sh cannot link" failure? POSIX sh; prints only.
# $1 = mcpp dist directory (MCPP_HOME) with llvm@23.1.3 already installed
# $2 = a writable work directory
D="$1"; W="$2"
mkdir -p "$W"; cd "$W" || exit 0
LLVM=$(ls -d "$D"/registry/data/xpkgs/xim-x-llvm/23.1.3 2>/dev/null)
echo "FEAS llvm: $LLVM"
TRIPLE=$(ls "$LLVM/lib" | grep -E 'unknown-linux-gnu$' | head -1)
LIBDIR="$LLVM/lib/$TRIPLE"
echo "FEAS lib dir: $LIBDIR"

echo "FEAS P1 clang++ --version:"
"$LLVM/bin/clang++" --version 2>&1 | head -2 | sed 's/^/FEAS /'

printf '#include <cstdio>\n#include <vector>\n#include <thread>\nint main(){ std::vector<int> v{1,2,3}; int s=0; std::thread t([&]{ for(int x: v) s+=x; }); t.join(); std::printf("glibc-llvm program ran, sum=%%d\\n", s); }\n' > h.cpp
echo "FEAS P2 compile + link + run with the payload's own configuration:"
"$LLVM/bin/clang++" -std=c++23 h.cpp -o h 2>&1 | head -10 | sed 's/^/FEAS /'
if [ -x ./h ]; then ./h 2>&1 | sed 's/^/FEAS /'; echo "FEAS exit $?"; else echo "FEAS no binary"; fi
echo "FEAS interp: $(grep -a -o '/[^ ]*ld-linux[^ ]*\.so\.[0-9]*' ./h 2>/dev/null | head -1)"

echo "FEAS P3 the loader environment on the shell (what ninja does today):"
LD_LIBRARY_PATH="$LIBDIR" /system/bin/sh -c 'echo shell-started' 2>&1 | head -2 | sed 's/^/FEAS /'
echo "FEAS P4 the loader environment on the tool only (D29):"
/system/bin/sh -c "env LD_LIBRARY_PATH='$LIBDIR' '$LLVM/bin/clang++' --version" 2>&1 | head -1 | sed 's/^/FEAS /'

echo "FEAS P5 mcpp build with llvm@23.1.3 (today's engine):"
mkdir -p "$W/proj/src"; cd "$W/proj"
printf '[package]\nname = "feas"\nversion = "0.1.0"\n\n[toolchain]\ndefault = "llvm@23.1.3"\n\n[targets.feas]\nkind = "bin"\nmain = "src/main.cpp"\n' > mcpp.toml
printf 'import std;\nint main() { std::println("import std on android"); }\n' > src/main.cpp
MCPP_HOME="$D" timeout 1200 "$D/bin/mcpp" build > build.log 2>&1
echo "FEAS exit $?"
grep -E 'error|CANNOT|Finished|FAILED' build.log | head -6 | sed 's/^/FEAS /'
exit 0
67 changes: 67 additions & 0 deletions .github/probe/termux_inner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/bin/sh
# POSIX sh: under run-as there is only the system shell.
# Runs inside a Termux userland (termux-docker, or an app sandbox through
# run-as). Prints what it sees; asserts nothing.
set -u
OUT="${PROBE_OUT:-/mnt/logs}"
TARBALL="${PROBE_TARBALL:-/mnt/dl/mcpp.tar.gz}"
export HOME="${PROBE_HOME:-${HOME:-/data/data/com.termux/files/home}}"
mkdir -p "$HOME" "$OUT"
export TMPDIR="${TMPDIR:-$HOME/tmp}"; mkdir -p "$TMPDIR"

echo "ENV ANDROID_ROOT=${ANDROID_ROOT-<unset>} ANDROID_DATA=${ANDROID_DATA-<unset>} PREFIX=${PREFIX-<unset>} TERMUX_VERSION=${TERMUX_VERSION-<unset>}"
echo "ENV HOME=$HOME TMPDIR=$TMPDIR SSL_CERT_FILE=${SSL_CERT_FILE-<unset>}"
echo "ENV TERMUX_EXEC: LD_PRELOAD=${LD_PRELOAD-<unset>} $(env | grep -i TERMUX_EXEC | tr '\n' ' ')"
echo "ENV linker64: $(ls -l /system/bin/linker64 2>&1 | head -1)"
echo "UNAME $(uname -a 2>&1)"
echo "ID $(id 2>&1)"
echo "ID selinux: $(cat /proc/self/attr/current 2>&1 | tr -d '\0')"

cd "$HOME"
rm -f hl_a hl_b
echo a > hl_a
if ln hl_a hl_b 2>"$TMPDIR/ln.err"; then echo "HARDLINK ok"; else echo "HARDLINK refused: $(cat "$TMPDIR/ln.err")"; fi
rm -f hl_a hl_b

mkdir -p "$HOME/dist"
tar -xzf "$TARBALL" -C "$HOME/dist" 2>&1 | tail -3
MCPP=$(find "$HOME/dist" -path '*/bin/mcpp' -type f | head -1)
echo "MCPP $MCPP"
"$MCPP" --version 2>&1
XL=$(dirname "$(dirname "$MCPP")")/registry/bin/xlings
echo "XLINGS $XL"
"$XL" --version 2>&1 | head -3
echo "XLINGS self init (direct):"
XLINGS_HOME=$(dirname "$XL")/.. "$XL" self init 2>&1 | tail -25
echo "XLINGS exit: $?"
"$MCPP" self env 2>&1 | head -40

mkdir -p "$HOME/hello/src"
cd "$HOME/hello"
printf '[package]\nname = "hello"\nversion = "0.1.0"\n\n[targets.hello]\nkind = "bin"\nmain = "src/main.cpp"\n' > mcpp.toml
printf 'import std;\nint main() { std::println("hello from termux"); }\n' > src/main.cpp

[ -f "$OUT/build-args" ] && PROBE_BUILD_ARGS=$(cat "$OUT/build-args")
echo "BUILD ARGS: ${PROBE_BUILD_ARGS:-<none>}"
echo "ELF mcpp: $(od -An -tx1 -j16 -N2 "$MCPP" | tr -d ' \n') (0200=ET_EXEC 0300=ET_DYN)"
t0=$(date +%s)
MCPP_LOG_LEVEL=info timeout 3600 "$MCPP" build --verbose ${PROBE_BUILD_ARGS:-} > "$OUT/first-build.log" 2>&1
rc=$?
cat "$OUT/first-build.log"
echo "TIMING first build: exit $rc, $(( $(date +%s) - t0 )) s"
t0=$(date +%s)
timeout 600 "$MCPP" run 2>&1 | tail -5
echo "TIMING run: $(( $(date +%s) - t0 )) s"

mkdir -p "$OUT/mcpp-log"
cp -r "$HOME"/.mcpp/log/. "$OUT/mcpp-log/" 2>/dev/null
cp -r "$(dirname "$(dirname "$MCPP")")"/log/. "$OUT/mcpp-log/" 2>/dev/null
for b in "$(dirname "$(dirname "$MCPP")")"/registry/data/xpkgs/*/*/bin/*; do
case "$b" in *gcc*|*g++*|*clang++|*ld|*as|*ninja|*patchelf) echo "ELF $(basename "$(dirname "$(dirname "$b")")")/$(basename "$b"): $(od -An -tx1 -j16 -N2 "$b" 2>/dev/null | tr -d ' \n')";; esac
done 2>/dev/null | sort -u | head -20
grep -h 'build/stage' "$OUT"/mcpp-log/*.log 2>/dev/null | tail -30 | sed 's/^/STAGE /'
cp "$TMPDIR"/mcpp-xlings-*.stderr "$OUT/" 2>/dev/null
ls "$HOME"/.mcpp/registry/logs/hooks/ 2>/dev/null | head
cp -r "$HOME"/.mcpp/registry/logs "$OUT/registry-logs" 2>/dev/null
cat "$HOME"/.mcpp/config.toml 2>/dev/null | grep -E 'toolchain|target'
exit 0
Loading
Loading