#!/bin/sh
set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
BUILD_ROOT=${BUILD_ROOT:-"$ROOT/.static-build"}
DIST_DIR=${DIST_DIR:-"$ROOT/dist"}
JOBS=${JOBS:-$(getconf _NPROCESSORS_ONLN 2>/dev/null || printf '1')}
TOOLCHAIN_RELEASE=20260823
SDL_VERSION=3.4.14
SDL_IMAGE_VERSION=3.4.4
SDL_IMAGE_COMMIT=bec9134a26c7d0f31b36d6083c25296e04cabff5
NASM_VERSION=3.02
SDL_ARCHIVE="SDL3-$SDL_VERSION.tar.gz"
SDL_SHA256=30d4aa2b3037718142b32dffd4e72f917ebb6cc5227150e7bb9c45efb2153aeb
SDL_URL="https://github.com/libsdl-org/SDL/releases/download/release-$SDL_VERSION/$SDL_ARCHIVE"
NASM_ARCHIVE="nasm-$NASM_VERSION.tar.xz"
NASM_SHA256=87336eba53b4acfe917424ab5d500d2b0054d9f5148d35c2273ccf2cfb712f0d
NASM_URL="https://www.nasm.us/pub/nasm/releasebuilds/$NASM_VERSION/$NASM_ARCHIVE"
log() {
printf '%s\n' "compile_static: $*" >&2
}
die() {
log "error: $*"
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}
for command_name in cmake curl file find git make ninja paste pkg-config readelf sha256sum tar; do
require_command "$command_name"
done
mkdir -p "$BUILD_ROOT/downloads" "$BUILD_ROOT/sources" \
"$BUILD_ROOT/toolchains" "$BUILD_ROOT/targets" "$DIST_DIR"
download_checked() {
url=$1
destination=$2
expected=$3
temporary="$destination.part"
if [ -f "$destination" ] &&
printf '%s %s\n' "$expected" "$destination" | sha256sum -c - >/dev/null 2>&1; then
return
fi
log "downloading $(basename "$destination")"
curl -fL --retry 3 --retry-delay 2 "$url" -o "$temporary"
printf '%s %s\n' "$expected" "$temporary" | sha256sum -c - >/dev/null ||
die "checksum mismatch for $(basename "$destination")"
mv -f "$temporary" "$destination"
}
remove_cache_directory() {
target=$1
case "$target" in
"$BUILD_ROOT"/*) rm -rf -- "$target" ;;
*) die "refusing to remove path outside build cache: $target" ;;
esac
}
extract_checked() {
archive=$1
destination=$2
expected=$3
stamp="$destination/.archive-sha256"
temporary="$destination.tmp.$$"
if [ -f "$stamp" ] && [ "$(sed -n '1p' "$stamp")" = "$expected" ]; then
return
fi
remove_cache_directory "$temporary"
mkdir -p "$temporary"
tar -xf "$archive" -C "$temporary" --strip-components=1
printf '%s\n' "$expected" >"$temporary/.archive-sha256"
remove_cache_directory "$destination"
mv "$temporary" "$destination"
}
download_checked "$SDL_URL" "$BUILD_ROOT/downloads/$SDL_ARCHIVE" "$SDL_SHA256"
extract_checked "$BUILD_ROOT/downloads/$SDL_ARCHIVE" \
"$BUILD_ROOT/sources/SDL-$SDL_VERSION" "$SDL_SHA256"
download_checked "$NASM_URL" "$BUILD_ROOT/downloads/$NASM_ARCHIVE" "$NASM_SHA256"
extract_checked "$BUILD_ROOT/downloads/$NASM_ARCHIVE" \
"$BUILD_ROOT/sources/nasm-$NASM_VERSION" "$NASM_SHA256"
NASM_PREFIX="$BUILD_ROOT/host-tools/nasm-$NASM_VERSION"
if [ ! -x "$NASM_PREFIX/bin/nasm" ]; then
NASM_BUILD="$BUILD_ROOT/host-tools/build-nasm-$NASM_VERSION"
remove_cache_directory "$NASM_BUILD"
mkdir -p "$NASM_BUILD" "$NASM_PREFIX"
log "building local NASM $NASM_VERSION"
(
cd "$NASM_BUILD"
"$BUILD_ROOT/sources/nasm-$NASM_VERSION/configure" \
--prefix="$NASM_PREFIX"
)
make -C "$NASM_BUILD" -j "$JOBS"
make -C "$NASM_BUILD" install
fi
PATH="$NASM_PREFIX/bin:$PATH"
export PATH
SDL_IMAGE_SOURCE="$BUILD_ROOT/sources/SDL_image-$SDL_IMAGE_VERSION"
if [ ! -d "$SDL_IMAGE_SOURCE/.git" ]; then
remove_cache_directory "$SDL_IMAGE_SOURCE"
log "cloning SDL_image $SDL_IMAGE_VERSION and its pinned codec submodules"
git clone --recursive --branch "release-$SDL_IMAGE_VERSION" \
https://github.com/libsdl-org/SDL_image.git "$SDL_IMAGE_SOURCE"
fi
actual_image_commit=$(git -C "$SDL_IMAGE_SOURCE" rev-parse HEAD)
[ "$actual_image_commit" = "$SDL_IMAGE_COMMIT" ] ||
die "cached SDL_image checkout is not the pinned commit; remove $SDL_IMAGE_SOURCE"
git -C "$SDL_IMAGE_SOURCE" submodule update --init --recursive
build_target() {
triple=$1
output_arch=$2
processor=$3
archive_sha=$4
expected_machine=$5
archive="$triple.tar.xz"
archive_url="https://github.com/cross-tools/musl-cross/releases/download/$TOOLCHAIN_RELEASE/$archive"
archive_path="$BUILD_ROOT/downloads/$archive"
toolchain_root="$BUILD_ROOT/toolchains/$triple-$TOOLCHAIN_RELEASE"
target_root="$BUILD_ROOT/targets/$triple"
prefix="$target_root/prefix"
toolchain_file="$target_root/toolchain.cmake"
sdl_build="$target_root/build-sdl"
image_build="$target_root/build-sdl-image"
download_checked "$archive_url" "$archive_path" "$archive_sha"
extract_checked "$archive_path" "$toolchain_root" "$archive_sha"
cc=$(find "$toolchain_root" -path "*/bin/$triple-gcc" -print -quit)
cxx=$(find "$toolchain_root" -path "*/bin/$triple-g++" -print -quit)
[ -n "$cc" ] && [ -x "$cc" ] || die "compiler missing from $archive"
[ -n "$cxx" ] && [ -x "$cxx" ] || die "C++ compiler missing from $archive"
tool_bin=$(dirname "$cc")
mkdir -p "$target_root"
cat >"$toolchain_file" <<EOF
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR $processor)
set(CMAKE_C_COMPILER "$cc")
set(CMAKE_CXX_COMPILER "$cxx")
set(CMAKE_AR "$tool_bin/$triple-ar")
set(CMAKE_RANLIB "$tool_bin/$triple-ranlib")
set(CMAKE_STRIP "$tool_bin/$triple-strip")
set(CMAKE_FIND_ROOT_PATH "$prefix")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
EOF
log "building static SDL $SDL_VERSION for $output_arch"
PKG_CONFIG_PATH= PKG_CONFIG_LIBDIR="$prefix/lib/pkgconfig" \
cmake -S "$BUILD_ROOT/sources/SDL-$SDL_VERSION" -B "$sdl_build" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$prefix" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DSDL_SHARED=OFF \
-DSDL_STATIC=ON \
-DSDL_INSTALL=ON \
-DSDL_UNIX_CONSOLE_BUILD=ON \
-DSDL_AUDIO=OFF \
-DSDL_VIDEO=OFF \
-DSDL_GPU=OFF \
-DSDL_RENDER=OFF \
-DSDL_CAMERA=OFF \
-DSDL_JOYSTICK=OFF \
-DSDL_HAPTIC=OFF \
-DSDL_HIDAPI=OFF \
-DSDL_POWER=OFF \
-DSDL_SENSOR=OFF \
-DSDL_DIALOG=OFF \
-DSDL_TRAY=OFF \
-DSDL_TEST_LIBRARY=OFF \
-DSDL_TESTS=OFF \
-DSDL_EXAMPLES=OFF
cmake --build "$sdl_build" --parallel "$JOBS"
cmake --install "$sdl_build"
log "building static SDL_image $SDL_IMAGE_VERSION for $output_arch"
log "SDL_image source=$SDL_IMAGE_SOURCE build=$image_build prefix=$prefix"
PKG_CONFIG_PATH= PKG_CONFIG_LIBDIR="$prefix/lib/pkgconfig" \
cmake -S "$SDL_IMAGE_SOURCE" -B "$image_build" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$prefix" \
-DCMAKE_INSTALL_PREFIX="$prefix" \
-DCMAKE_INSTALL_LIBDIR=lib \
-DBUILD_SHARED_LIBS=OFF \
-DSDLIMAGE_INSTALL=ON \
-DSDLIMAGE_VENDORED=ON \
-DSDLIMAGE_DEPS_SHARED=OFF \
-DSDLIMAGE_STRICT=ON \
-DSDLIMAGE_SAMPLES=OFF \
-DSDLIMAGE_TESTS=OFF \
-DDAV1D_ASM=OFF \
-DSDL_UNIX_CONSOLE_BUILD=ON \
-DSDL_AUDIO=OFF \
-DSDL_VIDEO=OFF \
-DSDL_GPU=OFF \
-DSDL_RENDER=OFF \
-DSDL_CAMERA=OFF \
-DSDL_JOYSTICK=OFF \
-DSDL_HAPTIC=OFF \
-DSDL_HIDAPI=OFF \
-DSDL_POWER=OFF \
-DSDL_SENSOR=OFF \
-DSDL_DIALOG=OFF \
-DSDL_TRAY=OFF
cmake --build "$image_build" --parallel "$JOBS"
cmake --install "$image_build"
pc_dirs=$(find "$prefix" -type d -name pkgconfig -print | paste -sd: -)
[ -n "$pc_dirs" ] || die "no target pkg-config metadata installed for $output_arch"
sdl_cflags=$(PKG_CONFIG_PATH= PKG_CONFIG_LIBDIR="$pc_dirs" \
pkg-config --static --cflags sdl3 sdl3-image)
sdl_platform_libs=$(PKG_CONFIG_PATH= PKG_CONFIG_LIBDIR="$pc_dirs" \
pkg-config --static --libs-only-other sdl3)
# SDL_image 3.4.4's generated .pc file leaves CMake generator expressions
# in Libs.private for some vendored targets. Link the known, installed
# static codec archives explicitly and group them to resolve circular
# references deterministically.
image_libs="-L$prefix/lib -Wl,--start-group -lSDL3_image -lavif -ldav1d -laom -lpng16 -ltiff -lwebpdemux -lwebpmux -lwebp -lsharpyuv -lz -lSDL3 -Wl,--end-group"
output="$DIST_DIR/accent-extract-linux-$output_arch"
test_output="$target_root/accent-extract-tests"
log "linking $output"
# Intentional field splitting: pkg-config returns compiler argument lists.
# shellcheck disable=SC2086
"$cc" -O2 -Wall -Wextra -Werror -std=c11 -ffunction-sections \
-fdata-sections -I"$ROOT/src" -I"$ROOT/vendor" $sdl_cflags \
"$ROOT/src/main.c" "$ROOT/src/image.c" "$ROOT/src/colour.c" \
"$ROOT/src/candidates.c" "$ROOT/src/arena.c" \
-static -Wl,--gc-sections -o "$output" $image_libs \
$sdl_platform_libs -lm
# shellcheck disable=SC2086
"$cc" -O2 -Wall -Wextra -Werror -std=c11 -I"$ROOT/src" \
-I"$ROOT/vendor" $sdl_cflags "$ROOT/tests/test.c" \
"$ROOT/src/image.c" "$ROOT/src/colour.c" \
"$ROOT/src/candidates.c" "$ROOT/src/arena.c" \
-static -Wl,--gc-sections -o "$test_output" $image_libs \
$sdl_platform_libs -lm
"$tool_bin/$triple-strip" "$output"
file "$output" | grep -F "$expected_machine" >/dev/null ||
die "$output has the wrong architecture"
file "$output" | grep -F 'statically linked' >/dev/null ||
die "$output is not statically linked"
if readelf -l "$output" | grep -q 'INTERP'; then
die "$output contains a dynamic interpreter"
fi
if readelf -d "$output" 2>/dev/null | grep -q '(NEEDED)'; then
die "$output contains dynamic dependencies"
fi
case "$output_arch" in
amd64)
log "running native amd64 tests"
"$test_output"
;;
arm64)
if command -v qemu-aarch64 >/dev/null 2>&1; then
log "running arm64 tests with qemu-aarch64"
qemu-aarch64 "$test_output"
else
log "qemu-aarch64 not found; arm64 binary was link-verified only"
fi
;;
esac
}
build_target x86_64-unknown-linux-musl amd64 x86_64 \
9752ecb10bafc0fc2ea75b3ed864a78137f3e5ba9b1579f1f16923d444c48096 \
'x86-64'
build_target aarch64-unknown-linux-musl arm64 aarch64 \
0fc483607d9ed83bdf75e7539bacc66721d7e37ca606377aed6a90cef82e45da \
'ARM aarch64'
log "finished"
file "$DIST_DIR/accent-extract-linux-amd64" \
"$DIST_DIR/accent-extract-linux-arm64"