Skip to content

Commit 61c25c1

Browse files
wellweiSunrisepeak
authored andcommitted
fix: statically link musl build helpers
1 parent a0d3ed1 commit 61c25c1

5 files changed

Lines changed: 141 additions & 6 deletions

File tree

.github/workflows/ci-aarch64-fresh-install.yml

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ on:
1616
workflow_dispatch:
1717
schedule:
1818
- cron: '0 6 * * 1' # weekly Mon 06:00 UTC
19+
pull_request:
20+
branches: [ main ]
21+
paths:
22+
- 'src/build/build_program.cppm'
23+
- 'src/platform/process.cppm'
24+
- 'tests/e2e/167_build_mcpp_musl_host_static.sh'
25+
- '.github/workflows/ci-aarch64-fresh-install.yml'
1926
push:
2027
branches: [ main ]
2128
paths:
2229
- '.github/workflows/ci-aarch64-fresh-install.yml'
2330

31+
permissions:
32+
contents: read
33+
2434
concurrency:
2535
group: ci-${{ github.workflow }}-${{ github.ref }}
2636
cancel-in-progress: true
@@ -34,6 +44,10 @@ jobs:
3444
# Verbose every mcpp invocation — cold bootstrap path (src/cli.cppm).
3545
MCPP_VERBOSE: "1"
3646
steps:
47+
- uses: actions/checkout@v4
48+
with:
49+
persist-credentials: false
50+
3751
- name: System info
3852
run: |
3953
uname -a
@@ -70,14 +84,38 @@ jobs:
7084
git clone --depth 1 https://github.com/openxlings/xim-pkgindex "$idx"
7185
grep -n "skipping relocation\|os.isfile(path.join(bindir" "$idx/pkgs/m/musl-gcc.lua" | head -2 || true
7286
87+
- name: Build current mcpp source for native regression tests
88+
run: |
89+
mcpp build --target aarch64-linux-musl
90+
self=$(find target/aarch64-linux-musl -type f -name mcpp | head -1)
91+
test -x "$self"
92+
self=$(realpath "$self")
93+
"$self" --version
94+
echo "MCPP_SELF=$self" >> "$GITHUB_ENV"
95+
7396
- name: Native build + run an `import std` program
7497
run: |
7598
work=$(mktemp -d); cd "$work"
76-
mcpp new hello
99+
"$MCPP_SELF" new hello
77100
cd hello
101+
cat > build.mcpp <<'EOF'
102+
#include <cstdio>
103+
int main() {
104+
FILE* marker = std::fopen("helper-ran", "w");
105+
if (!marker) return 2;
106+
std::fputs("ok\n", marker);
107+
std::fclose(marker);
108+
return 0;
109+
}
110+
EOF
78111
# default src uses import std (C++23)
79-
mcpp build
80-
out=$(mcpp run 2>/dev/null || true)
112+
"$MCPP_SELF" build
113+
test -f helper-ran
114+
helper=target/.build-mcpp/build.mcpp.bin
115+
file "$helper"
116+
file "$helper" | grep -q 'statically linked'
117+
! readelf -lW "$helper" | grep -q 'Requesting program interpreter'
118+
out=$("$MCPP_SELF" run 2>/dev/null)
81119
echo "program output: $out"
82120
bin=$(find target -type f -name hello | head -1)
83121
file "$bin"

src/build/build_program.cppm

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,14 @@ std::expected<void, std::string> run_build_program(
585585
auto childEnv = contract_env(root, outDir, env);
586586
std::string ctxHash = contract_hash(childEnv);
587587

588+
const bool staticHostHelper = mcpp::platform::supports_full_static
589+
&& mcpp::toolchain::is_musl_target(tc);
590+
std::string compilerIdentity = hostCompiler.string();
591+
compilerIdentity += staticHostHelper
592+
? "\nbuild-program-link=musl-static-v1"
593+
: "\nbuild-program-link=default-v1";
588594
std::string programHash = mcpp::toolchain::hash_file(src);
589-
std::string compilerHash = mcpp::toolchain::hash_string(hostCompiler.string());
595+
std::string compilerHash = mcpp::toolchain::hash_string(compilerIdentity);
590596

591597
// Fast path: declared inputs + contract unchanged → reapply cached
592598
// directives, no run.
@@ -641,6 +647,11 @@ std::expected<void, std::string> run_build_program(
641647
compileArgv.push_back("-x"); compileArgv.push_back("none");
642648
compileArgv.push_back((bdir / "mcpp.o").string());
643649
}
650+
// A dynamic musl helper requires /lib/ld-musl-<arch>.so.1 on the host,
651+
// which glibc distributions do not provide. Keep the host build program in
652+
// the musl toolchain's documented static world. This is link-only: `base`
653+
// also feeds the bundled module's compile/precompile commands above.
654+
if (staticHostHelper) compileArgv.push_back("-static");
644655
compileArgv.push_back("-o"); compileArgv.push_back(bin.string());
645656
mcpp::ui::info("build.mcpp", "compiling");
646657
// GCC resolves `import mcpp;` via gcm.cache/ relative to the compile cwd, so

src/platform/process.cppm

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ std::vector<std::string> merged_environ(
230230
}
231231
return out;
232232
}
233+
234+
std::string spawn_failure(std::string_view program, int error) {
235+
return std::format("posix_spawnp('{}') failed (error {}): {}\n",
236+
program, error, std::generic_category().message(error));
237+
}
233238
#else
234239
// Build a shell command line from an argv vector (Windows + residual non-POSIX
235240
// fallback only; Linux/macOS exec directly, #248). The first token (program)
@@ -444,7 +449,12 @@ RunResult capture_exec(
444449
int sp = ::posix_spawnp(&pid, cargv[0], &fa, nullptr, cargv.data(), envp.data());
445450
::posix_spawn_file_actions_destroy(&fa);
446451
::close(fds[1]);
447-
if (sp != 0) { ::close(fds[0]); result.exit_code = 127; return result; }
452+
if (sp != 0) {
453+
::close(fds[0]);
454+
result.exit_code = 127;
455+
result.output = spawn_failure(argv.front(), sp);
456+
return result;
457+
}
448458

449459
std::array<char, 4096> buf{};
450460
ssize_t n;
@@ -547,7 +557,12 @@ RunResult capture_exec_deadline(
547557
int sp = ::posix_spawnp(&pid, cargv[0], &fa, nullptr, cargv.data(), envp.data());
548558
::posix_spawn_file_actions_destroy(&fa);
549559
::close(fds[1]);
550-
if (sp != 0) { ::close(fds[0]); result.exit_code = 127; return result; }
560+
if (sp != 0) {
561+
::close(fds[0]);
562+
result.exit_code = 127;
563+
result.output = spawn_failure(argv.front(), sp);
564+
return result;
565+
}
551566

552567
::fcntl(fds[0], F_SETFL, ::fcntl(fds[0], F_GETFL) | O_NONBLOCK);
553568

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# requires: musl elf
3+
# A build.mcpp is a host executable. When the host toolchain targets musl it
4+
# must be fully static, otherwise a glibc host cannot exec the generated helper
5+
# because /lib/ld-musl-<arch>.so.1 is absent.
6+
set -euo pipefail
7+
8+
if [[ "$(uname -s)" != "Linux" ]]; then
9+
echo "SKIP: Linux ELF regression"
10+
exit 0
11+
fi
12+
13+
: "${MCPP:?MCPP must point to the self-built mcpp binary}"
14+
15+
TMP=$(mktemp -d)
16+
trap 'rm -rf "$TMP"' EXIT
17+
cd "$TMP"
18+
"$MCPP" new app > new.log 2>&1 || {
19+
cat new.log
20+
echo "FAIL: mcpp new failed"
21+
exit 1
22+
}
23+
cd app
24+
25+
cat >> mcpp.toml <<'EOF'
26+
[toolchain]
27+
default = "gcc@15.1.0-musl"
28+
EOF
29+
30+
cat > build.mcpp <<'EOF'
31+
#include <cstdio>
32+
int main() {
33+
FILE* marker = std::fopen("helper-ran", "w");
34+
if (!marker) return 2;
35+
std::fputs("ok\n", marker);
36+
std::fclose(marker);
37+
std::puts("mcpp:rerun-if-changed=build.mcpp");
38+
return 0;
39+
}
40+
EOF
41+
42+
"$MCPP" build > build.log 2>&1 || {
43+
cat build.log
44+
echo "FAIL: musl build.mcpp helper did not run on the glibc host"
45+
exit 1
46+
}
47+
48+
helper=target/.build-mcpp/build.mcpp.bin
49+
test -f helper-ran || { cat build.log; echo "FAIL: build.mcpp did not run"; exit 1; }
50+
test -x "$helper" || { cat build.log; echo "FAIL: helper binary missing"; exit 1; }
51+
52+
file "$helper"
53+
file "$helper" | grep -q 'statically linked' || {
54+
echo "FAIL: musl host helper is not statically linked"
55+
exit 1
56+
}
57+
if readelf -lW "$helper" | grep -q 'Requesting program interpreter'; then
58+
readelf -lW "$helper"
59+
echo "FAIL: musl host helper contains PT_INTERP"
60+
exit 1
61+
fi
62+
63+
echo "OK"

tests/unit/test_process_run_exec.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ TEST(RunExec, ReturnsErrorWhenProgramMissing) {
4444
EXPECT_NE(process::run_exec({"/no/such/program/mcpp-xyz"}), 0);
4545
}
4646

47+
TEST(CaptureExec, MissingProgramReportsSpawnFailure) {
48+
constexpr std::string_view missing = "/no/such/program/mcpp-capture-xyz";
49+
auto r = process::capture_exec({std::string(missing)});
50+
EXPECT_EQ(r.exit_code, 127);
51+
EXPECT_NE(r.output.find(missing), std::string::npos) << r.output;
52+
EXPECT_NE(r.output.find("error 2"), std::string::npos) << r.output;
53+
}
54+
4755
TEST(CaptureExec, CapturesStdoutWithoutShell) {
4856
auto r = process::capture_exec({"/bin/echo", "hello world"});
4957
EXPECT_EQ(r.exit_code, 0);

0 commit comments

Comments
 (0)