Skip to content

Commit 5795918

Browse files
committed
debug(do-not-merge): instrument the ninja invocation and the Windows popen leg
mcpp-index's Windows job dies with a bare 127 on the ffmpeg member: no mcpp output, no "build failed" diagnostic, and target/.build_cache never written. Four rounds of black-box probing narrowed it to "after build.ninja is generated" and ruled out xlings install, NASM provisioning, disk space, the SPEC-001 rename and the soname-alias regression — but the remaining question (is it the generated build.ninja, or how mcpp invokes ninja?) cannot be answered from outside the process. So print it from inside: * ninja_backend — the full ninja argv, the env pairs, then the exit code and captured output of every invocation. Unconditional, not verbose-gated: the failing run produces no output at all, so anything gated is useless here. * process.cppm capture() — the exact command string and its length. Windows funnels every argv through this one string into cmd.exe, which caps a command line at 8191 characters; ffmpeg (2281 TUs, 157 .asm) is the only member large enough for that to be a live hypothesis, and it is the only member that fails. Plus a Windows-only workflow that publishes the instrumented binary under a throwaway prerelease tag, so the index probe PR can pin it by version. No mirroring, no ecosystem steps, one platform. Verified locally on Linux: the traces fire and the build still passes.
1 parent 3901c1a commit 5795918

3 files changed

Lines changed: 142 additions & 0 deletions

File tree

‎.github/workflows/debug-win.yml‎

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: debug-win (do not merge)
2+
3+
# Publishes a Windows-only, instrumented mcpp under a throwaway prerelease tag
4+
# so mcpp-index's probe PR can pin it and reproduce the exit-127 failure of the
5+
# ffmpeg member with full tracing. Deliberately narrow: one platform, no
6+
# mirroring, no ecosystem steps, no smoke matrix.
7+
#
8+
# Delete this workflow and its branch once mcpp#... is understood.
9+
10+
on:
11+
push:
12+
branches: [ 'debug/win127-instrumentation' ]
13+
workflow_dispatch:
14+
15+
env:
16+
DBG_VERSION: 0.0.107-dbg
17+
DBG_TAG: v0.0.107-dbg
18+
19+
jobs:
20+
windows:
21+
name: build instrumented mcpp (Windows / x86_64)
22+
runs-on: windows-latest
23+
permissions:
24+
contents: write
25+
timeout-minutes: 45
26+
env:
27+
MCPP_HOME: C:\Users\runneradmin\.mcpp
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Cache mcpp sandbox
32+
uses: actions/cache@v4
33+
with:
34+
path: ~\.mcpp
35+
key: mcpp-sandbox-${{ runner.os }}-debugwin-${{ hashFiles('mcpp.toml', '.xlings.json') }}
36+
restore-keys: |
37+
mcpp-sandbox-${{ runner.os }}-debugwin-
38+
39+
- name: Cache xlings
40+
uses: actions/cache@v4
41+
with:
42+
path: ~\.xlings
43+
key: xlings-${{ runner.os }}-debugwin-${{ hashFiles('.xlings.json') }}
44+
restore-keys: |
45+
xlings-${{ runner.os }}-debugwin-
46+
47+
- name: Bootstrap mcpp via xlings
48+
shell: bash
49+
env:
50+
XLINGS_NON_INTERACTIVE: '1'
51+
XLINGS_VERSION: '0.4.69'
52+
run: |
53+
WORK=$(mktemp -d)
54+
zipfile="xlings-${XLINGS_VERSION}-windows-x86_64.zip"
55+
curl -fsSL -o "${WORK}/${zipfile}" \
56+
"https://github.com/openxlings/xlings/releases/download/v${XLINGS_VERSION}/${zipfile}"
57+
cd "${WORK}"
58+
unzip -q "${zipfile}"
59+
"$WORK/xlings-${XLINGS_VERSION}-windows-x86_64/subos/default/bin/xlings.exe" self install
60+
export PATH="$USERPROFILE/.xlings/subos/default/bin:$PATH"
61+
echo "$USERPROFILE/.xlings/subos/default/bin" >> "$GITHUB_PATH"
62+
xlings.exe install mcpp -y
63+
MCPP=$(find "$USERPROFILE/.xlings" -name "mcpp.exe" -path "*/bin/*" 2>/dev/null | head -1)
64+
test -n "$MCPP" || { echo "FAIL: mcpp not found after xlings install"; exit 1; }
65+
"$MCPP" --version
66+
echo "MCPP=$MCPP" >> "$GITHUB_ENV"
67+
echo "XLINGS_BIN=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe")" >> "$GITHUB_ENV"
68+
echo "XLINGS_BIN_UNIX=$USERPROFILE/.xlings/subos/default/bin/xlings.exe" >> "$GITHUB_ENV"
69+
70+
- name: Build instrumented mcpp from source
71+
shell: bash
72+
run: |
73+
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
74+
"$MCPP" build
75+
# Newest by mtime — target/ comes back from cache with one directory
76+
# per fingerprint, and `find | head -1` has already shipped the wrong
77+
# binary once (a 0.0.106 build running the 0.0.105 exe).
78+
MCPP_BIN=$(find target -name "mcpp.exe" -path "*/bin/*" -printf "%T@ %p\n" \
79+
| sort -rn | head -1 | cut -d" " -f2-)
80+
test -n "$MCPP_BIN" || { echo "FAIL: no mcpp.exe in target/"; exit 1; }
81+
MCPP_BIN=$(cd "$(dirname "$MCPP_BIN")" && pwd)/$(basename "$MCPP_BIN")
82+
"$MCPP_BIN" --version
83+
echo "MCPP_BIN=$MCPP_BIN" >> "$GITHUB_ENV"
84+
85+
- name: Package as the release layout mcpp-index expects
86+
shell: bash
87+
run: |
88+
WRAPPER="mcpp-${DBG_VERSION}-windows-x86_64"
89+
STAGING=$(mktemp -d)
90+
mkdir -p "$STAGING/$WRAPPER/bin" "$STAGING/$WRAPPER/registry/bin"
91+
cp "$MCPP_BIN" "$STAGING/$WRAPPER/bin/mcpp.exe"
92+
printf '@echo off\r\n"%%~dp0bin\\mcpp.exe" %%*\r\n' > "$STAGING/$WRAPPER/mcpp.bat"
93+
cp "$XLINGS_BIN_UNIX" "$STAGING/$WRAPPER/registry/bin/xlings.exe"
94+
mkdir -p dist
95+
(cd "$STAGING" && 7z a -tzip "${WRAPPER}.zip" "$WRAPPER")
96+
cp "$STAGING/${WRAPPER}.zip" "dist/${WRAPPER}.zip"
97+
(cd dist && sha256sum "${WRAPPER}.zip" > "${WRAPPER}.zip.sha256")
98+
ls -la dist/
99+
100+
- name: Publish to the throwaway prerelease tag
101+
uses: softprops/action-gh-release@v2
102+
with:
103+
tag_name: ${{ env.DBG_TAG }}
104+
name: ${{ env.DBG_TAG }} (debug build — do not use)
105+
prerelease: true
106+
body: |
107+
Instrumented Windows build for diagnosing the mcpp-index exit-127
108+
failure on the ffmpeg member. Not a real release; delete when done.
109+
files: |
110+
dist/mcpp-${{ env.DBG_VERSION }}-windows-x86_64.zip
111+
dist/mcpp-${{ env.DBG_VERSION }}-windows-x86_64.zip.sha256

‎src/build/ninja_backend.cppm‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,9 +1213,29 @@ std::expected<BuildResult, BuildError> NinjaBackend::build(const BuildPlan& plan
12131213
for (auto& ev : plan.toolchain.envOverrides)
12141214
nenv.emplace_back(ev.key, ev.value);
12151215

1216+
// DEBUG(win127): unconditional trace of every ninja invocation and its
1217+
// raw result. The Windows failure exits 127 with no diagnostic at all, so
1218+
// the normal verbose-gated logging is not enough — we need the argv, the
1219+
// exit code and whatever the child managed to print, on every call.
1220+
{
1221+
std::string joined;
1222+
for (auto& a : nargv) { joined += '['; joined += a; joined += "] "; }
1223+
std::println(stderr, "[win127] ninja argv: {}", joined);
1224+
std::println(stderr, "[win127] env pairs: {}", nenv.size());
1225+
for (auto& [k, v] : nenv)
1226+
std::println(stderr, "[win127] {}={}", k,
1227+
v.size() > 400 ? v.substr(0, 400) + "...<truncated>" : v);
1228+
std::fflush(stderr);
1229+
}
12161230
auto cap = mcpp::platform::process::capture_exec(nargv, nenv);
12171231
std::string out = cap.output;
12181232
bool ok = (cap.exit_code == 0);
1233+
std::println(stderr, "[win127] ninja exit={} output_bytes={}",
1234+
cap.exit_code, out.size());
1235+
if (!out.empty())
1236+
std::println(stderr, "[win127] ninja output:\n{}",
1237+
out.size() > 4000 ? out.substr(0, 4000) + "\n...<truncated>" : out);
1238+
std::fflush(stderr);
12191239

12201240
r.exitCode = ok ? 0 : 1;
12211241
r.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(

‎src/platform/process.cppm‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module;
2424
#endif
2525
#include <cstdio>
2626
#include <cstdlib>
27+
#include <cerrno> // errno — needed on every platform by the win127 trace
2728
#if defined(_WIN32)
2829
#include <stdlib.h> // _putenv_s
2930
#define popen _popen
@@ -261,8 +262,18 @@ RunResult capture(std::string_view command) {
261262
auto cmd = seal_stdin(command);
262263
RunResult result;
263264

265+
// DEBUG(win127): the Windows leg funnels every argv through this single
266+
// command string, and a failure here surfaces only as a bare exit code.
267+
// Print the string and its length — cmd.exe caps a command line at 8191
268+
// characters, which is a live hypothesis for the ffmpeg member.
269+
std::println(stderr, "[win127] popen len={} cmd={}", cmd.size(),
270+
cmd.size() > 3000 ? cmd.substr(0, 3000) + "...<truncated>" : cmd);
271+
std::fflush(stderr);
272+
264273
std::FILE* fp = ::popen(cmd.c_str(), "r");
265274
if (!fp) {
275+
std::println(stderr, "[win127] popen FAILED (errno={})", errno);
276+
std::fflush(stderr);
266277
result.exit_code = -1;
267278
return result;
268279
}

0 commit comments

Comments
 (0)