Compare commits

..
Author SHA1 Message Date
ff2e8c03a9
fix(packaging): classic libc++ layout, no debug info, clean wasm flags
Three findings from building the manifest locally.

LLVM's runtimes build installs libc++ under lib/<triple>/ by default.
clang finds it there, but the rpath crafter-build adds for a toolchain
outside /usr points at <prefix>/lib, and so does every distro layout the
resolver was modelled on; the bootstrap failed to start with
libc++.so.1 missing. LLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF puts the
runtimes in lib/ and include/c++/v1.

flatpak-builder's default CFLAGS carry -g. For all of LLVM that is a
30 GB build tree and a multi-gigabyte .Debug extension; the module now
builds without debug info and strips, keeping the hardening flags. The
no-debuginfo key also moves under build-options, where flatpak-builder
actually reads it.

The same default CFLAGS and LDFLAGS (-fcf-protection, -z relro) reach
wasi-sdk's nested wasm32 builds through the environment, and clang
rejects them for that target. The wasi-sysroot module clears them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:18:10 +02:00
add30b9a4f
feat(packaging): ship crafter-build as a Flatpak with its own toolchain
packaging/flatpak/ holds the manifest for net.catcrafts.CrafterBuild and
its side files. The runtime is the Freedesktop Sdk, not the Platform: a
build tool needs glibc's headers and crt objects, gcc's libgcc_s, cmake,
ninja, git and python at run time. Everything compiler-shaped is ours
under /app, built from one LLVM 22.1.8 source tree: clang, lld, libclang,
libc++ with its std module, compiler-rt for the host and for wasm32, the
WASI sysroot (wasi-sdk 34 in sysroot-only mode, driven by the clang just
built) and wasmtime for running the result. One tree means the modules
crafter-build builds and the compiler that consumes them cannot drift
apart, and the toolchain resolver finds all of it beside the executable
with no configuration.

The crafter-build module runs the bootstrap, the self-host build and the
test suite inside the sandbox, so a Flatpak that installs has passed the
suite with the toolchain it ships. CRAFTER_BUILD_MARCH is x86-64-v2 for
the shipped binary; user projects keep -march=native.

WasmHelloRuns compiles a fixture for wasm32-wasip1 and runs it under
wasmtime. The existing wasm tests only inspect generated files, so this
is the one that proves the sysroot, the std module for wasm and wasm-ld
work together; it skips where the sysroot or wasmtime is missing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:18:10 +02:00
c3485b7ab1
docs: name the toolchain in the cache directory layout
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:18:09 +02:00
59fc9501f1
fix: key PCM and project.so freshness on content, not installed mtimes
std.pcm was rebuilt when it was older than std.cppm, and project.so when
it was older than the crafter-build executable. Both inputs are installed
files, and an OSTree deployment (the Flatpak) gives every installed file
mtime 0: std.cppm and the executable always looked older than the cache,
so a clang bump would never rebuild std.pcm and a crafter-build update
would never recompile project.so. clang crashes on a PCM from another
compiler instead of reporting it, so the failure would have been a
backtrace on the user's first build after an update.

Each artifact now has a one-line stamp beside it. std.pcm's covers the
precompile command and the module source's bytes, which also catches a
libc++ update under the same clang. project.so's covers the crafter-build
version, the toolchain key, the module source stamp and the compile
command; project.cpp's and the pre-dependency artifacts' mtimes remain
inputs, since those live next to it with real timestamps. The module
source stamp stands in for the executable: project.so resolves Crafter::
symbols from the running binary, so only declarations matter, and an
implementation-only rebuild of crafter-build no longer recompiles it.
The std.pcm stamp folds into the module cache stamp, so a rebuilt
std.pcm rebuilds the PCMs that import it.

The cache directory carries the toolchain key
(<target>-<march>-<clang version>-<hash>), so a compiler bump starts
cold instead of handing the new clang PCMs it will crash on. LoadProject
and Build() now derive the directory from the same StdPcmDir. Old
<target>-<march> directories are left behind; `clean` never touched the
cache and still does not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 23:18:09 +02:00
d22a8603df
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>
2026-10-02 23:18:07 +02:00
5 changed files with 28 additions and 21 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

@ -63,8 +63,9 @@ modules:
- -DLLVM_ENABLE_RUNTIMES=compiler-rt;libcxx;libcxxabi;libunwind - -DLLVM_ENABLE_RUNTIMES=compiler-rt;libcxx;libcxxabi;libunwind
- -DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly - -DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly
# Classic layout: libc++ in lib/ and include/c++/v1, not under a # Classic layout: libc++ in lib/ and include/c++/v1, not under a
# per-target subdirectory. It is what clang's own default search, the # per-target subdirectory, the same shape as a distro install.
# rpath crafter-build adds and distro installs all assume. # crafter-build adds -L and an rpath for <prefix>/lib itself, since
# clang searches only its per-target directory and the system paths.
- -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF - -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF
# One shared LLVM and one shared clang library instead of a statically # One shared LLVM and one shared clang library instead of a statically
# linked copy per tool: a fraction of the install size. # linked copy per tool: a fraction of the install size.

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;