From 329dadf58460f8dfc4a831acbf76a75147c2e853 Mon Sep 17 00:00:00 2001 From: Faisal N Jawdat Date: Mon, 14 Sep 2026 15:15:06 +0100 Subject: [PATCH] Support a universal (x86_64+arm64) build of libgit2's static deps Add a UNIVERSAL_BUILD path to script/bootstrap and script/update_libgit2 that lipo's the two architectures' libssh2.a/libcrypto.a/libgit2.a into single universal static libraries, using a native arm64 Homebrew at /opt/homebrew and a Rosetta x86_64 Homebrew at /usr/local. The existing single-arch path (used by PR/test builds) is unchanged. Part of fixing gitx/gitx#279 (Sparkle auto-update), which needs a single release asset per version instead of separate x86_64/arm64 dmgs. --- script/bootstrap | 110 +++++++++++++++++++++++++++++++---------- script/update_libgit2 | 112 ++++++++++++++++++++++++++++++------------ 2 files changed, 164 insertions(+), 58 deletions(-) diff --git a/script/bootstrap b/script/bootstrap index b1bb42a7..0eb0c999 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -25,7 +25,11 @@ main() { if [ -n "$REQUIRED_TOOLS" ]; then echo "** Checking dependencies in Homebrew ..." - check_deps + if [ -n "$UNIVERSAL_BUILD" ]; then + check_deps_universal + else + check_deps + fi fi } @@ -60,27 +64,8 @@ check_deps() { export expected_prefix=/usr/local fi - # Ensure that we have libgit2's dependencies installed. - installed=$(brew list) - - for tool in $REQUIRED_TOOLS; do - # Skip packages that are already installed. - echo "$installed" | grep -q "$tool" && code=$? || code=$? - - if [ "$code" -eq "0" ]; then - echo "*** $tool is available 👍" - continue - elif [ "$code" -ne "1" ]; then - exit $code - fi - - echo "*** Installing $tool with Homebrew..." - brew install "$tool" - done - - install_path="./External" - echo "*** copy "$brew_prefix"/lib/libssh2.a to $install_path" - cp "$brew_prefix"/lib/libssh2.a $install_path + install_tools "$brew_prefix" + stage_deps "$brew_prefix" "./External" if [ "$brew_prefix" != "$expected_prefix" ]; then echo "*** Adding soft links into $expected_prefix..." @@ -97,12 +82,85 @@ check_deps() { sudo ln -s "$brew_prefix/$product" "$destination" done fi +} + +# Builds a universal libcrypto.a/libssh2.a by lipo-ing together the arm64 +# Homebrew install at /opt/homebrew with an x86_64 (Rosetta) Homebrew install +# at /usr/local. Both installs, and the tools under each, are expected to +# already exist -- setting them up is the CI workflow's job, not this +# script's, since it means bootstrapping a second Homebrew prefix. +check_deps_universal() { + local arm64_prefix=/opt/homebrew + local x86_64_prefix=/usr/local + + for prefix in "$arm64_prefix" "$x86_64_prefix"; do + if [ ! -x "$prefix/bin/brew" ]; then + echo "UNIVERSAL_BUILD requires a Homebrew install at $prefix; none found." >&2 + exit 1 + fi + done + + install_tools "$arm64_prefix" + install_tools "$x86_64_prefix" arch -x86_64 + + local staging="./External/universal-deps" + rm -rf "$staging" + mkdir -p "$staging/arm64" "$staging/x86_64" + + stage_deps "$arm64_prefix" "$staging/arm64" + stage_deps "$x86_64_prefix" "$staging/x86_64" + + echo "*** Combining arm64 + x86_64 libssh2.a and libcrypto.a into universal libraries" + for lib in libssh2.a libcrypto.a; do + lipo -create \ + "$staging/arm64/$lib" \ + "$staging/x86_64/$lib" \ + -output "./External/$lib" + done + + rm -rf "$staging" + ls -la ./External +} + +# install_tools [command prefix to run brew under, e.g. `arch -x86_64`] +install_tools() { + local prefix="$1" + shift + local run=("$@" "$prefix/bin/brew") + + local installed + installed=$("${run[@]}" list) + + for tool in $REQUIRED_TOOLS; do + echo "$installed" | grep -q "$tool" && code=$? || code=$? + + if [ "$code" -eq "0" ]; then + echo "*** $tool is available under $prefix 👍" + continue + elif [ "$code" -ne "1" ]; then + exit $code + fi + + echo "*** Installing $tool under $prefix with Homebrew..." + "${run[@]}" install "$tool" + done +} + +# stage_deps - copies that prefix's +# libssh2.a/libcrypto.a into destination, for either direct use (single-arch) +# or as lipo input (universal). +stage_deps() { + local prefix="$1" + local destination="$2" + + echo "*** copy $prefix/lib/libssh2.a to $destination" + cp "$prefix/lib/libssh2.a" "$destination" # openssl@1 is expected - sslSource=$(find $brew_prefix -name libcrypto.a | grep openssl | sort | head -1 | xargs dirname) - echo "Find libcrypto.a and take first $sslSource and copy to $install_path" - cp $sslSource/* $install_path 2>/dev/null | echo "Copy and ignore subdirectory. This makes build work on x86 and arm64" - ls -la $install_path + local sslSource + sslSource=$(find "$prefix" -name libcrypto.a | grep openssl | sort | head -1 | xargs dirname) + echo "Find libcrypto.a and take first $sslSource and copy to $destination" + cp $sslSource/* "$destination" 2>/dev/null | echo "Copy and ignore subdirectory. This makes build work on x86 and arm64" } main diff --git a/script/update_libgit2 b/script/update_libgit2 index 3152c446..a33f296e 100755 --- a/script/update_libgit2 +++ b/script/update_libgit2 @@ -4,39 +4,87 @@ set -e cd "External/libgit2" -if [ -d "build" ]; then - rm -rf "build" -fi +build_arch() { + arch_name="$1" + arch_prefix="$2" + runner="$3" # e.g. `arch -x86_64`, or empty for native + + build_dir="build-$arch_name" + rm -rf "$build_dir" + mkdir "$build_dir" + cd "$build_dir" + + export PKG_CONFIG_PATH="$arch_prefix/opt/openssl/lib/pkgconfig:$arch_prefix/lib/pkgconfig" + + # augment path to help it find cmake installed in /usr/local/bin, + # e.g. via brew. Xcode's Run Script phase doesn't seem to honor + # ~/.MacOSX/environment.plist + PATH="$arch_prefix/bin:$PATH" + + $runner cmake --version + $runner cmake -DBUILD_SHARED_LIBS:BOOL=OFF \ + -DBUILD_CLAR:BOOL=OFF \ + -DTHREADSAFE:BOOL=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_OSX_ARCHITECTURES="$arch_name" \ + .. + $runner cmake --build . + + cd .. +} + +if [ -n "$UNIVERSAL_BUILD" ]; then + # Requires a native arm64 Homebrew at /opt/homebrew and an x86_64 (Rosetta) + # Homebrew at /usr/local, each with openssl installed -- see script/bootstrap. + build_arch arm64 /opt/homebrew "" + build_arch x86_64 /usr/local "arch -x86_64" -mkdir build -cd build + product="libgit2.a" + install_path="../../${product}" + rm -rf "$install_path" + lipo -create \ + "build-arm64/${product}" \ + "build-x86_64/${product}" \ + -output "$install_path" -# OpenSSL is keg-only, so add its pkgconfig location manually -if [[ $(uname -m) == 'arm64' ]]; then - echo "Running on a Apple Silicon M1" - export ARCH_PREFIX=/opt/homebrew + rm -rf build-arm64 build-x86_64 + + echo "Universal libgit2 has been updated." else - echo "Running on a Apple x86" - export ARCH_PREFIX=/usr/local + if [ -d "build" ]; then + rm -rf "build" + fi + + mkdir build + cd build + + # OpenSSL is keg-only, so add its pkgconfig location manually + if [[ $(uname -m) == 'arm64' ]]; then + echo "Running on a Apple Silicon M1" + export ARCH_PREFIX=/opt/homebrew + else + echo "Running on a Apple x86" + export ARCH_PREFIX=/usr/local + fi + export PKG_CONFIG_PATH=$ARCH_PREFIX/opt/openssl/lib/pkgconfig:$ARCH_PREFIX/lib/pkgconfig + + # augment path to help it find cmake installed in /usr/local/bin, + # e.g. via brew. Xcode's Run Script phase doesn't seem to honor + # ~/.MacOSX/environment.plist + PATH="$ARCH_PREFIX/bin:$PATH" + + cmake --version + cmake -DBUILD_SHARED_LIBS:BOOL=OFF \ + -DBUILD_CLAR:BOOL=OFF \ + -DTHREADSAFE:BOOL=ON \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + .. + cmake --build . + + product="libgit2.a" + install_path="../../${product}" + rm -rf $install_path + cp -v "${product}" "${install_path}" + + echo "libgit2 has been updated." fi -export PKG_CONFIG_PATH=$ARCH_PREFIX/opt/openssl/lib/pkgconfig:$ARCH_PREFIX/lib/pkgconfig - -# augment path to help it find cmake installed in /usr/local/bin, -# e.g. via brew. Xcode's Run Script phase doesn't seem to honor -# ~/.MacOSX/environment.plist -PATH="$ARCH_PREFIX/bin:$PATH" - -cmake --version -cmake -DBUILD_SHARED_LIBS:BOOL=OFF \ - -DBUILD_CLAR:BOOL=OFF \ - -DTHREADSAFE:BOOL=ON \ - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - .. -cmake --build . - -product="libgit2.a" -install_path="../../${product}" -rm -rf $install_path -cp -v "${product}" "${install_path}" - -echo "libgit2 has been updated."