Crafter.Build/build.sh
Jorijn van der Graaf 046609ff6f
feat(toolchain): resolve the compiler in one place
Every clang++, clang and llvm-ar the tool spawned was a bare name on
PATH, the std module source was /usr/share/libc++/v1/std.cppm, the WASI
sysroot /usr/share/wasi-sysroot and the mingw headers
/usr/x86_64-w64-mingw32, spelled out at eighteen call sites. A
toolchain installed anywhere else, which is what the Flatpak ships
under /app, had no way in.

Crafter.Build:Toolchain resolves all of it once per process:
CRAFTER_BUILD_LLVM_DIR, else a clang++ installed beside crafter-build
(<prefix>/bin, found through the same walk GetCrafterBuildHome does),
else PATH. What sits beside the compiler is used when present: its
libclang.so, its clang-c headers, its std module source, its WASI
sysroot. An explicit prefix without a clang++ is an error rather than a
fallback, for the same reason CRAFTER_BUILD_LIBCLANG was already
exclusive. CRAFTER_BUILD_LIBCXX_DIR points at a libc++ install other
than the compiler's own, and CRAFTER_MINGW_DIR now does what its help
line always claimed: it is passed as --sysroot for the mingw target.
Binaries linked by a toolchain outside /usr get an rpath to its lib
dir, since its libc++.so.1 is not on the loader's default path.

HostTarget() is the resolved clang's -print-target-triple. A clang built
without a vendor reports x86_64-unknown-linux-gnu where Arch's reports
x86_64-pc-linux-gnu, so target equality that decides whether a test runs
locally and which targets a bare `test` sweeps goes through SameTarget,
which ignores the vendor field. The strings themselves are never
rewritten: clang rejects a PCM whose triple differs from the importing
unit's. The examples and README stop hardcoding the host triple; the
default is already the host.

`crafter-build --version` prints the version baked in at build time
(CRAFTER_BUILD_VERSION, else `git describe`, else 0.0.0-dev), the
compiler and the host triple, and is answered before any argument is
forwarded to project.cpp. The version is a define on the library, so
each version is its own library variant and relinks the executable and
tests once per commit.

build.sh gains a shebang and `set -eu`, honours CRAFTER_BUILD_LLVM_DIR
and CRAFTER_BUILD_VERSION, skips the glslang clone when it is already
there, and writes a fresh archive instead of appending to one that may
hold members from an older layout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 22:26:31 +02:00

126 lines
5.5 KiB
Shell
Executable file

#!/bin/sh
#SPDX-License-Identifier: LGPL-3.0-only
#SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
# Bootstrap: compile crafter-build by hand, without crafter-build. The
# self-host build through project.cpp follows (`./bin/crafter-build`).
set -eu
# The toolchain. CRAFTER_BUILD_LLVM_DIR names an LLVM prefix (the Flatpak
# sets /app); otherwise the tools come from PATH. The same rule the built
# binary applies at run time (Crafter.Build:Toolchain).
if [ -n "${CRAFTER_BUILD_LLVM_DIR:-}" ]; then
CXX="$CRAFTER_BUILD_LLVM_DIR/bin/clang++"
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.
if [ "$CRAFTER_BUILD_LLVM_DIR" != /usr ]; then
RPATH_FLAG="-Wl,-rpath,$CRAFTER_BUILD_LLVM_DIR/lib"
else
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"
else
STD_CPPM=/usr/share/libc++/v1/std.cppm
fi
else
CXX=clang++
CC=clang
AR=llvm-ar
LLVM_CONFIG=llvm-config
RPATH_FLAG=""
STD_CPPM=/usr/share/libc++/v1/std.cppm
fi
# The version baked into the binary: the environment (CI sets it from the
# tag), else the nearest tag, else a development marker. `latest` is the
# rolling CI tag and never a version.
VERSION="${CRAFTER_BUILD_VERSION:-$(git describe --tags --always --dirty --exclude=latest 2>/dev/null || echo 0.0.0-dev)}"
mkdir -p build bin lib share/crafter-build share/crafter-build/wasi-runtime
cp wasi-runtime/runtime.js share/crafter-build/wasi-runtime/
cp wasi-runtime/index.html.in share/crafter-build/wasi-runtime/
# Partition order is the import order: every unit is listed before the
# units that import it, and the umbrella last.
PARTITIONS="Toolchain Shader Platform Interface Implementation External Clang Test Lint Progress Asset"
cp interfaces/Crafter.Build.cppm share/crafter-build/
for p in $PARTITIONS; do
cp "interfaces/Crafter.Build-$p.cppm" share/crafter-build/
done
if [ ! -d build/glslang/.git ]; then
git clone --depth 1 https://github.com/KhronosGroup/glslang.git build/glslang
fi
current_dir=$(pwd)
cd build/glslang
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_BUILD_TYPE=Release \
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY="$current_dir/build" \
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY="$current_dir/build" \
-DENABLE_OPT=OFF
cmake --build build --config Release
cd ../../
MARCH="${CRAFTER_BUILD_MARCH:-native}"
MTUNE="${CRAFTER_BUILD_MTUNE:-native}"
# The bootstrap targets the toolchain's own triple: x86_64-pc-linux-gnu on
# glibc distros, x86_64-unknown-linux-gnu for the bundled LLVM,
# x86_64-alpine-linux-musl on Alpine. Override with CRAFTER_BUILD_TARGET.
TARGET="${CRAFTER_BUILD_TARGET:-$("$CXX" -print-target-triple)}"
TARGET_DEFINE="CRAFTER_BUILD_CONFIGURATION_TARGET_$(printf '%s' "$TARGET" | tr - _)"
# Crafter.Build-Lint includes libclang's clang-c/Index.h. glibc distros keep
# it in /usr/include; other prefixes keep it under their own include dir, so
# ask llvm-config and add the dir when it is not already on the default path
# (never add /usr/include itself: that breaks libc++'s #include_next).
LLVM_INCDIR="$("$LLVM_CONFIG" --includedir 2>/dev/null || true)"
case "$LLVM_INCDIR" in ""|/usr/include) LLVM_INCFLAG="" ;; *) LLVM_INCFLAG="-I$LLVM_INCDIR" ;; esac
common_options="--target=$TARGET -stdlib=libc++ -I./build/glslang $LLVM_INCFLAG -std=c++26 -O3 -march=$MARCH -mtune=$MTUNE -fprebuilt-module-path=./build -D $TARGET_DEFINE -D CRAFTER_BUILD_CONFIGURATION_TYPE_EXECUTABLE -D CRAFTER_BUILD_VERSION=\"$VERSION\" -c"
"$CXX" --target="$TARGET" -std=c++26 -stdlib=libc++ -O3 -march="$MARCH" -mtune="$MTUNE" -Wno-reserved-identifier -Wno-reserved-module-identifier --precompile "$STD_CPPM" -o ./build/std.pcm
# Asset partition: bootstrap compiles it WITHOUT CRAFTER_BUILD_HAS_ASSET so
# CompressAsset takes the stub branch and the impl unit doesn't try to
# `import Crafter.Asset` (no Crafter.Asset PCM exists at this stage — the
# self-host pass that follows wires in the real dep).
OBJECTS=""
for p in $PARTITIONS; do
"$CXX" $common_options -fmodule-output "interfaces/Crafter.Build-$p.cppm" -o "./build/Crafter.Build-$p.o"
OBJECTS="$OBJECTS ./build/Crafter.Build-$p.o"
done
"$CXX" $common_options -fmodule-output interfaces/Crafter.Build.cppm -o ./build/Crafter.Build.o
OBJECTS="$OBJECTS ./build/Crafter.Build.o"
for p in $PARTITIONS; do
"$CXX" $common_options "./implementations/Crafter.Build-$p.cpp" -o "./build/Crafter.Build-${p}_impl.o"
OBJECTS="$OBJECTS ./build/Crafter.Build-${p}_impl.o"
done
"$CXX" $common_options ./implementations/main.cpp -o ./build/main.o
# A fresh archive each time: `ar rcs` into an existing one keeps members a
# previous layout left behind.
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 \
-L./build \
$OBJECTS \
./build/main.o \
-lSPIRV -lGenericCodeGen -lglslang -lOSDependent -lMachineIndependent -lglslang-default-resource-limits \
-ldl \
-o ./bin/crafter-build