Template
build: add musl static target (clang/gcc)
This commit is contained in:
+8
-1
@@ -5,4 +5,11 @@
|
|||||||
SUBDIRS = src
|
SUBDIRS = src
|
||||||
|
|
||||||
TESTS = tests/run.sh
|
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