Template
build: add musl static target (clang/gcc)
This commit is contained in:
+8
-1
@@ -5,4 +5,11 @@
|
||||
SUBDIRS = src
|
||||
|
||||
TESTS = tests/run.sh
|
||||
EXTRA_DIST = tests/run.sh
|
||||
EXTRA_DIST = tests/run.sh scripts/build-static.sh
|
||||
|
||||
# Fully-static musl build (explicit opt-in; the default build stays dynamic).
|
||||
# Requires a musl toolchain on the host - see scripts/build-static.sh for
|
||||
# the probe order (musl-gcc -> musl-clang -> clang+sysroot -> apt -> musl.cc).
|
||||
.PHONY: static
|
||||
static:
|
||||
$(SHELL) "$(srcdir)/scripts/build-static.sh"
|
||||
|
||||
Executable
+301
@@ -0,0 +1,301 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# scripts/build-static.sh - build the stupidtools binary as a FULLY STATIC
|
||||
# musl-linked executable and verify it with file(1) + ldd(1).
|
||||
#
|
||||
# Toolchain probe order (first probe that can actually PRODUCE a static
|
||||
# binary wins - `command -v` alone is not enough, the wrapper must work):
|
||||
#
|
||||
# 1. musl-gcc -> CC=musl-gcc, LDFLAGS=-static (musl-tools package)
|
||||
# 2. musl-clang -> CC=musl-clang, LDFLAGS=-static
|
||||
# 3. clang --target=x86_64-linux-musl --sysroot="$MUSL_SYSROOT" -static
|
||||
# -fuse-ld=lld -> needs a musl sysroot; point the MUSL_SYSROOT env var
|
||||
# at it (e.g. /opt/x86_64-linux-musl from a musl.cc
|
||||
# cross toolchain, or /usr/lib/musl from musl-tools)
|
||||
# 4. apt-get install -y musl-tools (only if root or passwordless sudo)
|
||||
# 5. Download the musl.cc cross toolchain
|
||||
# https://musl.cc/x86_64-linux-musl-cross.tgz
|
||||
# (fallback mirror: https://more.musl.cc/x86_64-linux-musl-cross.tgz)
|
||||
# into /tmp - NEVER into the repo - and use its
|
||||
# bin/x86_64-linux-musl-gcc with -static.
|
||||
#
|
||||
# glibc -static is deliberately NOT a target: NSS and dlopen are broken in
|
||||
# fully-static glibc builds. musl is the only accepted static libc.
|
||||
# The DEFAULT build stays dynamic; static is an explicit opt-in via
|
||||
# `make static` (or running this script directly).
|
||||
#
|
||||
# CROSS-COMPILE VARIANT (aarch64) - documented, not wired as a flag:
|
||||
#
|
||||
# Download https://musl.cc/aarch64-linux-musl-cross.tgz into /tmp, then:
|
||||
# tar -C /tmp -xzf aarch64-linux-musl-cross.tgz
|
||||
# cd <project-root> && make clean
|
||||
# make CC=/tmp/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc \
|
||||
# CFLAGS=-O2 LDFLAGS=-static
|
||||
# verify the result with:
|
||||
# file src/stupidtools # expect: "... statically linked ... ARM aarch64"
|
||||
# (for a cross build, ldd must run under an emulator: use
|
||||
# `qemu-aarch64 /path/to/ldd src/stupidtools` or file(1) alone as proof.)
|
||||
#
|
||||
# Env knobs (optional):
|
||||
# MUSL_SYSROOT - musl sysroot for the clang --sysroot route (route 3)
|
||||
# STUPIDTOOLS_TOPDIR - project root override (default: this script's ../)
|
||||
#
|
||||
# Exit codes: 0 = static binary built AND verified; 1 = failure.
|
||||
|
||||
msg() { printf '%s\n' "$*" >&2; }
|
||||
|
||||
thisdir=$(CDPATH= cd "$(dirname "$0")" && pwd) || exit 1
|
||||
topdir=${STUPIDTOOLS_TOPDIR:-"$thisdir/.."}
|
||||
topdir=$(CDPATH= cd "$topdir" && pwd) || exit 1
|
||||
|
||||
out=src/stupidtools # relative to topdir (plan acceptance target)
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/stupidtools-static.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmpdir"' EXIT HUP INT TERM
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Verification gate - the ONLY definition of success. file(1) must report
|
||||
# "statically linked" and ldd(1) must report "not a dynamic executable".
|
||||
# Protects against stale dynamic binaries being mistaken for static ones.
|
||||
# ---------------------------------------------------------------------------
|
||||
verify_static() {
|
||||
bin=$1
|
||||
command -v file >/dev/null 2>&1 || { msg "ERROR: file(1) not available"; return 1; }
|
||||
command -v ldd >/dev/null 2>&1 || { msg "ERROR: ldd(1) not available"; return 1; }
|
||||
f=$(file "$bin") || { msg "ERROR: file(1) failed on $bin"; return 1; }
|
||||
case "$f" in
|
||||
*"statically linked"*) ;;
|
||||
*)
|
||||
msg "FAIL: $bin is NOT statically linked:"
|
||||
msg " $f"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
if l=$(ldd "$bin" 2>&1); then
|
||||
# ldd exiting 0 means it resolved dynamic dependencies
|
||||
msg "FAIL: $bin is dynamically linked (ldd resolved dependencies):"
|
||||
msg "$l"
|
||||
return 1
|
||||
fi
|
||||
case "$l" in
|
||||
*"not a dynamic executable"*) ;;
|
||||
*)
|
||||
msg "FAIL: ldd(1) output for $bin was unexpected:"
|
||||
msg "$l"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# probe_cc <cc> [flags...] - can this compiler produce a static binary?
|
||||
# ---------------------------------------------------------------------------
|
||||
probe_cc() {
|
||||
cc=$1
|
||||
shift
|
||||
command -v file >/dev/null 2>&1 || return 1
|
||||
printf 'int main(void){return 0;}\n' > "$tmpdir/probe.c" || return 1
|
||||
if "$cc" "$@" "$tmpdir/probe.c" -o "$tmpdir/probe" 2>"$tmpdir/probe.log"; then
|
||||
if file "$tmpdir/probe" 2>/dev/null | grep -q 'statically linked'; then
|
||||
return 0
|
||||
fi
|
||||
msg "note: $cc linked a probe, but the result is not static; skipping"
|
||||
return 1
|
||||
fi
|
||||
msg "note: $cc probe failed:"
|
||||
sed 's/^/ /' "$tmpdir/probe.log" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_via_make <cc> - preferred path: reuse the automake tree (no source
|
||||
# list duplication). Requires ./configure to have been run (Makefile exists).
|
||||
# ---------------------------------------------------------------------------
|
||||
build_via_make() {
|
||||
cc=$1
|
||||
[ -f "$topdir/Makefile" ] || return 1
|
||||
(
|
||||
cd "$topdir" || exit 1
|
||||
unset MAKEFLAGS MFLAGS
|
||||
make clean >"$tmpdir/make-clean.log" 2>&1 || {
|
||||
msg "ERROR: make clean failed:"
|
||||
sed 's/^/ /' "$tmpdir/make-clean.log" >&2
|
||||
exit 1
|
||||
}
|
||||
make CC="$cc" CFLAGS="-O2 $CC_CFLAGS" LDFLAGS="$CC_LDFLAGS" \
|
||||
>"$tmpdir/make.log" 2>&1 || {
|
||||
msg "ERROR: make CC=$cc failed:"
|
||||
sed 's/^/ /' "$tmpdir/make.log" >&2
|
||||
exit 1
|
||||
}
|
||||
) || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# build_direct <cc> - fallback when the tree is not configured: compile the
|
||||
# source list straight out of src/Makefile.am (single source of truth).
|
||||
# ---------------------------------------------------------------------------
|
||||
build_direct() {
|
||||
cc=$1
|
||||
srcs=$(sed -n 's/^stupidtools_SOURCES *= *//p' "$topdir/src/Makefile.am") || {
|
||||
msg "ERROR: cannot read the source list from src/Makefile.am"
|
||||
return 1
|
||||
}
|
||||
[ -n "$srcs" ] || {
|
||||
msg "ERROR: empty stupidtools_SOURCES in src/Makefile.am"
|
||||
return 1
|
||||
}
|
||||
set --
|
||||
for s in $srcs; do
|
||||
set -- "$@" "$topdir/src/$s"
|
||||
done
|
||||
(
|
||||
cd "$topdir" || exit 1
|
||||
# shellcheck disable=SC2086 # CC_CFLAGS/CC_LDFLAGS are flag words
|
||||
"$cc" $CC_CFLAGS -O2 -std=c23 -Wall -Wextra -Wpedantic \
|
||||
-I"$topdir/src" -I"$topdir/include" \
|
||||
$CC_LDFLAGS "$@" -o "$out" 2>"$tmpdir/direct.log" || {
|
||||
msg "ERROR: direct compile with $cc failed:"
|
||||
sed 's/^/ /' "$tmpdir/direct.log" >&2
|
||||
exit 1
|
||||
}
|
||||
) || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# try_apt_install - install musl-tools (route 4), non-interactively only.
|
||||
# ---------------------------------------------------------------------------
|
||||
try_apt_install() {
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
msg "note: apt-get not present (non-Debian host); skipping apt route"
|
||||
return 1
|
||||
fi
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
apt-get install -y musl-tools
|
||||
return $?
|
||||
fi
|
||||
if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
|
||||
sudo apt-get install -y musl-tools
|
||||
return $?
|
||||
fi
|
||||
msg "note: no root and no passwordless sudo; cannot apt-get musl-tools"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# try_muslcc_download - musl.cc cross toolchain into $tmpdir (route 5).
|
||||
# ---------------------------------------------------------------------------
|
||||
try_muslcc_download() {
|
||||
tarball=x86_64-linux-musl-cross.tgz
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
msg "downloading $tarball from musl.cc (curl; ~100 MB, allow time)..."
|
||||
if ! curl -fL --connect-timeout 15 --max-time 900 \
|
||||
-o "$tmpdir/$tarball" "https://musl.cc/$tarball" 2>"$tmpdir/dl.log"; then
|
||||
msg "note: primary mirror failed, trying more.musl.cc..."
|
||||
curl -fL --connect-timeout 15 --max-time 900 \
|
||||
-o "$tmpdir/$tarball" "https://more.musl.cc/$tarball" 2>"$tmpdir/dl.log" || {
|
||||
msg "ERROR: musl.cc download failed:"
|
||||
sed 's/^/ /' "$tmpdir/dl.log" >&2
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
msg "downloading $tarball from musl.cc (wget; ~100 MB, allow time)..."
|
||||
if ! wget -q --timeout=15 -O "$tmpdir/$tarball" \
|
||||
"https://musl.cc/$tarball" 2>"$tmpdir/dl.log"; then
|
||||
msg "note: primary mirror failed, trying more.musl.cc..."
|
||||
wget -q --timeout=15 -O "$tmpdir/$tarball" \
|
||||
"https://more.musl.cc/$tarball" 2>"$tmpdir/dl.log" || {
|
||||
msg "ERROR: musl.cc download failed:"
|
||||
sed 's/^/ /' "$tmpdir/dl.log" >&2
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
else
|
||||
msg "ERROR: neither curl nor wget available for the musl.cc route"
|
||||
return 1
|
||||
fi
|
||||
size=$(wc -c < "$tmpdir/$tarball")
|
||||
msg "downloaded $size bytes; extracting..."
|
||||
tar -C "$tmpdir" -xzf "$tmpdir/$tarball" || {
|
||||
msg "ERROR: tar extraction of $tarball failed"
|
||||
return 1
|
||||
}
|
||||
dlcc=$tmpdir/x86_64-linux-musl-cross/bin/x86_64-linux-musl-gcc
|
||||
[ -x "$dlcc" ] || { msg "ERROR: $dlcc missing after extraction"; return 1; }
|
||||
if probe_cc "$dlcc" -static -O2; then
|
||||
CC_STATIC=$dlcc
|
||||
return 0
|
||||
fi
|
||||
msg "ERROR: downloaded gcc could not produce a static binary"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Route selection
|
||||
# ---------------------------------------------------------------------------
|
||||
CC_STATIC=
|
||||
CC_CFLAGS=
|
||||
CC_LDFLAGS=-static
|
||||
|
||||
if command -v musl-gcc >/dev/null 2>&1 && probe_cc musl-gcc -static -O2; then
|
||||
CC_STATIC=musl-gcc
|
||||
elif command -v musl-clang >/dev/null 2>&1 && probe_cc musl-clang -static -O2; then
|
||||
CC_STATIC=musl-clang
|
||||
elif [ -n "${MUSL_SYSROOT:-}" ] && command -v clang >/dev/null 2>&1 \
|
||||
&& probe_cc clang --target=x86_64-linux-musl \
|
||||
--sysroot="$MUSL_SYSROOT" -static -fuse-ld=lld; then
|
||||
CC_STATIC=clang
|
||||
CC_CFLAGS="--target=x86_64-linux-musl --sysroot=$MUSL_SYSROOT"
|
||||
CC_LDFLAGS="$CC_CFLAGS -fuse-ld=lld -static"
|
||||
fi
|
||||
|
||||
if [ -z "$CC_STATIC" ]; then
|
||||
msg "no local musl toolchain found; trying apt-get (route 4)..."
|
||||
if try_apt_install; then
|
||||
if command -v musl-gcc >/dev/null 2>&1 && probe_cc musl-gcc -static -O2; then
|
||||
CC_STATIC=musl-gcc
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$CC_STATIC" ]; then
|
||||
msg "trying musl.cc cross toolchain (route 5)..."
|
||||
try_muslcc_download
|
||||
fi
|
||||
|
||||
if [ -z "$CC_STATIC" ]; then
|
||||
msg "ERROR: no usable musl toolchain could be obtained."
|
||||
msg " Options: install musl-tools (Debian/Ubuntu: apt-get install"
|
||||
msg " musl-tools; Arch: pacman -S musl), download a musl.cc cross"
|
||||
msg " toolchain, or set MUSL_SYSROOT and use clang (route 3)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg "building static binary with $CC_STATIC ..."
|
||||
if ! build_via_make "$CC_STATIC"; then
|
||||
msg "note: automake route unavailable or failed; trying direct compile"
|
||||
build_direct "$CC_STATIC" || {
|
||||
msg "restoring default dynamic build (best effort)..."
|
||||
if [ -f "$topdir/Makefile" ]; then
|
||||
(cd "$topdir" && unset MAKEFLAGS MFLAGS && make >/dev/null 2>&1) || true
|
||||
fi
|
||||
msg "ERROR: static build failed"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
if ! verify_static "$topdir/$out"; then
|
||||
msg "restoring default dynamic build (best effort)..."
|
||||
if [ -f "$topdir/Makefile" ]; then
|
||||
(cd "$topdir" && unset MAKEFLAGS MFLAGS && make >/dev/null 2>&1) || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg "OK: $out is a fully static musl-linked binary."
|
||||
file "$topdir/$out" 2>/dev/null || true
|
||||
ldd "$topdir/$out" 2>&1 || true
|
||||
exit 0
|
||||
Reference in New Issue
Block a user