#!/usr/bin/env bash set -e # Parameterize PYTHON_VERSION=3.9.7 BUILDDIR=/tmp/electrum-build PACKAGE=Electrum GIT_REPO=https://github.com/spesmilo/electrum export GCC_STRIP_BINARIES="1" export PYTHONDONTWRITEBYTECODE=1 # don't create __pycache__/ folders with .pyc files . "$(dirname "$0")/../build_tools_util.sh" CONTRIB_OSX="$(dirname "$(realpath "$0")")" CONTRIB="$CONTRIB_OSX/.." PROJECT_ROOT="$CONTRIB/.." CACHEDIR="$CONTRIB_OSX/.cache" mkdir -p "$CACHEDIR" cd "$PROJECT_ROOT" which brew > /dev/null 2>&1 || fail "Please install brew from https://brew.sh/ to continue" which xcodebuild > /dev/null 2>&1 || fail "Please install xcode command line tools to continue" # Code Signing: See https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html if [ -n "$CODESIGN_CERT" ]; then # Test the identity is valid for signing by doing this hack. There is no other way to do this. cp -f /bin/ls ./CODESIGN_TEST set +e codesign -s "$CODESIGN_CERT" --dryrun -f ./CODESIGN_TEST > /dev/null 2>&1 res=$? set -e rm -f ./CODESIGN_TEST if ((res)); then fail "Code signing identity \"$CODESIGN_CERT\" appears to be invalid." fi unset res info "Code signing enabled using identity \"$CODESIGN_CERT\"" else warn "Code signing DISABLED. Specify a valid macOS Developer identity installed on the system to enable signing." fi function DoCodeSignMaybe { # ARGS: infoName fileOrDirName infoName="$1" file="$2" deep="" if [ -z "$CODESIGN_CERT" ]; then # no cert -> we won't codesign return fi if [ -d "$file" ]; then deep="--deep" fi if [ -z "$infoName" ] || [ -z "$file" ] || [ ! -e "$file" ]; then fail "Argument error to internal function DoCodeSignMaybe()" fi hardened_arg="--entitlements=${CONTRIB_OSX}/entitlements.plist -o runtime" info "Code signing ${infoName}..." codesign -f -v $deep -s "$CODESIGN_CERT" $hardened_arg "$file" || fail "Could not code sign ${infoName}" } info "Installing Python $PYTHON_VERSION" PKG_FILE="python-${PYTHON_VERSION}-macosx10.9.pkg" if [ ! -f "$CACHEDIR/$PKG_FILE" ]; then curl -o "$CACHEDIR/$PKG_FILE" "https://www.python.org/ftp/python/${PYTHON_VERSION}/$PKG_FILE" fi echo "f40f7407d20f88a6a64678b6586853de16cc5d7d07c82a8a2a7e43bba59dbae5 $CACHEDIR/$PKG_FILE" | shasum -a 256 -c \ || fail "python pkg checksum mismatched" sudo installer -pkg "$CACHEDIR/$PKG_FILE" -target / \ || fail "failed to install python" # sanity check "python3" has the version we just installed. FOUND_PY_VERSION=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') if [[ "$FOUND_PY_VERSION" != "$PYTHON_VERSION" ]]; then fail "python version mismatch: $FOUND_PY_VERSION != $PYTHON_VERSION" fi break_legacy_easy_install # create a fresh virtualenv # This helps to avoid older versions of pip-installed dependencies interfering with the build. VENV_DIR="$CONTRIB_OSX/build-venv" rm -rf "$VENV_DIR" python3 -m venv $VENV_DIR source $VENV_DIR/bin/activate info "Installing build dependencies" python3 -m pip install --no-dependencies --no-warn-script-location -Ir ./contrib/deterministic-build/requirements-build-mac.txt \ || fail "Could not install build dependencies" info "Using these versions for building $PACKAGE:" sw_vers python3 --version echo -n "Pyinstaller " pyinstaller --version rm -rf ./dist git submodule update --init rm -rf "$BUILDDIR" > /dev/null 2>&1 mkdir "$BUILDDIR" info "generating locale" ( if ! which msgfmt > /dev/null 2>&1; then brew install gettext brew link --force gettext fi cd "$CONTRIB"/deterministic-build/electrum-locale # we want the binary to have only compiled (.mo) locale files; not source (.po) files rm -rf "$PROJECT_ROOT/electrum/locale/" for i in ./locale/*; do dir="$PROJECT_ROOT/electrum/$i/LC_MESSAGES" mkdir -p "$dir" msgfmt --output-file="$dir/electrum.mo" "$i/electrum.po" || true done ) || fail "failed generating locale" info "Installing some build-time deps for compilation..." brew install autoconf automake libtool gettext coreutils pkgconfig if [ ! -f "$PROJECT_ROOT"/electrum/libsecp256k1.0.dylib ]; then info "Building libsecp256k1 dylib..." "$CONTRIB"/make_libsecp256k1.sh || fail "Could not build libsecp" else info "Skipping libsecp256k1 build: reusing already built dylib." fi cp "$PROJECT_ROOT"/electrum/libsecp256k1.0.dylib "$CONTRIB"/osx if [ ! -f "$PROJECT_ROOT"/electrum/libzbar.0.dylib ]; then info "Building ZBar dylib..." "$CONTRIB"/make_zbar.sh || fail "Could not build ZBar dylib" else info "Skipping ZBar build: reusing already built dylib." fi cp "$PROJECT_ROOT"/electrum/libzbar.0.dylib "$CONTRIB"/osx if [ ! -f "$PROJECT_ROOT"/electrum/libusb-1.0.dylib ]; then info "Building libusb dylib..." "$CONTRIB"/make_libusb.sh || fail "Could not build libusb dylib" else info "Skipping libusb build: reusing already built dylib." fi cp "$PROJECT_ROOT"/electrum/libusb-1.0.dylib "$CONTRIB"/osx info "Installing requirements..." python3 -m pip install --no-dependencies --no-warn-script-location -Ir ./contrib/deterministic-build/requirements.txt \ || fail "Could not install requirements" info "Installing hardware wallet requirements..." python3 -m pip install --no-dependencies --no-warn-script-location -Ir ./contrib/deterministic-build/requirements-hw.txt \ || fail "Could not install hardware wallet requirements" info "Installing dependencies specific to binaries..." python3 -m pip install --no-dependencies --no-warn-script-location -Ir ./contrib/deterministic-build/requirements-binaries-mac.txt \ || fail "Could not install dependencies specific to binaries" info "Building $PACKAGE..." python3 -m pip install --no-dependencies --no-warn-script-location . > /dev/null || fail "Could not build $PACKAGE" info "Faking timestamps..." find . -exec touch -t '200101220000' {} + || true VERSION=`git describe --tags --dirty --always` info "Building binary" pyinstaller --noconfirm --ascii --clean --name $VERSION contrib/osx/osx.spec || fail "Could not build binary" info "Adding bitcoin URI types to Info.plist" plutil -insert 'CFBundleURLTypes' \ -xml ' CFBundleURLName bitcoin CFBundleURLSchemes bitcoinlightning ' \ -- dist/$PACKAGE.app/Contents/Info.plist \ || fail "Could not add keys to Info.plist. Make sure the program 'plutil' exists and is installed." DoCodeSignMaybe "app bundle" "dist/${PACKAGE}.app" if [ ! -z "$CODESIGN_CERT" ]; then if [ ! -z "$APPLE_ID_USER" ]; then info "Notarizing .app with Apple's central server..." "${CONTRIB_OSX}/notarize_app.sh" "dist/${PACKAGE}.app" || fail "Could not notarize binary." else warn "AppleID details not set! Skipping Apple notarization." fi fi info "Creating .DMG" hdiutil create -fs HFS+ -volname $PACKAGE -srcfolder dist/$PACKAGE.app dist/electrum-$VERSION.dmg || fail "Could not create .DMG" DoCodeSignMaybe ".DMG" "dist/electrum-${VERSION}.dmg" if [ -z "$CODESIGN_CERT" ]; then warn "App was built successfully but was not code signed. Users may get security warnings from macOS." warn "Specify a valid code signing identity to enable code signing." fi