#!/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//. 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 [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 [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() { # 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 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" <> "$TC" fi echo "== crossDeps: target=$T triple=$TRIPLE zig=$($ZIG version) ==" echo " wrappers + $TC regenerated" # =================================================================================================== # Per-dependency build functions. Each builds vendor/ 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//{lib,obj} object listings. local c mkdir -p "$O/obj" "$O/lib" || return 1 # zlib: -DHAVE_UNISTD_H so it includes 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 -B . 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//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//lib/libmruby.a; we copy that to $O/mruby/. # The generated include tree (mrbconf.h + mruby/presym.h) stays at # vendor/mruby/build//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" <.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 =="