Compare commits

..
Author SHA1 Message Date
7ab892ccfe
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:02:44 +02:00
587c722a1d
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 22:34:57 +02:00
f72dc8f396
docs: name the toolchain in the cache directory layout
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:32:35 +02:00
e2daeab8e0
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 22:32:35 +02:00
5 changed files with 21 additions and 28 deletions

View file

@ -14,13 +14,12 @@ 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 both the
# 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.
# A prefix other than the system one keeps its libc++ off the loader's
# default path; the rpath is what lets the binary start.
if [ "$CRAFTER_BUILD_LLVM_DIR" != /usr ]; then
LIBCXX_LINK_FLAGS="-L$CRAFTER_BUILD_LLVM_DIR/lib -Wl,-rpath,$CRAFTER_BUILD_LLVM_DIR/lib"
RPATH_FLAG="-Wl,-rpath,$CRAFTER_BUILD_LLVM_DIR/lib"
else
LIBCXX_LINK_FLAGS=""
RPATH_FLAG=""
fi
if [ -f "$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm" ]; then
STD_CPPM="$CRAFTER_BUILD_LLVM_DIR/share/libc++/v1/std.cppm"
@ -32,7 +31,7 @@ else
CC=clang
AR=llvm-ar
LLVM_CONFIG=llvm-config
LIBCXX_LINK_FLAGS=""
RPATH_FLAG=""
STD_CPPM=/usr/share/libc++/v1/std.cppm
fi
@ -66,8 +65,8 @@ cmake -B build \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_CXX_FLAGS="-stdlib=libc++" \
-DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \
-DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $LIBCXX_LINK_FLAGS" \
-DCMAKE_EXE_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \
-DCMAKE_SHARED_LINKER_FLAGS="-stdlib=libc++ $RPATH_FLAG" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$current_dir/build" \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$current_dir/build" \
@ -118,7 +117,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 $LIBCXX_LINK_FLAGS \
-Wl,--export-dynamic $RPATH_FLAG \
-L./build \
$OBJECTS \
./build/main.o \

View file

@ -51,17 +51,14 @@ std::string Toolchain::LibcxxCompileFlags() const {
}
std::string Toolchain::LibcxxLinkFlags() const {
// 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;
std::string flags;
if (!libcxxDir.empty()) {
lib = libcxxDir / "lib";
fs::path lib = libcxxDir / "lib";
flags += std::format(" -L{} -Wl,-rpath,{}", lib.string(), lib.string());
} else if (!prefix.empty() && prefix != "/usr") {
lib = prefix / "lib";
flags += std::format(" -Wl,-rpath,{}", (prefix / "lib").string());
}
if (lib.empty()) return {};
return std::format(" -L{} -Wl,-rpath,{}", lib.string(), lib.string());
return flags;
}
std::string Toolchain::Key() const {

View file

@ -44,12 +44,10 @@ 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: a -L
// and an rpath for <prefix>/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.
// 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.
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

View file

@ -63,9 +63,8 @@ modules:
- -DLLVM_ENABLE_RUNTIMES=compiler-rt;libcxx;libcxxabi;libunwind
- -DLLVM_TARGETS_TO_BUILD=X86;AArch64;WebAssembly
# Classic layout: libc++ in lib/ and include/c++/v1, not under a
# per-target subdirectory, the same shape as a distro install.
# crafter-build adds -L and an rpath for <prefix>/lib itself, since
# clang searches only its per-target directory and the system paths.
# per-target subdirectory. It is what clang's own default search, the
# rpath crafter-build adds and distro installs all assume.
- -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF
# One shared LLVM and one shared clang library instead of a statically
# 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.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("-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");
Check(Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (full / "lib").string())), "binaries get an rpath into 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("-L{}", (bare / "lib").string())) && Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (bare / "lib").string())), "the link flags follow the explicit prefix");
Check(Contains(tc.LibcxxLinkFlags(), std::format("-Wl,-rpath,{}", (bare / "lib").string())), "the rpath follows the explicit prefix");
}
{
ToolchainInputs in;