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/<triple>) and the system library paths for libc++, not
<prefix>/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 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-02 23:18:07 +02:00
commit d22a8603df
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
4 changed files with 25 additions and 19 deletions

View file

@ -14,12 +14,13 @@ if [ -n "${CRAFTER_BUILD_LLVM_DIR:-}" ]; then
CC="$CRAFTER_BUILD_LLVM_DIR/bin/clang" CC="$CRAFTER_BUILD_LLVM_DIR/bin/clang"
AR="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-ar" AR="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-ar"
LLVM_CONFIG="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-config" LLVM_CONFIG="$CRAFTER_BUILD_LLVM_DIR/bin/llvm-config"
# A prefix other than the system one keeps its libc++ off the loader's # A prefix other than the system one keeps its libc++ off both the
# default path; the rpath is what lets the binary start. # linker's search path (clang looks in lib/<triple> and the system dirs,
# not <prefix>/lib) and the loader's; -L and an rpath cover the two.
if [ "$CRAFTER_BUILD_LLVM_DIR" != /usr ]; then 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 else
RPATH_FLAG="" LIBCXX_LINK_FLAGS=""
fi fi
if [ -f "$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" ]; then if [ -f "$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" ]; then
STD_CPPM="$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" STD_CPPM="$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm"
@ -31,7 +32,7 @@ else
CC=clang CC=clang
AR=llvm-ar AR=llvm-ar
LLVM_CONFIG=llvm-config LLVM_CONFIG=llvm-config
RPATH_FLAG="" LIBCXX_LINK_FLAGS=""
STD_CPPM=/usr/share/libc++/v1/std.cppm STD_CPPM=/usr/share/libc++/v1/std.cppm
fi fi
@ -65,8 +66,8 @@ cmake -B build \
-DCMAKE_C_COMPILER="$CC" \ -DCMAKE_C_COMPILER="$CC" \
-DCMAKE_CXX_COMPILER="$CXX" \ -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" \ -DCMAKE_CXX_FLAGS="-stdlib=libc++" \
-DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \ -DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \
-DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \ -DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$current_dir/build" \ -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$current_dir/build" \
-DCMAKE_LIBRARY_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 "$AR" rcs ./lib/libcrafter-build.a $OBJECTS
"$CXX" --target="$TARGET" -std=c++26 -stdlib=libc++ -O3 -march="$MARCH" -mtune="$MTUNE" -fuse-ld=lld \ "$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 \ -L./build \
$OBJECTS \ $OBJECTS \
./build/main.o \ ./build/main.o \

View file

@ -51,14 +51,17 @@ std::string Toolchain::LibcxxCompileFlags() const {
} }
std::string Toolchain::LibcxxLinkFlags() const { std::string Toolchain::LibcxxLinkFlags() const {
std::string flags; // clang searches its per-target runtime directory and the system library
// paths for -lc++, not <prefix>/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()) { if (!libcxxDir.empty()) {
fs::path lib = libcxxDir / "lib"; lib = libcxxDir / "lib";
flags += std::format(" -L{} -Wl,-rpath,{}", lib.string(), lib.string());
} else if (!prefix.empty() && prefix != "/usr") { } 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 { std::string Toolchain::Key() const {

View file

@ -44,10 +44,12 @@ export namespace Crafter {
// Flags for compiling against libcxxDir; empty without one. // Flags for compiling against libcxxDir; empty without one.
std::string LibcxxCompileFlags() const; std::string LibcxxCompileFlags() const;
// Flags for linking against the libc++ the compiler will use: an // Flags for linking against the libc++ the compiler will use: a -L
// rpath into the prefix when that is not the system one (a bundled // and an rpath for <prefix>/lib when the prefix is not the system one
// toolchain's libc++.so.1 is not on the loader's default path), plus // (clang searches only its per-target runtime directory and the
// -L and an rpath for an explicit libcxxDir. // 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; std::string LibcxxLinkFlags() const;
// Names this toolchain for cache directory names: the clang version // Names this toolchain for cache directory names: the clang version
// followed by a hash of what decides which headers and libraries a // followed by a hash of what decides which headers and libraries a

View file

@ -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.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.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(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; ToolchainInputs in;
@ -95,7 +95,7 @@ int main() {
Check(tc.llvmIncludeDir.empty(), "no clang-c headers in the prefix: nothing added"); 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.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(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; ToolchainInputs in;