From d22a8603dfaaf9ccb34b4ebccfc7ee90df36d265 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Fri, 2 Oct 2026 23:18:07 +0200 Subject: [PATCH] fix(toolchain): pass -L as well as an rpath for a toolchain outside /usr Inside the Flatpak the bundled toolchain compiled every bootstrap object, then glslang's configure failed its test link with `unable to find library -lc++`. clang searches its per-target runtime directory (lib/) and the system library paths for libc++, not /lib, so with the classic layout a toolchain outside /usr needs the search path spelled out, just as its loader needs the rpath. LibcxxLinkFlags and build.sh now emit both, the same pair an explicit CRAFTER_BUILD_LIBCXX_DIR already got. Co-Authored-By: Claude Fable 5.1 --- build.sh | 17 +++++++++-------- implementations/Crafter.Build-Toolchain.cpp | 13 ++++++++----- interfaces/Crafter.Build-Toolchain.cppm | 10 ++++++---- tests/ToolchainResolve/main.cpp | 4 ++-- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/build.sh b/build.sh index eeff75f..2d7df47 100755 --- a/build.sh +++ b/build.sh @@ -14,12 +14,13 @@ if [ -n "${CRAFTER_BUILD_LLVM_DIR:-}" ]; then CC="$CRAFTER_BUILD_LLVM_DIR/bin/clang" AR="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-ar" LLVM_CONFIG="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-config" - # A prefix other than the system one keeps its libc++ off the loader's - # default path; the rpath is what lets the binary start. + # A prefix other than the system one keeps its libc++ off both the + # linker's search path (clang looks in lib/ and the system dirs, + # not /lib) and the loader's; -L and an rpath cover the two. if [ "$CRAFTER_BUILD_LLVM_DIR" != /usr ]; then - RPATH_FLAG="-Wl,-rpath,$CRAFTER_BUILD_LLVM_DIR/lib" + LIBCXX_LINK_FLAGS="-L$CRAFTER_BUILD_LLVM_DIR/lib -Wl,-rpath,$CRAFTER_BUILD_LLVM_DIR/lib" else - RPATH_FLAG="" + LIBCXX_LINK_FLAGS="" fi if [ -f "$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" ]; then STD_CPPM="$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" @@ -31,7 +32,7 @@ else CC=clang AR=llvm-ar LLVM_CONFIG=llvm-config - RPATH_FLAG="" + LIBCXX_LINK_FLAGS="" STD_CPPM=/usr/share/libc++/v1/std.cppm fi @@ -65,8 +66,8 @@ cmake -B build \ -DCMAKE_C_COMPILER="$CC" \ -DCMAKE_CXX_COMPILER="$CXX" \ -DCMAKE_CXX_FLAGS="-stdlib=libc++" \ - -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \ - -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \ + -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \ + -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$current_dir/build" \ -DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$current_dir/build" \ @@ -117,7 +118,7 @@ rm -f ./lib/libcrafter-build.a "$AR" rcs ./lib/libcrafter-build.a $OBJECTS "$CXX" --target="$TARGET" -std=c++26 -stdlib=libc++ -O3 -march="$MARCH" -mtune="$MTUNE" -fuse-ld=lld \ - -Wl,--export-dynamic $RPATH_FLAG \ + -Wl,--export-dynamic $LIBCXX_LINK_FLAGS \ -L./build \ $OBJECTS \ ./build/main.o \ diff --git a/implementations/Crafter.Build-Toolchain.cpp b/implementations/Crafter.Build-Toolchain.cpp index 8a236a9..d7e4868 100644 --- a/implementations/Crafter.Build-Toolchain.cpp +++ b/implementations/Crafter.Build-Toolchain.cpp @@ -51,14 +51,17 @@ std::string Toolchain::LibcxxCompileFlags() const { } std::string Toolchain::LibcxxLinkFlags() const { - std::string flags; + // clang searches its per-target runtime directory and the system library + // paths for -lc++, not /lib, so a toolchain outside /usr with the + // classic layout needs the search path as well as the run-time path. + fs::path lib; if (!libcxxDir.empty()) { - fs::path lib = libcxxDir / "lib"; - flags += std::format(" -L{} -Wl,-rpath,{}", lib.string(), lib.string()); + lib = libcxxDir / "lib"; } else if (!prefix.empty() && prefix != "/usr") { - flags += std::format(" -Wl,-rpath,{}", (prefix / "lib").string()); + lib = prefix / "lib"; } - return flags; + if (lib.empty()) return {}; + return std::format(" -L{} -Wl,-rpath,{}", lib.string(), lib.string()); } std::string Toolchain::Key() const { diff --git a/interfaces/Crafter.Build-Toolchain.cppm b/interfaces/Crafter.Build-Toolchain.cppm index b0fd2dc..853b2b6 100644 --- a/interfaces/Crafter.Build-Toolchain.cppm +++ b/interfaces/Crafter.Build-Toolchain.cppm @@ -44,10 +44,12 @@ export namespace Crafter { // Flags for compiling against libcxxDir; empty without one. std::string LibcxxCompileFlags() const; - // Flags for linking against the libc++ the compiler will use: an - // rpath into the prefix when that is not the system one (a bundled - // toolchain's libc++.so.1 is not on the loader's default path), plus - // -L and an rpath for an explicit libcxxDir. + // Flags for linking against the libc++ the compiler will use: a -L + // and an rpath for /lib when the prefix is not the system one + // (clang searches only its per-target runtime directory and the + // system paths, and a bundled toolchain's libc++.so.1 is on neither + // the linker's nor the loader's default path), or for an explicit + // libcxxDir. std::string LibcxxLinkFlags() const; // Names this toolchain for cache directory names: the clang version // followed by a hash of what decides which headers and libraries a diff --git a/tests/ToolchainResolve/main.cpp b/tests/ToolchainResolve/main.cpp index aedb261..51d8af6 100644 --- a/tests/ToolchainResolve/main.cpp +++ b/tests/ToolchainResolve/main.cpp @@ -73,7 +73,7 @@ int main() { Check(tc.stdCppm == full / "share/libc++/v1/std.cppm", "the prefix's std module source is used"); Check(tc.wasiSysroot == full / "share/wasi-sysroot", "the prefix's wasi sysroot is used"); Check(tc.LibcxxCompileFlags().empty(), "the compiler finds its own libc++ headers without flags"); - Check(Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (full / "lib").string())), "binaries get an rpath into the prefix's lib dir"); + Check(Contains(tc.LibcxxLinkFlags(), std::format("-L{}", (full / "lib").string())) && Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (full / "lib").string())), "binaries get -L and an rpath for the prefix's lib dir"); } { ToolchainInputs in; @@ -95,7 +95,7 @@ int main() { Check(tc.llvmIncludeDir.empty(), "no clang-c headers in the prefix: nothing added"); Check(tc.stdCppm == "/usr/share/libc++/v1/std.cppm", "no std module in the prefix: the system copy"); Check(tc.wasiSysroot == "/usr/share/wasi-sysroot", "no wasi sysroot in the prefix: the system one"); - Check(Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (bare / "lib").string())), "the rpath follows the explicit prefix"); + Check(Contains(tc.LibcxxLinkFlags(), std::format("-L{}", (bare / "lib").string())) && Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (bare / "lib").string())), "the link flags follow the explicit prefix"); } { ToolchainInputs in;