calog/tools/crossDeps.sh

814 lines
42 KiB
Bash
Executable file

#!/usr/bin/env bash
# crossDeps.sh -- reproducibly (re)build the heavy vendored dependencies for a cross target from
# vendor/ source using a single zig toolchain, into build/cross/<target>/. This is what makes the
# full-CLI cross builds (tools/crossMacFull.sh, tools/crossWinFull.sh) reproducible from a clean
# checkout: those scripts CONSUME the pinned deps this script PRODUCES (OpenSSL, libxml2, PCRE2,
# libssh2, MariaDB, PostgreSQL/libpq, Tcl, mruby, the compression/archive stack, and -- on Windows --
# winpthreads). It regenerates the zig compiler wrappers + the CMake toolchain file first, so nothing
# under build/cross/ needs to pre-exist.
#
# Usage: [ZIG=/path/to/zig] ./tools/crossDeps.sh <win|mac-x64|mac-arm64> [dep ...]
# dep (optional): build only the named dep(s), e.g. `openssl libssh2`. Default: all, in dep order.
# Deps in order: codecs xz openssl libxml2 pcre2 libssh2 mariadb postgres tcl mruby winpthreads libarchive
#
# Requirements: zig (https://ziglang.org/download/), cmake, perl (OpenSSL), and a POSIX make/rake env.
set -eu
cd "$(dirname "$0")/.."
R=$(pwd)
ZIG=${ZIG:-${CALOG_ZIG:-/home/scott/zig/current/zig}}
[ -x "$ZIG" ] || command -v "$ZIG" >/dev/null 2>&1 || { echo "error: zig not found at '$ZIG'. Set ZIG=/path/to/zig (a PERMANENT install; see https://ziglang.org/download/)." >&2; exit 1; }
ZIGABS=$(command -v "$ZIG" 2>/dev/null || echo "$ZIG"); ZIGABS=$(readlink -f "$ZIGABS" 2>/dev/null || echo "$ZIGABS")
export CALOG_ZIG="$ZIGABS"
command -v cmake >/dev/null 2>&1 || { echo "error: cmake not found." >&2; exit 1; }
T=${1:-}
case "$T" in
win) TRIPLE=x86_64-windows-gnu; SYSNAME=Windows; SYSPROC=x86_64 ;;
mac-x64) TRIPLE=x86_64-macos; SYSNAME=Darwin; SYSPROC=x86_64 ;;
mac-arm64) TRIPLE=aarch64-macos; SYSNAME=Darwin; SYSPROC=aarch64 ;;
*) echo "usage: $0 <win|mac-x64|mac-arm64> [dep ...]" >&2; exit 1 ;;
esac
shift
BIN="$R/build/cross/bin"
O="${OUT:-$R/build/cross/$T}" # OUT overrides the per-target output dir (e.g. to verify without clobbering pins)
TC="$R/build/cross/toolchain-$T.cmake"
CC="$BIN/zcc-$T"
CXX="$BIN/zcxx-$T"
AR="$BIN/zar"
RANLIB="$BIN/zranlib"
mkdir -p "$BIN" "$O"
# ---------------------------------------------------------------------------------------------------
# Regenerate the single-binary zig wrappers + the CMake toolchain file (idempotent). The wrappers
# resolve zig via $CALOG_ZIG so they never bake in a session/temp path.
# ---------------------------------------------------------------------------------------------------
gen_wrapper() { # <name> <zig-subcommand-and-flags>
printf '#!/bin/sh\n# calog cross wrapper -- resolves zig via $CALOG_ZIG (permanent install).\nexec "%s" %s "$@"\n' \
'${CALOG_ZIG:-/home/scott/zig/current/zig}' "$2" > "$BIN/$1"
chmod +x "$BIN/$1"
}
gen_wrapper zar "ar"
gen_wrapper zranlib "ranlib"
gen_wrapper "zcc-$T" "cc -target $TRIPLE"
gen_wrapper "zcxx-$T" "c++ -target $TRIPLE"
if [ "$T" = win ]; then
# windres-compatible shim backed by `zig rc` (CMake appends win32/libxml2.rc for libxml2, etc.).
cat > "$BIN/zwindres-win" <<'WINDRES'
#!/bin/sh
# windres-compatible shim backed by `zig rc`, targeting x86_64-windows. Handles BOTH arg styles:
# CMake's positional `windres -O coff <defs> <incs> INPUT OUTPUT`, and the GNU/PostgreSQL form
# `windres -i INPUT -o OUTPUT --include-dir=DIR`.
ZIG="${CALOG_ZIG:-/home/scott/zig/current/zig}"
DEFS=""; INCS=""; POS=""; IN=""; OUT=""
while [ $# -gt 0 ]; do
case "$1" in
-O) shift ;; # output-format selector; we always emit coff
-O*) ;;
-i) shift; IN="$1" ;;
-o) shift; OUT="$1" ;;
-D) shift; DEFS="$DEFS /d $1" ;;
-D*) DEFS="$DEFS /d ${1#-D}" ;;
-I) shift; INCS="$INCS /i $1" ;;
-I*) INCS="$INCS /i ${1#-I}" ;;
--include-dir) shift; INCS="$INCS /i $1" ;;
--include-dir=*) INCS="$INCS /i ${1#--include-dir=}" ;;
*) POS="$POS $1" ;;
esac
shift
done
[ -n "$IN" ] || { set -- $POS; IN="$1"; OUT="$2"; } # positional INPUT OUTPUT when no -i/-o given
exec "$ZIG" rc /:output-format coff /:target x86_64 /:auto-includes gnu $DEFS $INCS /fo "$OUT" "$IN"
WINDRES
chmod +x "$BIN/zwindres-win"
fi
# CMake toolchain: SYSTEM_NAME triggers cross mode (skips run-only checks); the mac SYSTEM_NAME is
# Darwin, win is Windows. FIND_ROOT_PATH modes keep CMake off host libs/headers.
cat > "$TC" <<EOF
set(CMAKE_SYSTEM_NAME $SYSNAME)
set(CMAKE_SYSTEM_PROCESSOR $SYSPROC)
set(CMAKE_C_COMPILER "$CC")
set(CMAKE_CXX_COMPILER "$CXX")
set(CMAKE_AR "$AR")
set(CMAKE_RANLIB "$RANLIB")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
EOF
if [ "$T" = win ]; then
# CMake compiles win32 .rc resources (libxml2 has win32/libxml2.rc) with windres; point it at the
# zig-rc shim so no host windres is needed.
echo "set(CMAKE_RC_COMPILER \"$BIN/zwindres-win\")" >> "$TC"
fi
echo "== crossDeps: target=$T triple=$TRIPLE zig=$($ZIG version) =="
echo " wrappers + $TC regenerated"
# ===================================================================================================
# Per-dependency build functions. Each builds vendor/<dep> into $O, producing the artifact the full
# CLI link expects. (Populated below -- reconstructed by tools and verified by build.)
# ===================================================================================================
# ---- codecs (confidence: high) ----
build_codecs() {
# Builds the 4 object-rule compression codecs (zlib, bzip2, lz4, zstd) from
# vendor/ source into $O/lib as static archives. Pure C, no configure/cmake.
# Target-agnostic: the caller's $CC (zcc-$T) already selects the triple, so
# win / mac-x64 / mac-arm64 all take the identical path. Verified against
# tools/crossArchive.sh build_codecs and tools/crossMacFull.sh:38-48, and
# against the pinned build/cross/<T>/{lib,obj} object listings.
local c
mkdir -p "$O/obj" "$O/lib" || return 1
# zlib: -DHAVE_UNISTD_H so it includes <unistd.h> for lseek (configure would
# normally define this; the object-rule build must pass it explicitly).
for c in "$R"/vendor/zlib/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -DHAVE_UNISTD_H -w -O2 -I"$R/vendor/zlib" -c "$c" -o "$O/obj/z_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libz.a" "$O"/obj/z_*.o || return 1
# bzip2
for c in "$R"/vendor/bzip2/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -I"$R/vendor/bzip2" -c "$c" -o "$O/obj/bz_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libbz2.a" "$O"/obj/bz_*.o || return 1
# lz4 (lz4.c lz4frame.c lz4hc.c xxhash.c)
for c in "$R"/vendor/lz4/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -I"$R/vendor/lz4" -c "$c" -o "$O/obj/l4_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/liblz4.a" "$O"/obj/l4_*.o || return 1
# zstd: -DZSTD_DISABLE_ASM (no huf_decompress_amd64 asm under a cross triple).
# Only common/ compress/ decompress/ are vendored (no legacy/ dictBuilder/).
for c in "$R"/vendor/zstd/common/*.c "$R"/vendor/zstd/compress/*.c "$R"/vendor/zstd/decompress/*.c; do
"$CC" -std=c11 -D_GNU_SOURCE -w -O2 -DZSTD_DISABLE_ASM -I"$R/vendor/zstd" -c "$c" -o "$O/obj/zs_$(basename "$c" .c).o" || return 1
done
"$AR" rcs "$O/lib/libzstd.a" "$O"/obj/zs_*.o || return 1
echo " [codecs] $O/lib/{libz,libbz2,liblz4,libzstd}.a"
}
# ---- xz (confidence: high) ----
build_xz() {
# xz/liblzma -- CMake, out-of-tree, pure C (no CXX, no sibling deps).
# Identical recipe for win / mac-x64 / mac-arm64: the target triple + AR/RANLIB
# all come from $TC (the per-target CMake toolchain file), so no per-target branches.
# vendor/xz stays pristine: -S <source> -B <builddir under $O>.
local SRC="$R/vendor/xz"
local B="$O/xz"
cmake -S "$SRC" -B "$B" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DENABLE_THREADS=OFF \
-DENABLE_NLS=OFF \
-DENABLE_DOXYGEN=OFF \
>"$O/xz-cfg.log" 2>&1 \
|| { echo " xz configure FAILED (see $O/xz-cfg.log)"; return 1; }
cmake --build "$B" --target liblzma -j \
>"$O/xz-build.log" 2>&1 \
|| { echo " xz build FAILED (see $O/xz-build.log)"; return 1; }
# Artifact: $O/xz/liblzma.a . Consumers (libarchive full-CLI link) also need the
# public headers, exposed straight from the pristine vendor tree at
# $R/vendor/xz/src/liblzma/api -- nothing is installed or copied.
test -f "$B/liblzma.a" || { echo " xz: liblzma.a missing after build"; return 1; }
}
# ---- openssl (confidence: high) ----
build_openssl() {
# Cross-build OpenSSL 3.5.7 static libs (libssl.a + libcrypto.a) for $T from vendor/openssl.
# Contract vars assumed set by caller: R, T, CC, AR, O.
# vendor/openssl is configured IN-TREE by the native build, so we build in a COPY under
# $O/openssl-src and leave vendor/ pristine.
local SRC="$O/openssl-src"
# 1) Fresh copy of the vendored tree. cp -r preserves mtimes, so any glibc/native objects
# left in vendor/openssl would look "up to date" -- drop all build products + the previous
# configure state so Configure/make start clean.
rm -rf "$SRC"
cp -r "$R/vendor/openssl" "$SRC" || return 1
find "$SRC" -type f \( -name '*.o' -o -name '*.a' -o -name '*.d' \) -delete
rm -f "$SRC/configdata.pm" "$SRC/Makefile"
# 2) Pick the OpenSSL Configure target for this platform (names verified against each
# pinned openssl-src/cfg.log + configdata.pm "perlargv").
local OSSL_TARGET
case "$T" in
win) OSSL_TARGET="mingw64" ;;
mac-x64) OSSL_TARGET="darwin64-x86_64-cc" ;;
mac-arm64) OSSL_TARGET="darwin64-arm64-cc" ;;
*) echo "build_openssl: unknown target $T" >&2; return 1 ;;
esac
# 3) Configure + build the static libs only (build_libs skips apps/tests).
# no-asm on all three targets (zig cc / mingw has no gas-compatible perlasm path here).
# CC is the only toolchain var passed -- AR/RANLIB stay at host defaults (host ar emits
# GNU-format .a; mac fixes that with the repack in step 4, win/mingw reads GNU fine).
(
cd "$SRC" || exit 1
perl ./Configure "$OSSL_TARGET" no-shared no-tests no-docs no-asm CC="$CC" \
> "$O/openssl-cfg.log" 2>&1 || exit 1
make -j"$(nproc)" build_libs > "$O/openssl-build.log" 2>&1 || exit 1
) || { echo " openssl ($T) FAILED (see $O/openssl-cfg.log / $O/openssl-build.log)" >&2; return 1; }
# Artifacts now at: $O/openssl-src/libssl.a and $O/openssl-src/libcrypto.a
[ -f "$SRC/libssl.a" ] && [ -f "$SRC/libcrypto.a" ] || { echo " openssl ($T): missing libs" >&2; return 1; }
# 4) MAC ONLY: zig's Mach-O linker cannot parse the GNU-format .a that OpenSSL's build emits
# ("unknown cpu architecture"). Repack the SAME objects into BSD/darwin ar format
# (#1/N long names + __.SYMDEF) at $O/openssl-repack/. Member basenames are already
# unique (OpenSSL prefixes each object with its lib target, e.g. libcrypto-lib-*.o,
# libcommon-lib-*.o), so flat extraction cannot collide.
if [ "$T" = mac-x64 ] || [ "$T" = mac-arm64 ]; then
local REPACK="$O/openssl-repack"
rm -rf "$REPACK"
mkdir -p "$REPACK"
local lib
for lib in ssl crypto; do
local tmp="$REPACK/tmp-$lib"
rm -rf "$tmp"
mkdir -p "$tmp"
(
cd "$tmp" || exit 1
"$AR" x "$SRC/lib$lib.a" || exit 1
"$AR" --format=darwin crs "$REPACK/lib$lib.a" ./*.o || exit 1
) || { echo " openssl repack lib$lib ($T) FAILED" >&2; return 1; }
rm -rf "$tmp"
done
# Convenience: expose the same generated headers under the repack dir (as the pinned
# openssl-repack/ did) so an OPENSSL_ROOT_DIR=openssl-repack also finds include/.
ln -sfn "$SRC/include" "$REPACK/include"
[ -f "$REPACK/libssl.a" ] && [ -f "$REPACK/libcrypto.a" ] || { echo " openssl repack ($T): missing libs" >&2; return 1; }
fi
}
# ---- libxml2 (confidence: high) ----
build_libxml2() {
# libxml2 static lib, cross-built out-of-tree from vendor/libxml2 with the
# per-target CMake toolchain ($TC). Flags are identical for win/mac-x64/mac-arm64
# (verified against each target's build/cross/<T>/libxml2/CMakeCache.txt) and match
# tools/crossArchive.sh build_libxml2_musl. Only $TC differs per target, so there is
# no per-target if-branch. Pure C: no CXX, no zwindres (PROGRAMS=OFF). No sibling deps
# or host libs: HTTP/FTP/ICU/LZMA/ZLIB/ICONV all OFF, so libxml2 is self-contained
# (mac iconv would come from libSystem but is disabled here; win/mingw likewise).
# Produces exactly: $O/libxml2/libxml2.a (path the full-CLI link expects).
# Clean build dir first: CMAKE_RC_COMPILER (win .rc -> zwindres-win) is cached on the FIRST
# configure, so a stale cache from an earlier run would keep an old windres path.
rm -rf "$O/libxml2"
cmake -S "$R/vendor/libxml2" -B "$O/libxml2" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF \
-DLIBXML2_WITH_PYTHON=OFF -DLIBXML2_WITH_PROGRAMS=OFF -DLIBXML2_WITH_TESTS=OFF \
-DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_ICU=OFF \
-DLIBXML2_WITH_LZMA=OFF -DLIBXML2_WITH_ZLIB=OFF -DLIBXML2_WITH_ICONV=OFF \
-DLIBXML2_WITH_MODULES=OFF -DLIBXML2_WITH_CATALOG=OFF -DLIBXML2_WITH_THREADS=ON \
>"$O/libxml2-cfg.log" 2>&1 \
&& cmake --build "$O/libxml2" --target LibXml2 -j >"$O/libxml2-build.log" 2>&1 \
|| { echo " libxml2 ($T) FAILED (see $O/libxml2-build.log)"; return 1; }
}
# ---- pcre2 (confidence: high) ----
build_pcre2() {
# pcre2: 8-bit, static, NO JIT, Unicode ON. Pure C (no CXX needed -- so
# win's missing zcxx wrapper is irrelevant here). Out-of-tree CMake build
# directly INTO $O/pcre2 so the generated pcre2.h and libpcre2-8.a land at
# the exact path the full-CLI link expects (-I$O/pcre2 / $O/pcre2/libpcre2-8.a).
# vendor/pcre2 stays pristine (clean out-of-tree, no in-tree config gotcha).
echo " building pcre2 for $T ..."
rm -rf "$O/pcre2"
cmake -G Ninja -S "$R/vendor/pcre2" -B "$O/pcre2" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DPCRE2_BUILD_PCRE2_8=ON \
-DPCRE2_BUILD_PCRE2_16=OFF \
-DPCRE2_BUILD_PCRE2_32=OFF \
-DPCRE2_SUPPORT_JIT=OFF \
-DPCRE2_SUPPORT_UNICODE=ON \
-DPCRE2_NEWLINE=LF \
-DPCRE2_BUILD_TESTS=OFF \
-DPCRE2_BUILD_PCRE2GREP=OFF \
>"$O/pcre2-cfg.log" 2>&1 || { echo " pcre2 configure FAILED (see $O/pcre2-cfg.log)"; return 1; }
# Static-lib target name is pcre2-8-static; it emits $O/pcre2/libpcre2-8.a.
cmake --build "$O/pcre2" --target pcre2-8-static -j \
>"$O/pcre2-build.log" 2>&1 || { echo " pcre2 build FAILED (see $O/pcre2-build.log)"; return 1; }
test -f "$O/pcre2/libpcre2-8.a" || { echo " pcre2 artifact missing"; return 1; }
echo " pcre2 OK -> $O/pcre2/libpcre2-8.a"
}
# ---- libssh2 (confidence: high) ----
build_libssh2() {
# libssh2 -- CMake, out-of-tree (vendor/libssh2 stays pristine), static, OpenSSL crypto backend
# pinned to the cross OpenSSL built earlier under $O/openssl-src. No zlib (compression stays off,
# matching the pinned build), no examples, no tests. Pure C -- $CXX is unused.
#
# Uses caller globals: R T CC CXX AR RANLIB TC O (set by crossDeps.sh harness).
# Dependency: build_openssl must have run first ($O/openssl-src/{include,libcrypto.a,libssl.a}).
[ -f "$O/openssl-src/libcrypto.a" ] || { echo " libssh2: missing cross OpenSSL at $O/openssl-src (build openssl first)"; return 1; }
local B="$O/libssh2/_build" # out-of-tree build dir; matches the pinned layout exactly
rm -rf "$O/libssh2"
mkdir -p "$B"
# NOTE: CMAKE_BUILD_TYPE=Release (i.e. -O2, NO UBSan). The ORIGINAL pinned mac libssh2 was built
# with NO build type, so it picked up zig's default UndefinedBehaviorSanitizer instrumentation
# (why the pinned mac libssh2.a is ~4.5-4.9MB vs ~1.5MB here) and the mac final link had to pass
# -fsanitize=undefined. Building Release here drops that instrumentation, so tools/crossMacFull.sh
# can drop its link-only -fsanitize=undefined once deps are rebuilt with this script.
cmake -S vendor/libssh2 -B "$B" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON \
-DBUILD_EXAMPLES=OFF -DBUILD_TESTING=OFF \
-DENABLE_DEBUG_LOGGING=OFF \
-DCRYPTO_BACKEND=OpenSSL \
-DOPENSSL_ROOT_DIR="$O/openssl-src" \
-DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_CRYPTO_LIBRARY="$O/openssl-src/libcrypto.a" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-src/libssl.a" \
>"$O/libssh2-cfg.log" 2>&1 || { echo " libssh2 configure FAILED (see $O/libssh2-cfg.log)"; return 1; }
# Static-lib target is libssh2_static (LIB_NAME_static); it emits src/libssh2.a inside $B.
cmake --build "$B" --target libssh2_static -j >"$O/libssh2-build.log" 2>&1 \
|| { echo " libssh2 build FAILED (see $O/libssh2-build.log)"; return 1; }
# Artifact: $O/libssh2/_build/src/libssh2.a (this is what the mac link consumes directly).
# The Windows full-CLI link expects it at $O/libssh2/libssh2.a, so mirror the pinned copy step.
if [ "$T" = win ]; then
cp "$B/src/libssh2.a" "$O/libssh2/libssh2.a" || return 1
echo " [lib] libssh2.a -> $O/libssh2/libssh2.a"
else
echo " [lib] libssh2.a -> $B/src/libssh2.a"
fi
}
# ---- mariadb (confidence: high) ----
build_mariadb() {
# MariaDB Connector/C (client library only, no server).
# CMake out-of-tree: source stays pristine at vendor/mariadb, build tree at $O/mariadb.
# Depends on the already-built sibling OpenSSL under $O/openssl-src (pinned, never host).
local src="$R/vendor/mariadb"
local bld="$O/mariadb"
local ossl="$O/openssl-src"
# Pin the sibling cross OpenSSL; refuse to fall back to host TLS.
if [ ! -f "$ossl/libssl.a" ] || [ ! -f "$ossl/libcrypto.a" ]; then
echo "build_mariadb: sibling OpenSSL missing at $ossl (build openssl first)" >&2
return 1
fi
# Fresh out-of-tree build dir (== the pinned layout the full-CLI link expects).
rm -rf "$bld"
mkdir -p "$bld"
# Generator: pins used Unix Makefiles (win) / Ninja (mac); either yields
# identical artifacts. Prefer Ninja when present.
local gen="Unix Makefiles"
if command -v ninja >/dev/null 2>&1; then
gen="Ninja"
fi
# Configure. The toolchain file ($TC) already hardcodes CMAKE_C_COMPILER
# (zcc-$T), CMAKE_AR (zar), CMAKE_RANLIB (zranlib) and the Darwin/Windows
# CMAKE_SYSTEM_NAME, so CC/AR/RANLIB are not re-passed. Pure C -- no CXX,
# which matters for win (no zcxx). The only per-target input that varies is
# $TC and the $O path; both are already target-scoped by the caller.
cmake -S "$src" -B "$bld" \
-G "$gen" \
-DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release \
-DWITH_SSL=OPENSSL \
-DOPENSSL_ROOT_DIR="$ossl" \
-DOPENSSL_INCLUDE_DIR="$ossl/include" \
-DOPENSSL_SSL_LIBRARY="$ossl/libssl.a" \
-DOPENSSL_CRYPTO_LIBRARY="$ossl/libcrypto.a" \
-DWITH_EXTERNAL_ZLIB=OFF \
-DWITH_UNIT_TESTS=OFF \
-DWITH_CURL=OFF \
> "$O/mariadb-cfg.log" 2>&1 || {
echo "build_mariadb: configure failed (see $O/mariadb-cfg.log)" >&2
return 1
}
# Build ONLY the static client lib target (no shared libmariadb, no tools,
# no plugins-as-DLLs). This is exactly what both pinned build.logs did.
cmake --build "$bld" --target mariadbclient -j"$(nproc)" \
> "$O/mariadb-build.log" 2>&1 || {
echo "build_mariadb: build failed (see $O/mariadb-build.log)" >&2
return 1
}
# Verify the pinned artifact path the full-CLI link consumes.
if [ ! -f "$bld/libmariadb/libmariadbclient.a" ]; then
echo "build_mariadb: artifact libmariadbclient.a missing" >&2
return 1
fi
}
# ---- tcl (confidence: high) ----
build_tcl() {
# Builds Tcl 9.0.4 as a STATIC archive for target $T using the caller's
# zig-cc toolchain, out-of-tree, from vendored source. Tcl bundles its own
# zlib + libtommath, so it depends on NO sibling deps under $O.
#
# Artifact (in-place in the build dir, exactly where the full-CLI link expects):
# mac-x64 / mac-arm64 : $O/tcl/libtcl9.0.a
# win : $O/tcl/libtcl90.a
#
# Toolchain contract (set by caller): R T CC CXX AR RANLIB TC O ZIG
local BUILD="$O/tcl"
local WINRC="$R/build/cross/bin/zwindres-win"
local SRC # absolute path to the configure script's directory
local CFG # absolute path to configure
local LIB # target-specific static lib filename == make target
local HOSTTRIPLE
rm -rf "$BUILD"
mkdir -p "$BUILD"
if [ "$T" = "win" ]; then
# GOTCHA: the vendored tree (vendor/tcl) was trimmed to a unix-only
# checkout -- it has generic/ unix/ compat/ libtommath/ library/ but NO
# win/ subdirectory. The Windows port is driven by win/configure +
# win/Makefile.in + the tclWin*.c sources, none of which are vendored.
# The original pinned build used a full upstream tcl9.0.4 tree unpacked
# in a scratchpad. To reproduce, vendor/tcl MUST first be restored to a
# complete tcl9.0.4 source tree (re-add the win/ subdir from the
# upstream tarball). We resolve the win source root here and fail loudly
# if it is missing rather than silently producing a broken lib.
SRC="$R/vendor/tcl/win"
if [ ! -x "$SRC/configure" ]; then
echo "build_tcl: ERROR: $SRC/configure not found." >&2
echo " vendor/tcl lacks the win/ subdir; restore the full tcl9.0.4" >&2
echo " source tree (with win/) before cross-building tcl for win." >&2
return 1
fi
CFG="$SRC/configure"
LIB="libtcl90.a"
HOSTTRIPLE="x86_64-w64-mingw32"
else
# mac-x64 / mac-arm64: unix/configure, driven out-of-tree from $O/tcl.
# GOTCHA: like win/, the trimmed vendor/tcl also lacks the macosx/ subdir,
# which configure --host=*-apple-darwin pulls in (tclMacOSXBundle.c etc.).
# The native Linux build never needs it; a mac cross build does. Fail loudly
# here rather than emit a cryptic "No rule to make target .../macosx/*.c".
if [ ! -f "$R/vendor/tcl/macosx/tclMacOSXBundle.c" ]; then
echo "build_tcl: ERROR: vendor/tcl lacks the macosx/ subdir (needed for a" >&2
echo " Darwin cross build). Restore the full tcl9.0.4 source tree (with" >&2
echo " macosx/) before cross-building tcl for mac-x64/mac-arm64." >&2
return 1
fi
SRC="$R/vendor/tcl/unix"
CFG="$SRC/configure"
LIB="libtcl9.0.a"
if [ "$T" = "mac-arm64" ]; then
HOSTTRIPLE="aarch64-apple-darwin"
else
HOSTTRIPLE="x86_64-apple-darwin"
fi
fi
(
cd "$BUILD" || exit 1
if [ "$T" = "win" ]; then
# win: mingw cross via --host, static, zig windres for the .rc.
# No CXX and no tcl_cv_sys_version needed (win/configure does not
# probe the target with a runnable uname). Verified against
# build/cross/win/tcl/config.log (the $ configure line).
"$CFG" \
--host="$HOSTTRIPLE" \
--disable-shared \
CC="$CC" \
AR="$AR" \
RANLIB="$RANLIB" \
RC="$WINRC" \
> cfg.log 2>&1 || { echo "build_tcl: configure failed (see $BUILD/cfg.log)" >&2; exit 1; }
else
# mac: unix/configure via --host with an Apple-Darwin triple.
# tcl_cv_sys_version=Darwin-24.0.0 is REQUIRED: it short-circuits the
# configure step that would otherwise run `uname -r` on the target
# (impossible when cross-compiling; on the Linux host it would
# misdetect and disable Darwin code paths). Value taken verbatim
# from build/cross/mac-*/tcl/config.log. No CXX (Tcl is pure C).
"$CFG" \
--host="$HOSTTRIPLE" \
--disable-shared \
CC="$CC" \
AR="$AR" \
RANLIB="$RANLIB" \
tcl_cv_sys_version=Darwin-24.0.0 \
> cfg.log 2>&1 || { echo "build_tcl: configure failed (see $BUILD/cfg.log)" >&2; exit 1; }
fi
# Build ONLY the static library target (== $LIB). This deliberately
# avoids the default `all`/`binaries` targets, which link tclsh -- a
# cross-compiled binary that cannot run on the build host. The lib rule
# only needs object files + ar/ranlib (STLIB_LD = "$AR cr"; RANLIB).
# win rule (Makefile ~605): ${TCL_LIB_FILE}: ${TCL_OBJS} tclWinPanic
# ${DDE_OBJS} ${REG_OBJS} -> ar cr; ranlib (no zip prereq)
# mac rule (Makefile ~803): ${LIB_FILE}: ${STUB_LIB_FILE} ${OBJS}
# ${TCL_ZIP_FILE} -> ar cr; ranlib. The ${TCL_ZIP_FILE} prereq
# (libtcl9.0.4.zip) is built with the HOST compiler + host zip
# (minizip) -- it does NOT run the cross tclsh. With ZIPFS_BUILD=2
# the zip is a sidecar file and is NOT appended into the .a, so
# the archive contents are pure object files either way.
make -j"$(nproc)" "$LIB" > build.log 2>&1 || { echo "build_tcl: make $LIB failed (see $BUILD/build.log)" >&2; exit 1; }
) || return 1
if [ ! -f "$BUILD/$LIB" ]; then
echo "build_tcl: expected artifact $BUILD/$LIB not produced" >&2
return 1
fi
echo "build_tcl: produced $BUILD/$LIB"
}
# ---- mruby (confidence: high) ----
# ---------------------------------------------------------------------------------------------------
# mruby 4.0.0 (Ruby engine). UNLIKE the C-source deps, libmruby.a is GENERATED by mruby's own Rake
# build: mrbc (the bytecode compiler, run DURING the build to compile the Ruby-written stdlib/gems)
# must be NATIVE, while libmruby.a itself is cross-compiled with the zig wrappers. We therefore emit
# a two-target build_config: a 'host' build that produces ONLY native mrbc, and a cross build that
# produces the target libmruby.a. Gembox matches calog's lean native src/mruby/build_config.rb
# (stdlib + stdlib-ext + math + metaprog; metaprog pulls mruby-compiler for runtime eval). MRB_INT64
# keeps script integers 64-bit. No bin gems in the cross target -- calog links only the library.
#
# Artifact (both mac + win): $O/mruby/libmruby.a
# The mruby Rake writes to vendor/mruby/build/<buildname>/lib/libmruby.a; we copy that to $O/mruby/.
# The generated include tree (mrbconf.h + mruby/presym.h) stays at
# vendor/mruby/build/<buildname>/include -- the adapter compile in crossMacFull/crossWinFull.sh
# includes it directly, so we do NOT relocate it.
# Requires on the build host: ruby (>=2.5), the `rake` gem (minirake just exec's rake), and bison
# (mruby-compiler regenerates y.tab.c). These are host tools; the cross objects come from zig.
# ---------------------------------------------------------------------------------------------------
build_mruby() {
# Per-target build name + host_target triple. The build name must match what the full-CLI adapter
# compile expects for its -I: crossWinFull.sh uses cross-mingw; crossMacFull.sh uses cross-$T.
local buildname
local hosttriple
case "$T" in
win) buildname=cross-mingw; hosttriple=x86_64-w64-mingw32 ;;
mac-x64) buildname=cross-mac-x64; hosttriple=x86_64-apple-darwin ;;
mac-arm64) buildname=cross-mac-arm64; hosttriple=aarch64-apple-darwin ;;
esac
# Windows target links executables with .exe; also gates for_windows? source paths in gems.
local execext_line=""
if [ "$T" = win ]; then
execext_line=" conf.exts.executable = '.exe'"
fi
mkdir -p "$O/mruby"
# Emit the cross build_config next to the artifact (matches the pinned layout: the build_config.rb
# sits in $O/mruby/ and mruby drops its build_config.rb.lock there too). Unquoted heredoc so the
# shell interpolates the wrapper paths / build name / triple; the Ruby %{outfile}/%{objs} tokens
# and << appends contain no shell metacharacters.
cat > "$O/mruby/build_config.rb" <<EOF
# GENERATED by tools/crossDeps.sh -- calog cross build_config for mruby ($T).
# host -- native gcc build producing ONLY mrbc (runs on the build host to compile the
# Ruby-written stdlib/gems into libmruby.a). libmruby disabled here.
# $buildname -- the cross static libmruby.a, compiled by the zig wrapper for $T.
# Host target: native gcc, mrbc only. Named exactly 'host' so CrossBuild#mrbcfile finds it.
MRuby::Build.new('host') do |conf|
conf.toolchain :gcc
# Match the target's integer width so mrbc emits bytecode consistent with 64-bit script ints.
conf.cc.defines << 'MRB_INT64'
conf.build_mrbc_exec
conf.disable_libmruby
end
# Cross target: $T static libmruby.a via zig.
MRuby::CrossBuild.new('$buildname') do |conf|
conf.toolchain :gcc
# host_target drives for_windows? and platform-aware source paths.
conf.host_target = '$hosttriple'
conf.cc.command = "$CC"
conf.cc.flags << '-O2'
conf.cc.defines << 'MRB_INT64'
conf.linker.command = "$CC"
# archiver: mruby PREPENDS conf.archiver.command to archive_options, so the rcs letters must live
# in archive_options (putting them in command would inject a stray "rcs" arg before the options).
conf.archiver.command = "$AR"
conf.archiver.archive_options = 'rcs "%{outfile}" %{objs}'
$execext_line
# Lean gembox -- identical to calog's native src/mruby/build_config.rb.
conf.gembox 'stdlib'
conf.gembox 'stdlib-ext'
conf.gembox 'math'
conf.gembox 'metaprog'
end
EOF
# Force a clean rebuild of THIS target's cross tree so no stale objects survive (the native 'host'
# mrbc is left cached across targets -- it is architecture-neutral). vendor/mruby stays pristine
# apart from its own build/ output dir, exactly as the native Makefile build already uses it.
rm -rf "$R/vendor/mruby/build/$buildname"
# CALOG_ZIG is already exported by the caller so the zcc-*/zar wrappers resolve zig. Run mruby's
# Rake (minirake exec's the system rake) from the mruby tree so `conf.gembox 'name'` resolves
# against vendor/mruby/mrbgems. -j4 matches the native build.
( cd "$R/vendor/mruby" && MRUBY_CONFIG="$O/mruby/build_config.rb" ruby ./minirake -j4 )
# Copy the pinned artifact to the path the full-CLI link expects.
cp "$R/vendor/mruby/build/$buildname/lib/libmruby.a" "$O/mruby/libmruby.a"
}
# ---- winpthreads (confidence: high) ----
build_winpthreads() {
# WIN ONLY. macOS has native pthreads in libSystem (zig links it); no
# vendored winpthreads and no -DWINPTHREAD_STATIC on mac. Return early.
case "$T" in
mac-x64|mac-arm64)
echo "== winpthreads: skipped for $T (native libSystem pthreads) =="
return 0
;;
esac
# --- Windows (x86_64-windows-gnu / mingw) -------------------------------
# Object-rule build: compile every top-level src/*.c (NOT src/libgcc/*),
# archive into the single static lib the full-CLI link expects.
echo "== building vendored winpthreads for $T =="
local SRCDIR="$R/vendor/winpthreads"
local OBJ="$O/wpobj"
local OUTLIB="$O/libwinpthreads.a"
rm -rf "$OBJ"
mkdir -p "$OBJ"
local c
for c in "$SRCDIR"/src/*.c; do
"$CC" -c -O2 -w \
-I"$SRCDIR/include" -I"$SRCDIR/src" \
-DWINPTHREAD_STATIC=1 -DIN_WINPTHREAD=1 \
"$c" -o "$OBJ/$(basename "$c" .c).o" \
|| { echo "winpthreads build failed on $c"; return 1; }
done
rm -f "$OUTLIB"
"$AR" rcs "$OUTLIB" "$OBJ"/*.o \
|| { echo "winpthreads archive failed"; return 1; }
echo " [ok] $OUTLIB"
}
# ---- postgres (confidence: high) ----
# build_postgres -- PATCHED out-of-tree (in a COPY) build of vendor/postgres; produces the libpq
# frontend stack the full-CLI link consumes (libpq.a + libpgcommon_shlib.a + libpgport_shlib.a +
# fe_memutils.o + generated src/include). mac uses libSystem pthreads (no patch); win renames
# .obj->.o, forces a real ar rule for libpq.a, drops unsupported LDFLAGS, and localizes libpq's
# bundled pthread-win32 symbols to pg_pthread_* (vs winpthreads).
build_postgres() {
local BLD="$O/postgres-build"
local HOST LDOSSL PGLIBS PGWINDRES J
J=$(nproc 2>/dev/null || echo 4)
# PGLIBS: the Windows system libs OpenSSL depends on, so configure's `-lcrypto` link test passes
# (mingw OpenSSL pulls in ws2_32/crypt32/bcrypt/...). Empty on mac (libSystem covers it).
# PGWINDRES: on win, libpq compiles a win32ver.rc version resource (win32ver.o) via $(WINDRES) --
# point it at the zig-rc shim. Empty on mac (no .rc).
case "$T" in
win) HOST=x86_64-w64-mingw32 ; LDOSSL="$O/openssl-src" ; PGLIBS="-lws2_32 -lcrypt32 -lsecur32 -lbcrypt -lgdi32 -luser32 -ladvapi32" ; PGWINDRES="WINDRES=$BIN/zwindres-win" ;;
mac-x64) HOST=x86_64-apple-darwin ; LDOSSL="$O/openssl-repack" ; PGLIBS="" ; PGWINDRES="" ;;
mac-arm64) HOST=aarch64-apple-darwin; LDOSSL="$O/openssl-repack" ; PGLIBS="" ; PGWINDRES="" ;;
esac
# Fresh in-tree COPY so vendor/postgres stays pristine; distclean any inherited config/objects.
rm -rf "$BLD"
cp -a "$R/vendor/postgres" "$BLD"
( cd "$BLD" && make distclean >/dev/null 2>&1 || true )
if [ "$T" = win ]; then
# (1) Drop the LDFLAGS zig's lld/mingw rejects (absent from the produced Makefile.global).
perl -pi -e 's/ -Wl,--allow-multiple-definition//g; s/ -Wl,--disable-auto-import//g;' \
"$BLD/src/template/win32"
# (2) On win32 the stock Makefile.shlib builds libpq.a as a DLL import lib. Neutralize
# haslibarule + delete that override so the stock static-lib rule (rm; ar crs; touch)
# builds a real archive -- the recipe recorded in pg-libpq.log.
perl -pi -e 's/^\s*haslibarule\s*=\s*yes\s*$//' "$BLD/src/Makefile.shlib"
perl -0pi -e 's/\$\(stlib\): \$\(shlib\)\n\ttouch \$\@\n//g' "$BLD/src/Makefile.shlib"
fi
# configure (in-tree; template auto-selected from --host). SSL=openssl gives libpq TLS + RNG;
# CPPFLAGS/LDFLAGS point at the pinned OpenSSL (mac links the BSD-ar repack).
( cd "$BLD" && ./configure --host="$HOST" \
--without-readline --without-zlib --without-icu --with-ssl=openssl \
CC="$CC" AR="$AR" RANLIB="$RANLIB" \
CPPFLAGS="-I$O/openssl-src/include" LDFLAGS="-L$LDOSSL" LIBS="$PGLIBS" ) >"$O/pg-cfg.log" 2>&1
if [ "$T" = win ]; then
# (3) autoconf named conditional LIBOBJS objects .obj (OBJEXT=obj on mingw) but PG archives .o.
perl -pi -e 's/\.obj\b/.o/g' "$BLD/src/Makefile.global"
fi
# Generated headers first (codegen must not race under -j), then the leaf libs.
( cd "$BLD" && make -C src/backend generated-headers $PGWINDRES ) >"$O/pg-genhdr.log" 2>&1
( cd "$BLD" && make -C src/port -j"$J" $PGWINDRES ) >"$O/pg-port.log" 2>&1
( cd "$BLD" && make -C src/common -j"$J" $PGWINDRES ) >"$O/pg-common.log" 2>&1
( cd "$BLD" && make -C src/interfaces/libpq all-static-lib -j"$J" $PGWINDRES ) >"$O/pg-libpq.log" 2>&1
if [ "$T" = win ]; then
# (4) Localize libpq's bundled pthread-win32 symbols (ABI-incompatible with winpthreads).
local LIBPQ="$BLD/src/interfaces/libpq/libpq.a" TMP
TMP=$(mktemp -d)
( cd "$TMP" && cp "$LIBPQ" . && "$AR" x libpq.a && \
printf 'pthread_self pg_pthread_self\npthread_setspecific pg_pthread_setspecific\npthread_getspecific pg_pthread_getspecific\npthread_mutex_init pg_pthread_mutex_init\npthread_mutex_lock pg_pthread_mutex_lock\npthread_mutex_unlock pg_pthread_mutex_unlock\n' > s.txt && \
for o in *.o; do "${OBJCOPY:-objcopy}" --redefine-syms=s.txt "$o" "$o.n" && mv "$o.n" "$o"; done && \
rm -f "$LIBPQ" && "$AR" rcs "$LIBPQ" *.o )
rm -rf "$TMP"
echo " [patch] libpq.a pthread-win32 symbols localized (pg_pthread_*)"
fi
echo " [lib] postgres: libpq.a + libpgcommon_shlib.a + libpgport_shlib.a + fe_memutils.o ($T)"
}
# ---- libarchive (confidence: high) ----
# build_libarchive -- libarchive.a with all 5 codecs pinned to the just-built $O artifacts. mac = XAR
# via pinned OpenSSL (MD5/SHA1, BSD-ar repack) + libxml2 + iconv-from-libSystem (empty ICONV dir);
# win = codecs only (Windows xar auto-uses system XmlLite + CNG/bcrypt at the final link).
build_libarchive() {
if [ "$T" = win ]; then
rm -rf "$O/libarchive"
cmake -S vendor/libarchive -B "$O/libarchive" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_ICONV=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LIBXML2=OFF -DENABLE_EXPAT=OFF -DENABLE_OPENSSL=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-cfg.log" 2>&1
cmake --build "$O/libarchive" --target archive_static -j >"$O/la-build.log" 2>&1
echo " [lib] libarchive.a (codecs only; win xar via XmlLite/CNG) ($T)"
else
# macOS: libarchive gates libxml2 (hence xar) on a working iconv, but zig's SDK-less libSystem
# stub exports no iconv symbols, so the detection link-test fails and xar silently stubs out.
# With an Apple SDK, feed CMake the SDK's iconv.h plus the minimal link stub
# (tools/macStubs/libiconv.tbd, which resolves to the real /usr/lib/libiconv at runtime) so
# iconv is detected and xar is really built; without one, keep the empty-dir path (the build
# still succeeds, but xar ends up stubbed). musl uses crossArchive.sh, not this script.
local MACSDK ICONVFLAGS
MACSDK="${CALOG_MACSDK:-/home/scott/macos-sdk/MacOSX13.3.sdk}"
rm -rf "$O/libarchive-xar"
if [ -f "$MACSDK/usr/include/iconv.h" ]; then
mkdir -p "$O/iconvinc"
cp "$MACSDK/usr/include/iconv.h" "$O/iconvinc/iconv.h"
ICONVFLAGS="-DICONV_INCLUDE_DIR=$O/iconvinc -DLIBICONV_PATH=$R/tools/macStubs/libiconv.tbd"
echo " [sdk] mac libarchive: iconv via SDK ($MACSDK) -> xar ENABLED"
else
mkdir -p "$O/noinc"
ICONVFLAGS="-DICONV_INCLUDE_DIR=$O/noinc"
echo " [sdk] mac libarchive: no Apple SDK at $MACSDK -> xar will be STUBBED (set CALOG_MACSDK)"
fi
cmake -S vendor/libarchive -B "$O/libarchive-xar" -DCMAKE_TOOLCHAIN_FILE="$TC" \
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DENABLE_TEST=OFF -DENABLE_INSTALL=OFF \
-DENABLE_TAR=OFF -DENABLE_CPIO=OFF -DENABLE_CAT=OFF -DENABLE_UNZIP=OFF \
-DENABLE_ACL=OFF -DENABLE_XATTR=OFF -DENABLE_LIBB2=OFF \
-DENABLE_LZO=OFF -DENABLE_NETTLE=OFF -DENABLE_MBEDTLS=OFF -DENABLE_PCREPOSIX=OFF -DENABLE_PCRE2POSIX=OFF \
-DENABLE_EXPAT=OFF -DPOSIX_REGEX_LIB=NONE \
-DENABLE_ICONV=ON $ICONVFLAGS \
-DENABLE_LIBXML2=ON "-DLIBXML2_INCLUDE_DIR=$R/vendor/libxml2/include;$O/libxml2" -DLIBXML2_LIBRARY="$O/libxml2/libxml2.a" \
-DENABLE_OPENSSL=ON -DOPENSSL_ROOT_DIR="$O/openssl-src" -DOPENSSL_INCLUDE_DIR="$O/openssl-src/include" \
-DOPENSSL_SSL_LIBRARY="$O/openssl-repack/libssl.a" -DOPENSSL_CRYPTO_LIBRARY="$O/openssl-repack/libcrypto.a" \
-DENABLE_ZLIB=ON -DZLIB_INCLUDE_DIR="$R/vendor/zlib" -DZLIB_LIBRARY="$O/lib/libz.a" \
-DENABLE_BZip2=ON -DBZIP2_INCLUDE_DIR="$R/vendor/bzip2" -DBZIP2_LIBRARIES="$O/lib/libbz2.a" \
-DENABLE_LZMA=ON -DLIBLZMA_INCLUDE_DIR="$R/vendor/xz/src/liblzma/api" -DLIBLZMA_LIBRARY="$O/xz/liblzma.a" \
-DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR="$R/vendor/zstd" -DZSTD_LIBRARY="$O/lib/libzstd.a" \
-DENABLE_LZ4=ON -DLZ4_INCLUDE_DIR="$R/vendor/lz4" -DLZ4_LIBRARY="$O/lib/liblz4.a" \
>"$O/la-xar-cfg.log" 2>&1
cmake --build "$O/libarchive-xar" --target archive_static -j >"$O/la-xar-build.log" 2>&1
echo " [lib] libarchive.a (+xar via libxml2/openssl) ($T)"
fi
}
# ===================================================================================================
# Dispatch: build the requested deps (or all, in dependency order).
# ===================================================================================================
ALL="codecs xz openssl libxml2 pcre2 libssh2 mariadb postgres tcl mruby winpthreads libarchive"
DEPS=${*:-$ALL}
# Tolerant dispatch: build every requested dep, collect failures, and report at the end (so one
# broken dep -- e.g. tcl when vendor/tcl lacks win//macosx/ -- does not abort the rest). The `if`
# scopes off `set -e` for the build call; `set -e` still applies INSIDE each build function.
FAILED=""
for d in $DEPS; do
echo "==================== build $d ($T) ===================="
if "build_$d"; then :; else FAILED="$FAILED $d"; fi
done
if [ -n "$FAILED" ]; then
echo "== crossDeps: FAILED:$FAILED (other deps built OK) =="
exit 1
fi
echo "== crossDeps done: $DEPS =="