diff --git a/src/build/plan.cppm b/src/build/plan.cppm index fc4968715..b743f9265 100644 --- a/src/build/plan.cppm +++ b/src/build/plan.cppm @@ -2496,10 +2496,28 @@ make_plan(const mcpp::manifest::Manifest& manifest, // runtime: `provides = ["mcpp:..."]`) is beneath every image, and // where its objects go is the runtime contract's decision, which // #641 made (`cxx_runtime = { shared = "self-contained" }`). + // + // A KERNEL-ABI PACKAGE UNDER NO C LIBRARY OF THE GRAPH IS NOT A LAYER. + // When the C library is the platform's (glibc, libSystem, the Windows + // CRT), openkal-linux (-macos, -windows) is a static library over it + // that a package (`mcpplibs.os`) depends on like any other. Treated as + // a layer, it went into the programs only, and a shared image that + // reached it was left with `kal_*` undefined: ELF linked it, and a + // host that had not linked the package -- CPython importing an + // extension module -- refused to load it (`undefined symbol: + // kal_process_close`); Mach-O and PE refuse the link. It is beneath + // every image when a C library of the graph (`mcpp:c-abi`, openkal- + // musl) sits on it, and placed like any static package otherwise. + const bool graphHasCLibrary = std::ranges::any_of(packages, [](const auto& pkg) { + return std::ranges::any_of(pkg.manifest.provides, [](const std::string& p) { + return p.starts_with("mcpp:c-abi"); + }); + }); std::set boundaries; for (std::size_t i = 1; i < packages.size(); ++i) { auto const& pm = packages[i].manifest; - const bool layer = std::ranges::any_of(pm.provides, [](const std::string& p) { + const bool layer = std::ranges::any_of(pm.provides, [&](const std::string& p) { + if (p.starts_with("mcpp:kernel-abi")) return graphHasCLibrary; return p.starts_with("mcpp:") || p == "hosted-standard-library"; }); if (layer || mcpp::pack::is_distribution_package(pm)) boundaries.insert(i); diff --git a/tests/e2e/940_a_kernel_abi_package_over_the_system_c_library_is_in_the_shared_image.sh b/tests/e2e/940_a_kernel_abi_package_over_the_system_c_library_is_in_the_shared_image.sh new file mode 100755 index 000000000..a4d937a07 --- /dev/null +++ b/tests/e2e/940_a_kernel_abi_package_over_the_system_c_library_is_in_the_shared_image.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# requires: elf +# 940 -- a `kernel-abi` package over the platform's C library is linked into a +# shared image that reaches it, like any static package. +# +# THE DEFECT. Every package that provides an `mcpp:` layer was a boundary of +# static placement (#646 F1): never placed into a shared image, its objects +# went into the programs only. That is right for a C library or a C++ runtime, +# whose contract decides where they go. It is not right for openkal-linux +# (-macos, -windows) on a hosted target: there the C library is the system's, +# and the kernel-abi package is a static library over it that `mcpplibs.os` +# depends on like any other. A shared image reaching it was left with `kal_*` +# undefined. ELF linked it, and a host that had not linked the package itself +# could not load it -- measured with CPython importing an extension module +# built on `mcpplibs.os`: +# +# ImportError: .../_openmui.abi3.so: undefined symbol: kal_process_close +# +# and Mach-O and PE refuse the link outright. +# +# Two legs, both a program over a dependency's shared image: +# A the program reaches the kernel-abi package only through the image: the +# image defines its symbols and does not import them, as it does for the +# ordinary static package beside it. +# B the program reaches the package itself too: it links and runs as +# before. +set -e + +MCPP="${MCPP:-mcpp}" +work="$(mktemp -d)" +trap 'rm -rf "$work"' EXIT +cd "$work" +mkdir -p kernel/src sys/src ext/src app/src + +cat > kernel/src/kernel.c <<'EOF' +int kal_fake_answer(void) { return 42; } +EOF +cat > kernel/mcpp.toml <<'EOF' +[package] +name = "fakekernel" +version = "0.1.0" +provides = ["mcpp:kernel-abi=openkal"] + +[targets.fakekernel] +kind = "lib" +sources = ["src/*.c"] +EOF + +cat > sys/src/sys.c <<'EOF' +extern int kal_fake_answer(void); +int sys_answer(void) { return kal_fake_answer(); } +EOF +cat > sys/mcpp.toml <<'EOF' +[package] +name = "fakesys" +version = "0.1.0" + +[targets.fakesys] +kind = "lib" +sources = ["src/*.c"] + +[dependencies] +fakekernel = { path = "../kernel" } +EOF + +cat > ext/src/ext.c <<'EOF' +extern int sys_answer(void); +int ext_answer(void) { return sys_answer(); } +EOF +cat > ext/mcpp.toml <<'EOF' +[package] +name = "ext" +version = "0.1.0" + +[build] +sources = ["src/*.c"] + +[targets.ext] +kind = "shared" + +[dependencies] +fakesys = { path = "../sys" } +EOF + +write_app() { # $1 = an extra dependency line, $2 = what main checks + cat > app/src/main.c < app/mcpp.toml <&1) || { echo "$out" >&2; echo "FAIL: $1: the program must build" >&2; exit 1; } + so="$(find app/target -name 'libext.so' -type f | head -1)" + bin="$(find app/target -path '*/bin/app' -type f | head -1)" + if [ -z "$so" ] || [ -z "$bin" ]; then + echo "$out" >&2; echo "FAIL: $1: libext.so or bin/app was not produced" >&2; exit 1 + fi + "$bin" || { echo "FAIL: $1: the program ran and answered $?" >&2; exit 1; } +} + +# ── A. the image is the package's one home ──────────────────────────────── +write_app "" "ext_answer() == 42" +build_app A +nm --defined-only "$so" | grep -qw sys_answer || { + echo "FAIL: A: the shared image does not define sys_answer (its static dependency)" >&2; exit 1; } +if nm -D --undefined-only "$so" | grep -qw kal_fake_answer; then + nm -D --undefined-only "$so" >&2 + echo "FAIL: A: the shared image imports kal_fake_answer from the program; a host" \ + "that did not link fakekernel cannot load it" >&2 + exit 1 +fi +nm --defined-only "$so" | grep -qw kal_fake_answer || { + echo "FAIL: A: the shared image does not define kal_fake_answer" >&2; exit 1; } +echo "OK: A (the shared image defines the kernel-abi package's symbols)" + +# ── B. the program reaches the package itself ───────────────────────────── +write_app 'fakekernel = { path = "../kernel" }' "ext_answer() == 42 && kal_fake_answer() == 42" +build_app B +echo "OK: B (a program reaching the package links and runs)"