#!/bin/zsh
#
# build-tools.sh — Build base build tools for Hammerhead
#
# Base system tools built as part of hh-build.
# These are essential for rebuilding Hammerhead and for coral package builds.
#
# NOT included here (moved to bootstrap-hh.sh or coral packages):
#   - curl, git, python, mercurial (coral packages — frequently updated)
#   - autoconf, automake, libtool, pkgconf (bootstrap-hh.sh — ports tools)
#   - OCaml, reefc, coral (bootstrap-hh.sh — dev toolchain)
#
# Called by hh-build as a pre-build toolchain step.
#
# Usage: build-tools.sh [--jobs N]
#   --jobs N    Parallel make jobs (default: 4)
#
# Environment (set by hh-build before calling):
#   SRC         — path to usr/src
#   CONTRIB     — path to usr/src/contrib ($SRC/contrib)
#   GNUC_ROOT   — GCC root (/usr or staged toolchain path)
#
# Source tarballs must be pre-extracted into:
#   $CONTRIB/m4/           — GNU m4 1.4.19
#   $CONTRIB/make/         — GNU Make 4.4.1
#   $CONTRIB/perl/         — perl 5.40.1
#   $CONTRIB/cmake/        — cmake 3.31.4
#   $CONTRIB/libffi/       — libffi 3.4.7
#   $CONTRIB/expat/        — expat 2.7.1
#   $CONTRIB/XML-Parser/   — Perl XML::Parser 2.47 (XS module on expat)
#   $CONTRIB/bzip2/        — bzip2 1.0.8
#   $CONTRIB/xz/           — xz 5.8.2
#   $CONTRIB/gzip/         — gzip 1.14
#   $CONTRIB/tar/          — GNU tar 1.35
#   $CONTRIB/bash/         — bash 5.2
#   $CONTRIB/zstd/         — zstd 1.5.7
#
# Installs to staging root: $SRC/tools/.tools-stage/usr
# hh-build adds the staging root to PATH for subsequent build steps.
#

set -euo pipefail
setopt NULL_GLOB

# Verify /dev/null is functional. If corrupted (regular file instead of
# device), restore the devfs symlink atomically. Do NOT use mknod — the
# major/minor numbers differ between OI and Hammerhead kernels.
#
# Atomic restore via rename(): build a temporary symlink at /dev/null.fix
# and `mv -f` it onto /dev/null. This survives the race where parallel
# build processes do `cmd >/dev/null` between rm + ln, recreating
# /dev/null as a regular file (which makes naive ln -sf fail with
# "File exists" because something snuck in between unlink and symlink).
if [[ ! -c /dev/null ]]; then
    echo "WARNING: /dev/null corrupted — restoring devfs symlink" >&2
    ln -sf ../devices/pseudo/mm@0:null "/dev/null.fix.$$" \
        && mv -f "/dev/null.fix.$$" /dev/null \
        || { echo "ERROR: /dev/null restore failed" >&2; rm -f "/dev/null.fix.$$"; }
fi

JOBS=4

while [[ $# -gt 0 ]]; do
    case "$1" in
        --jobs)  JOBS="$2"; shift 2 ;;
        -j*)     JOBS="${1#-j}"; shift ;;
        *)       echo "Unknown argument: $1" >&2; exit 1 ;;
    esac
done

# ── Paths ────────────────────────────────────────────────────────────────

: "${SRC:?SRC must be set}"
: "${CONTRIB:=$SRC/contrib}"
: "${GNUC_ROOT:=/usr}"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
PATCHES="$REPO_ROOT/tools/bootstrap/patches"

STAGE="$SRC/tools/.tools-stage"

# ── Logging ──────────────────────────────────────────────────────────────

log()  { echo "==> [build-tools] $*"; }
log2() { echo "  -> $*"; }
die()  { echo "FATAL: $*" >&2; exit 1; }

# ── No stamp caching ─────────────────────────────────────────────────────
# Stamp-based caching removed: it masked bugs (missing cmake platform
# files, incomplete staging) by silently skipping rebuilds.
# Full rebuilds every time are slower but reliable.
log "Full base tools rebuild (no stamp caching)"

# ── Environment ──────────────────────────────────────────────────────────

export CC="$GNUC_ROOT/bin/gcc -m64"
export CXX="$GNUC_ROOT/bin/g++ -m64"
export CFLAGS="-m64 -O2"
export CXXFLAGS="-m64 -O2"
export LDFLAGS="-m64 -L${STAGE}/usr/lib"
export CPPFLAGS="-I${STAGE}/usr/include"
export PATH="${STAGE}/usr/bin:${GNUC_ROOT}/bin:$PATH"

export PKG_CONFIG_PATH="${STAGE}/usr/lib/pkgconfig"
export LD_LIBRARY_PATH="${STAGE}/usr/lib:${STAGE}/usr/local/lib"
export LIBRARY_PATH="${STAGE}/usr/lib:${STAGE}/usr/local/lib"

# Hammerhead's uname -s returns "Hammerhead" which config.guess doesn't
# recognize. Pass explicit build/host triplet to all configure scripts.
BUILD_HOST="--build=x86_64-pc-solaris2.11 --host=x86_64-pc-solaris2.11"
# zsh doesn't word-split unquoted variables by default; use array
BUILD_TRIPLET=(--build=x86_64-pc-solaris2.11 --host=x86_64-pc-solaris2.11)

# makeinfo/autoconf/automake/aclocal not available on Hammerhead — export
# so all recursive make invocations inherit them
export MAKEINFO=true
export AUTOCONF=true
export ACLOCAL=true
export AUTOMAKE=true
export AUTOHEADER=true

# Some package Makefiles ignore $(AUTOCONF) and call the literal `autoconf`
# command (e.g. bash 5.2's Makefile rule "$(srcdir)/configure: configure.ac;
# cd $(srcdir) && autoconf"). When autoconf runs it shells out to autom4te,
# whose hardcoded M4 default is baked from the install prefix at
# autoconf-build time. The system /usr/bin/autom4te on hh-mvp was installed
# by a prior bootstrap.sh run with M4 set to a path that no longer exists
# (/usr/src/hammerhead/tools/bootstrap/root/usr/bin/m4). autom4te checks
# $M4 first, so override it to a working binary. /usr/bin/m4 is GNU 1.4.19
# on Hammerhead and always present.
export M4=/usr/bin/m4

mkdir -p "$STAGE"

# ── Clean stale in-tree build state ─────────────────────────────────────
# hh-build's make clobber partially cleans contrib/ sources, leaving
# inconsistent state (stale config.status, obj dirs, Makefile fragments).
# Run distclean/clean on all components to ensure fresh builds.
log2 "Cleaning stale build state from contrib/"
for d in "$CONTRIB"/m4 "$CONTRIB"/make "$CONTRIB"/gzip "$CONTRIB"/tar \
         "$CONTRIB"/bzip2 "$CONTRIB"/xz "$CONTRIB"/expat "$CONTRIB"/libffi; do
    [[ -d "$d" ]] && (cd "$d" && gmake distclean 2>/dev/null || true)
done
# cmake: in-tree bootstrap build
(cd "$CONTRIB/cmake" 2>/dev/null && gmake clean 2>/dev/null; rm -f CMakeCache.txt cmake_bootstrap.log; rm -rf Bootstrap.cmk) || true
# perl: heavy in-tree state
(cd "$CONTRIB/perl" 2>/dev/null && rm -f config.sh Policy.sh && gmake distclean 2>/dev/null) || true
# zstd: obj directories (gmake clean doesn't remove nested obj/ dirs)
(cd "$CONTRIB/zstd" 2>/dev/null && rm -rf lib/obj && gmake clean 2>/dev/null) || true

# ── Helper ───────────────────────────────────────────────────────────────

clean_la_files() {
    find "$STAGE" -name '*.la' -delete 2>/dev/null || true
}

# Verify /dev/null is still a character device. In-tree builds (especially
# Perl Configure and autotools distclean) can corrupt it by doing shell
# redirects when the devfs symlink is temporarily stale. Call after each
# component build to catch and fix corruption early.
check_dev_null() {
    if [[ ! -c /dev/null ]]; then
        log2 "WARNING: /dev/null corrupted — restoring devfs symlink"
        # Atomic restore via rename — see file-top comment for why.
        ln -sf ../devices/pseudo/mm@0:null "/dev/null.fix.$$" \
            && mv -f "/dev/null.fix.$$" /dev/null \
            || { log2 "WARNING: /dev/null restore failed"; rm -f "/dev/null.fix.$$"; }
    fi
}

# Touch autotools-generated files so Make doesn't try to regenerate
# them (aclocal/automake/autoconf not available on Hammerhead).
# Must be called before configure for each autotools-based component.
prevent_autoreconf() {
    local srcdir="$1"
    find "$srcdir" \( -name 'configure' -o -name 'Makefile.in' \
        -o -name 'aclocal.m4' -o -name 'config.h.in' \) \
        -exec touch {} + 2>/dev/null || true
}

# ── Build m4 ─────────────────────────────────────────────────────────────

build_m4() {
    local src="$CONTRIB/m4"
    [[ -d "$src" ]] || { log2 "m4 source not found, skipping"; return; }
    log "Building GNU m4 1.4.19"
    cd "$src"
    # Fix gnulib getlocalename_l const mismatch with illumos headers
    patch -p1 -N < "$PATCHES/m4-getlocalename.patch" 2>/dev/null || true
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET \
        --prefix=/usr \
        --disable-nls
    gmake -j"$JOBS" || true
    [[ -f src/m4 ]] || die "m4 binary not found after build"
    gmake -C src install DESTDIR="$STAGE"
    gmake -C lib install DESTDIR="$STAGE" 2>/dev/null || true
}

# ── Build make ───────────────────────────────────────────────────────────

build_make() {
    local src="$CONTRIB/make"
    [[ -d "$src" ]] || { log2 "make source not found, skipping"; return; }
    log "Building GNU Make 4.4.1"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET \
        --prefix=/usr \
        --disable-nls \
        --without-guile
    gmake -j"$JOBS" || true
    [[ -f make ]] || die "make binary not found after build"
    gmake install DESTDIR="$STAGE" || true

    # Install as gmake, create make symlink
    cd "$STAGE/usr/bin"
    if [[ -f make && ! -f gmake ]]; then
        mv make gmake
        ln -sf gmake make
    fi
}

# ── Build perl ───────────────────────────────────────────────────────────

build_perl() {
    # Re-enabled: /dev/null corruption was caused by running as root.
    # Building as unprivileged user (zygaena) prevents the issue.
    local src="$CONTRIB/perl"
    [[ -d "$src" ]] || { log2 "perl source not found, skipping"; return; }
    log "Building perl 5.40.1"
    cd "$src"

    # Perl uses Configure (capital C), not autotools.
    # Pipe 'y' to handle "THIS PACKAGE SEEMS TO BE INCOMPLETE" prompt
    # (in-tree distclean may have removed non-essential test files).
    # Hammerhead isn't recognized by Perl — force Solaris identity so
    # Configure picks up the right hints (shared lib flags, etc.)
    [[ ! -f hints/hammerhead.sh ]] && ln -sf solaris_2.sh hints/hammerhead.sh
    echo y | ./Configure -des \
        -Dosname=hammerhead \
        -Dprefix=/usr \
        -Dvendorprefix=/usr \
        -Dprivlib=/usr/lib/perl5/5.40 \
        -Darchlib=/usr/lib/perl5/5.40 \
        -Dvendorlib=/usr/lib/perl5/vendor_perl \
        -Dvendorarch=/usr/lib/perl5/vendor_perl \
        -Dusethreads \
        -Duseshrplib \
        -Dcc="${GNUC_ROOT}/bin/gcc" \
        -Dccflags="-m64 -D_REENTRANT -fPIC" \
        -Doptimize="-O2" \
        -Dldflags="-m64" \
        -Dlddlflags="-m64 -shared -G" \
        -Dloclibpth="$STAGE/usr/lib /usr/lib" \
        -Dlocincpth="$STAGE/usr/include /usr/include"
    gmake -j"$JOBS"
    gmake install DESTDIR="$STAGE"
}

# ── Build cmake ──────────────────────────────────────────────────────────

build_cmake() {
    # CMake bootstrap requires Platform/Hammerhead.cmake (symlinks to SunOS)
    # and --no-system-libs (bootstrap cmake can't find system libraries).
    local src="$CONTRIB/cmake"
    [[ -d "$src" ]] || { log2 "cmake source not found, skipping"; return; }
    log "Building CMake 3.31.4"
    cd "$src"
    # Hammerhead: cmake's ./bootstrap keys ALL its SunOS-specific logic off
    # `uname` (stored in cmake_system): socket libs for the bootstrap cmake's
    # own libuv link (-lkstat -lnsl -lsendfile -lsocket -lrt), the C99 check,
    # and platform detection. uname returns "Hammerhead", so none of the
    # `case "${cmake_system}" in *SunOS*)` blocks match and the bootstrap
    # binary fails to link libuv (undefined setsockopt/__xnet_socketpair/
    # getsockname). Normalize cmake_system Hammerhead->SunOS at its single
    # assignment site so every SunOS case applies (Hammerhead IS illumos-
    # derived). This must happen on the freshly-extracted source each build.
    if ! grep -q 'cmake_system=SunOS' bootstrap; then
        sed -i 's/^cmake_system=`uname`$/&\ncase "${cmake_system}" in *Hammerhead*) cmake_system=SunOS ;; esac/' bootstrap
    fi
    # Hammerhead: cmake has no Platform module for our sysname. The full cmake
    # configure ("initial CMake") aborts with "System is unknown to cmake,
    # create: Platform/Hammerhead", which also cascades into bogus
    # LIBMD_LIBRARY/CHECK_CRYPTO TRY_COMPILE failures (the generated test
    # project can't configure either). Hammerhead is illumos-derived == SunOS
    # to cmake, so generate Platform/Hammerhead*.cmake shims that include the
    # SunOS equivalents. Created in the source Modules/ so they also ship in the
    # installed cmake (needed when cmake later builds OpenSSL / coral packages).
    local pmod="Modules/Platform" sfx
    for sfx in "" "-Initialize" "-GNU" "-GNU-CXX" "-GNU-Fortran"; do
        if [[ -f "$pmod/SunOS$sfx.cmake" ]]; then
            echo "include(Platform/SunOS$sfx)" > "$pmod/Hammerhead$sfx.cmake"
        fi
    done
    # Hammerhead: cmake full BUILD compiles its OWN bundled libraries (kwsys,
    # libuv, libarchive) whose CMakeLists gate platform code on
    # `CMAKE_SYSTEM_NAME STREQUAL "SunOS"`. CMAKE_SYSTEM_NAME derives from
    # CMAKE_HOST_SYSTEM_NAME (= uname -s = "Hammerhead") in
    # CMakeDetermineSystem.cmake, so those gates miss and cmake s all fails:
    # kwsys can t link (undefined getifaddrs/getnameinfo) and libuv omits
    # sunos.c (undefined uv__hrtime/uv__io_poll/sendfile). Remap the host system
    # name Hammerhead->SunOS at detection time; the native branch below then sets
    # CMAKE_SYSTEM_NAME=SunOS with CMAKE_CROSSCOMPILING=FALSE (no cross-compile).
    # Patched in the source Modules/ so the installed cmake also treats
    # Hammerhead as SunOS when building OpenSSL / coral packages.
    local dsys="Modules/CMakeDetermineSystem.cmake"
    if [[ -f "$dsys" ]] && ! grep -q 'STREQUAL "Hammerhead"' "$dsys"; then
        sed -i 's/^if(CMAKE_HOST_UNIX)$/&\n  if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Hammerhead")\n    set(CMAKE_HOST_SYSTEM_NAME "SunOS")\n  endif()/' "$dsys"
    fi
    # illumos needs __EXTENSIONS__ + _XOPEN_SOURCE=600 in CFLAGS for all
    # cmake components (libuv, curl, etc.), and socket libs for linking.
    # These must be in env CFLAGS/LDFLAGS so both bootstrap AND full build get them.
    # __EXTENSIONS__ + _XOPEN_SOURCE=600 needed for all cmake components.
    # Socket libs (for the post-bootstrap full build) are in
    # Platform/Hammerhead.cmake via CMAKE_C_STANDARD_LIBRARIES.
    CFLAGS="${CFLAGS:-} -D__EXTENSIONS__ -D_XOPEN_SOURCE=600" \
    CXXFLAGS="${CXXFLAGS:-} -D__EXTENSIONS__ -D_XOPEN_SOURCE=600" \
    ./bootstrap \
        --prefix=/usr \
        --parallel="$JOBS" \
        --no-system-curl \
        --no-system-zlib \
        --no-system-libs \
        -- \
        -DCMAKE_INSTALL_LIBDIR=lib \
        -DHAVE_MEMRCHR=0
    # -j1: cmake has a parallel build race condition at ~93% on Hammerhead
    gmake -j1
    gmake install DESTDIR="$STAGE"
}

# ── Build libffi ─────────────────────────────────────────────────────────

build_libffi() {
    # Re-enabled: configure script replaced with working copy from OI bootstrap.
    local src="$CONTRIB/libffi"
    [[ -d "$src" ]] || { log2 "libffi source not found, skipping"; return; }
    log "Building libffi 3.4.7"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET \
        --prefix=/usr \
        --libdir=/usr/lib \
        --disable-static
    gmake -j"$JOBS"
    gmake install DESTDIR="$STAGE"
    clean_la_files
}

# ── Build expat ──────────────────────────────────────────────────────────

build_expat() {
    # Re-enabled: expat should build cleanly with distclean + prevent_autoreconf.
    local src="$CONTRIB/expat"
    [[ -d "$src" ]] || { log2 "expat source not found, skipping"; return; }
    log "Building expat 2.7.1"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET \
        --prefix=/usr \
        --libdir=/usr/lib \
        --without-docbook \
        --without-xmlwf
    gmake -j"$JOBS"
    gmake install DESTDIR="$STAGE"
    clean_la_files
}

# ── Build XML::Parser (perl XS module) ──────────────────────────────────

build_xml_parser() {
    # Perl XS wrapper around libexpat. Used by lib/libbsm/auditxml to
    # generate adt_xlate.c / adt_event.h from adt.xml at build time.
    # Must run after build_perl AND build_expat (depends on both).
    local src="$CONTRIB/XML-Parser"
    [[ -d "$src" ]] || { log2 "XML-Parser source not found, skipping"; return; }
    log "Building XML::Parser 2.47"
    cd "$src"
    [[ -f Makefile ]] && gmake distclean 2>/dev/null || true
    # Build against expat in $STAGE; install into $STAGE for merge_toolchain
    # to rsync into proto. The build host's perl is the same 5.40.1 we ship,
    # so the resulting Expat.so is ABI-compatible with the deployed perl.
    perl Makefile.PL \
        EXPATLIBPATH="$STAGE/usr/lib" \
        EXPATINCPATH="$STAGE/usr/include"
    gmake -j"$JOBS"
    gmake install DESTDIR="$STAGE"
}

# ── Build bzip2 ─────────────────────────────────────────────────────────

build_bzip2() {
    local src="$CONTRIB/bzip2"
    [[ -d "$src" ]] || { log2 "bzip2 source not found, skipping"; return; }
    log "Building bzip2 1.0.8"
    cd "$src"
    gmake clean 2>/dev/null || true
    gmake -j"$JOBS" \
        CC="${CC:-gcc}" \
        CFLAGS="${CFLAGS:--O2 -pipe} -fPIC" \
        PREFIX=/usr
    gmake install PREFIX="$STAGE/usr"
    # Fix absolute symlinks created by bzip2 Makefile
    cd "$STAGE/usr/bin"
    ln -sf bzdiff bzcmp
    ln -sf bzgrep bzegrep
    ln -sf bzgrep bzfgrep
    ln -sf bzmore bzless
    # Also build shared library
    cd "$src"
    gmake clean 2>/dev/null || true
    gmake -f Makefile-libbz2_so \
        CC="${CC:-gcc}" \
        CFLAGS="${CFLAGS:--O2 -pipe} -fPIC"
    cp -a libbz2.so* "$STAGE/usr/lib/"
}

# ── Build xz ────────────────────────────────────────────────────────────

build_xz() {
    local src="$CONTRIB/xz"
    [[ -d "$src" ]] || { log2 "xz source not found, skipping"; return; }
    log "Building xz 5.8.2"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET --prefix=/usr --disable-nls
    gmake -j"$JOBS" || true
    [[ -f src/xz/xz ]] || die "xz binary not found after build"
    gmake install DESTDIR="$STAGE" || true
}

# ── Build gzip ───────────────────────────────────────────────────────────

build_gzip() {
    local src="$CONTRIB/gzip"
    [[ -d "$src" ]] || { log2 "gzip source not found, skipping"; return; }
    log "Building gzip 1.14"
    cd "$src"
    # Patch gzip.c: disable BUFFER_ALIGNED which uses alignas(4096) in a
    # position GCC 14 rejects (inside DECLARE macro as type qualifier).
    # Patch the source directly — config.h gets regenerated by make.
    if grep -q 'define BUFFER_ALIGNED alignas' gzip.c; then
        sed -i 's/# define BUFFER_ALIGNED alignas (4096)/# define BUFFER_ALIGNED \/* disabled *\//' gzip.c
    fi
    prevent_autoreconf "$src"
    ./configure $BUILD_TRIPLET --prefix=/usr
    gmake -j1 MAKEINFO=true || true
    [[ -f gzip ]] || die "gzip binary not found after build"
    gmake install DESTDIR="$STAGE" || true
    # Create gzcat symlink (GNU gzip only installs zcat, not gzcat)
    ln -sf gzip "$STAGE/usr/bin/gzcat"
}

# ── Build tar ────────────────────────────────────────────────────────────

build_tar() {
    local src="$CONTRIB/tar"
    [[ -d "$src" ]] || { log2 "tar source not found, skipping"; return; }
    log "Building tar 1.35"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    FORCE_UNSAFE_CONFIGURE=1 \
    ./configure $BUILD_TRIPLET --prefix=/usr --disable-nls
    gmake -j"$JOBS" || true
    [[ -f src/tar ]] || die "tar binary not found after build"
    gmake install DESTDIR="$STAGE" || true
    # Create gtar symlink for compatibility
    ln -sf tar "$STAGE/usr/bin/gtar"
}

# ── Build bash ───────────────────────────────────────────────────────────
#
# bash is required in base: gnu-ld-wrapper, /usr/bin/{gunzip,zcat,zgrep,...},
# zports build.sh scripts, and many third-party shell scripts all use
# #!/usr/bin/bash or #!/bin/bash.  Without bash in /usr/bin, gcc's collect2
# invokes gnu-ld-wrapper which fails with posix_spawnp: No such file.
#
build_bash() {
    local src="$CONTRIB/bash"
    [[ -d "$src" ]] || { log2 "bash source not found, skipping"; return; }
    log "Building bash 5.2"
    cd "$src"
    gmake distclean 2>/dev/null || true
    prevent_autoreconf "$src"
    # GCC 14 promoted -Wincompatible-pointer-types from warning to error
    # by default. Bash 5.2's bundled readline calls tputs with a function
    # pointer that doesn't match illumos's <termcap.h> declaration
    # (int(*)(int) vs int(*)(char)). Restore the GCC 13- behaviour for
    # this build only.
    CFLAGS="$CFLAGS -Wno-error=incompatible-pointer-types" \
    ./configure $BUILD_TRIPLET \
        --prefix=/usr \
        --without-bash-malloc \
        --disable-nls
    CFLAGS="$CFLAGS -Wno-error=incompatible-pointer-types" \
    gmake -j"$JOBS"
    [[ -f bash ]] || die "bash binary not found after build"
    gmake install DESTDIR="$STAGE"
    # /bin/bash is also referenced by many scripts (zloop etc.) — install
    # a copy so both shebang paths work on the deployed system.
    mkdir -p "$STAGE/bin"
    cp "$STAGE/usr/bin/bash" "$STAGE/bin/bash"
    chmod 755 "$STAGE/bin/bash"
}

# ── Main ─────────────────────────────────────────────────────────────────

# ── Build zstd ───────────────────────────────────────────────────────────

build_zstd() {
    local src="$CONTRIB/zstd"
    [[ -d "$src" ]] || { log2 "zstd source not found, skipping"; return; }
    log "Building zstd 1.5.7"
    cd "$src"
    # MKDIR="mkdir -p": hh-build env exports MKDIR=/bin/mkdir (no -p),
    # which breaks zstd's nested obj/ directory creation.
    gmake -j"$JOBS" \
        CC="${GNUC_ROOT}/bin/gcc" \
        CFLAGS="-m64 -O2 -fPIC" \
        LDFLAGS="-m64 -shared" \
        MKDIR="mkdir -p" \
        PREFIX=/usr \
        LIBDIR=/usr/lib \
        lib-mt
    # Manual install (lib-mt target has no install rule)
    mkdir -p "$STAGE/usr/lib" "$STAGE/usr/include"
    cp lib/libzstd.so* "$STAGE/usr/lib/" 2>/dev/null || true
    cp lib/libzstd.a "$STAGE/usr/lib/" 2>/dev/null || true
    cp lib/zstd.h lib/zstd_errors.h lib/zdict.h "$STAGE/usr/include/" 2>/dev/null || true
    # Create soname symlinks
    cd "$STAGE/usr/lib"
    ln -sf libzstd.so.1.5.7 libzstd.so.1 2>/dev/null || true
    ln -sf libzstd.so.1 libzstd.so 2>/dev/null || true
}

# ── Main ─────────────────────────────────────────────────────────────────

log "Building base tools"
log2 "CONTRIB=$CONTRIB"
log2 "GNUC_ROOT=$GNUC_ROOT"
log2 "STAGE=$STAGE"

mkdir -p "$STAGE/usr/bin" "$STAGE/usr/lib" "$STAGE/usr/include"

build_m4;      check_dev_null
build_make;    check_dev_null
build_perl;    check_dev_null
build_cmake;   check_dev_null
build_libffi;  check_dev_null
build_expat;   check_dev_null
build_xml_parser; check_dev_null
build_bzip2;   check_dev_null
build_xz;      check_dev_null
build_gzip;    check_dev_null
build_tar;     check_dev_null
build_bash;    check_dev_null
build_zstd;    check_dev_null

log "Base tools build complete"
