diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 01f040249..fe4415c1f 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -euo pipefail +# shellcheck source=lib/macos.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/macos.sh" + # Build script for creating architecture-specific releases # Usage: ./build-release.sh [arm64|x86_64|both] @@ -15,171 +18,6 @@ NOTARIZE="${NOTARIZE:-false}" echo "🏗️ Building TablePro for: $ARCH" -# Ensure libmariadb.a has correct architecture -prepare_mariadb() { - local target_arch=$1 - echo "📦 Preparing libmariadb.a for $target_arch..." - - # If libmariadb.a already exists with the correct architecture, skip preparation. - # CI pre-copies the architecture-specific slice from Libs via scripts/ci/prepare-libs.sh. - # Homebrew is not involved: the whole library is vendored by download-libs.sh. - if [ -f "Libs/libmariadb.a" ] && lipo -info "Libs/libmariadb.a" 2>/dev/null | grep -q "$target_arch"; then - local size - size=$(ls -lh Libs/libmariadb.a 2>/dev/null | awk '{print $5}') - echo "✅ libmariadb.a already present for $target_arch ($size), skipping" - return 0 - fi - - # Change to Libs directory - cd Libs || { - echo "❌ FATAL: Cannot access Libs directory" - exit 1 - } - - # Check if universal library exists - if [ ! -f "libmariadb_universal.a" ]; then - echo "❌ ERROR: libmariadb_universal.a not found!" - echo "Run this first to create universal library:" - echo " lipo -create libmariadb_arm64.a libmariadb_x86_64.a -output libmariadb_universal.a" - cd - > /dev/null - exit 1 - fi - - # Extract thin slice for target architecture - if ! lipo libmariadb_universal.a -thin "$target_arch" -output libmariadb.a; then - echo "❌ FATAL: Failed to extract $target_arch slice from universal library" - echo "Ensure the universal library contains $target_arch architecture" - cd - > /dev/null - exit 1 - fi - - # Verify the output file was created - if [ ! -f "libmariadb.a" ]; then - echo "❌ FATAL: libmariadb.a was not created successfully" - cd - > /dev/null - exit 1 - fi - - # Get and display size - local size - size=$(ls -lh libmariadb.a 2>/dev/null | awk '{print $5}') - if [ -z "$size" ]; then - size="unknown" - fi - - echo "✅ libmariadb.a is now $target_arch-only ($size)" - - cd - > /dev/null || exit 1 -} - -# Ensure libpq + OpenSSL static libraries have correct architecture -prepare_libpq() { - local target_arch=$1 - echo "📦 Preparing libpq + OpenSSL static libraries for $target_arch..." - - local all_ok=1 - for lib in libpq libpgcommon libpgport libssl libcrypto; do - # If already present with the correct architecture, skip - if [ -f "Libs/${lib}.a" ] && lipo -info "Libs/${lib}.a" 2>/dev/null | grep -q "$target_arch"; then - continue - fi - - if [ ! -f "Libs/${lib}_universal.a" ]; then - echo "❌ ERROR: Libs/${lib}_universal.a not found!" - echo "Run this first: ./scripts/build-libpq.sh both" - all_ok=0 - continue - fi - - if ! lipo "Libs/${lib}_universal.a" -thin "$target_arch" -output "Libs/${lib}.a"; then - echo "❌ FATAL: Failed to extract $target_arch slice from ${lib}_universal.a" - exit 1 - fi - done - - if [ "$all_ok" -eq 0 ]; then - exit 1 - fi - - echo "✅ libpq + OpenSSL libraries ready for $target_arch" -} - -prepare_libmongoc() { - local target_arch=$1 - echo "📦 Preparing libmongoc + libbson static libraries for $target_arch..." - - local all_ok=1 - for lib in libmongoc libbson; do - # If already present with the correct architecture, skip - if [ -f "Libs/${lib}.a" ] && lipo -info "Libs/${lib}.a" 2>/dev/null | grep -q "$target_arch"; then - continue - fi - - # Try arch-specific file first (libmongoc_arm64.a) - if [ -f "Libs/${lib}_${target_arch}.a" ]; then - cp "Libs/${lib}_${target_arch}.a" "Libs/${lib}.a" - continue - fi - - # Fall back to universal - if [ ! -f "Libs/${lib}_universal.a" ]; then - echo "❌ ERROR: Libs/${lib}_${target_arch}.a and Libs/${lib}_universal.a not found!" - echo "Run this first: ./scripts/build-libmongoc.sh both" - all_ok=0 - continue - fi - - if ! lipo "Libs/${lib}_universal.a" -thin "$target_arch" -output "Libs/${lib}.a"; then - echo "❌ FATAL: Failed to extract $target_arch slice from ${lib}_universal.a" - exit 1 - fi - done - - if [ "$all_ok" -eq 0 ]; then - exit 1 - fi - - echo "✅ libmongoc + libbson libraries ready for $target_arch" -} - -prepare_hiredis() { - local target_arch=$1 - echo "📦 Preparing hiredis static libraries for $target_arch..." - - local all_ok=1 - for lib in libhiredis libhiredis_ssl; do - # If already present with the correct architecture, skip - if [ -f "Libs/${lib}.a" ] && lipo -info "Libs/${lib}.a" 2>/dev/null | grep -q "$target_arch"; then - continue - fi - - # Try arch-specific file first - if [ -f "Libs/${lib}_${target_arch}.a" ]; then - cp "Libs/${lib}_${target_arch}.a" "Libs/${lib}.a" - continue - fi - - # Fall back to universal - if [ ! -f "Libs/${lib}_universal.a" ]; then - echo "❌ ERROR: Libs/${lib}_${target_arch}.a and Libs/${lib}_universal.a not found!" - echo "Run this first: ./scripts/build-hiredis.sh both" - all_ok=0 - continue - fi - - if ! lipo "Libs/${lib}_universal.a" -thin "$target_arch" -output "Libs/${lib}.a"; then - echo "❌ FATAL: Failed to extract $target_arch slice from ${lib}_universal.a" - exit 1 - fi - done - - if [ "$all_ok" -eq 0 ]; then - exit 1 - fi - - echo "✅ hiredis libraries ready for $target_arch" -} - # Bundle non-system dynamic libraries into the app bundle # so the app runs without Homebrew on end-user machines. bundle_dylibs() { @@ -312,10 +150,8 @@ build_for_arch() { echo "🔨 Building for $arch..." # Prepare architecture-specific libraries - prepare_mariadb "$arch" - prepare_libpq "$arch" - prepare_libmongoc "$arch" - prepare_hiredis "$arch" + echo "📦 Preparing static libraries for $arch..." + prepare_arch_libs "$arch" libmariadb libpq libpgcommon libpgport libssl libcrypto libmongoc libbson libhiredis libhiredis_ssl # Create OpenSSL shared dylibs for this architecture echo "📦 Creating OpenSSL shared dylibs for $arch..." diff --git a/scripts/ci/prepare-libs.sh b/scripts/ci/prepare-libs.sh index 6cc91d570..29d5ca64f 100755 --- a/scripts/ci/prepare-libs.sh +++ b/scripts/ci/prepare-libs.sh @@ -1,32 +1,26 @@ #!/usr/bin/env bash set -euo pipefail -ARCH="${1:-}" +# Selects the per-architecture slice of every vendored static library, so an Xcode build links the +# right one. Called by the release workflow before scripts/build-release.sh. +# +# The selection lives in scripts/lib/macos.sh and is shared with build-release.sh, which used to +# carry four near-identical copies of it. This script did a bare `cp` of the per-architecture file +# with no fallback, so the two took different sources for the same library. + +# shellcheck source=../lib/macos.sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/lib/macos.sh" +ARCH="${1:-}" if [[ "$ARCH" != "arm64" && "$ARCH" != "x86_64" ]]; then - echo "Usage: $0 " - exit 1 + echo "Usage: $0 " >&2 + exit 1 fi -# Prepare libmariadb -echo "📦 Preparing libmariadb.a for $ARCH..." -cp "Libs/libmariadb_${ARCH}.a" "Libs/libmariadb.a" -echo "✅ libmariadb.a ready" -lipo -info Libs/libmariadb.a -ls -lh Libs/libmariadb.a - -# Prepare libpq + OpenSSL -echo "📦 Preparing libpq + OpenSSL static libraries for $ARCH..." -for lib in libpq libpgcommon libpgport libssl libcrypto; do - cp "Libs/${lib}_${ARCH}.a" "Libs/${lib}.a" -done -echo "✅ libpq + OpenSSL libraries ready" -ls -lh Libs/lib{pq,pgcommon,pgport,ssl,crypto}.a - -# Prepare hiredis -echo "📦 Preparing hiredis static libraries for $ARCH..." -for lib in libhiredis libhiredis_ssl; do - cp "Libs/${lib}_${ARCH}.a" "Libs/${lib}.a" -done -echo "✅ hiredis libraries ready" -ls -lh Libs/lib{hiredis,hiredis_ssl}.a +echo "📦 Preparing static libraries for $ARCH..." +prepare_arch_libs "$ARCH" \ + libmariadb \ + libpq libpgcommon libpgport libssl libcrypto \ + libmongoc libbson \ + libhiredis libhiredis_ssl +echo "✅ Static libraries ready for $ARCH" diff --git a/scripts/lib/macos.sh b/scripts/lib/macos.sh index 98aef86db..64986ec2b 100644 --- a/scripts/lib/macos.sh +++ b/scripts/lib/macos.sh @@ -127,3 +127,53 @@ build_openssl() { echo "✅ OpenSSL $arch: $(du -h "$prefix/lib/libssl.a" | cut -f1) (libssl) $(du -h "$prefix/lib/libcrypto.a" | cut -f1) (libcrypto)" } + +# Puts the named libraries in Libs/.a at the requested architecture, which is what +# project.yml links. +# +# Three sources, in order: a slice that is already correct, a per-architecture file, or a thinned +# universal. This existed in five places that disagreed. build-release.sh carried four +# near-identical functions, 159 lines between them, one of which cd'd into Libs and one of which +# only ever thinned the universal; scripts/ci/prepare-libs.sh did a bare `cp` of the per-arch file +# with no fallback at all, so the two took different sources for the same library. +# The architectures in a Mach-O archive, space separated, or empty if it is not there. +current_arch() { + [ -f "$1" ] || return 0 + lipo -info "$1" 2> /dev/null | sed 's/.*: //' | tr -s ' ' | sed 's/ $//' +} + +prepare_arch_libs() { + local target_arch="$1" + shift + local lib unresolved=0 + + for lib in "$@"; do + # An exact match, not "contains". `lipo -info | grep -q arm64` also matches a universal, + # so the substring form left fat libraries in place and the release linked those instead + # of the slice it asked for. + if [ "$(current_arch "$LIBS_DIR/${lib}.a")" = "$target_arch" ]; then + continue + fi + + if [ -f "$LIBS_DIR/${lib}_${target_arch}.a" ]; then + cp "$LIBS_DIR/${lib}_${target_arch}.a" "$LIBS_DIR/${lib}.a" + continue + fi + + if [ ! -f "$LIBS_DIR/${lib}_universal.a" ]; then + echo "ERROR: neither $LIBS_DIR/${lib}_${target_arch}.a nor ${lib}_universal.a exists" >&2 + unresolved=1 + continue + fi + + if ! lipo "$LIBS_DIR/${lib}_universal.a" -thin "$target_arch" -output "$LIBS_DIR/${lib}.a"; then + echo "ERROR: could not extract the $target_arch slice from ${lib}_universal.a" >&2 + unresolved=1 + fi + done + + if [ "$unresolved" -ne 0 ]; then + echo " run scripts/download-libs.sh, or rebuild the library it names" >&2 + return 1 + fi +}