note: newer versions of pyinstaller (4.4+ ?) want to sign the bundle themselves, in which case modifying the Info.plist file after pyinstaller returns invalidates the sig.
242 lines
8.9 KiB
Bash
Executable File
242 lines
8.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -e
|
|
|
|
# Parameterize
|
|
PYTHON_VERSION=3.9.11
|
|
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 "c2073d44c404c661dadbf0cbda55c6e7d681baba9178ed1bdb126d34caa898a9 $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
|
|
|
|
# don't add debug info to compiled C files (e.g. when pip calls setuptools/wheel calls gcc)
|
|
# see https://github.com/pypa/pip/issues/6505#issuecomment-526613584
|
|
export CFLAGS="-g0"
|
|
|
|
info "Installing build dependencies"
|
|
python3 -m pip install --no-build-isolation --no-dependencies --no-warn-script-location \
|
|
-Ir ./contrib/deterministic-build/requirements-build-base.txt \
|
|
|| fail "Could not install build dependencies (base)"
|
|
python3 -m pip install --no-build-isolation --no-dependencies --no-warn-script-location \
|
|
-Ir ./contrib/deterministic-build/requirements-build-mac.txt \
|
|
|| fail "Could not install build dependencies (mac)"
|
|
|
|
info "Installing some build-time deps for compilation..."
|
|
brew install autoconf automake libtool gettext coreutils pkgconfig
|
|
|
|
info "Building PyInstaller."
|
|
PYINSTALLER_REPO="https://github.com/pyinstaller/pyinstaller.git"
|
|
PYINSTALLER_COMMIT="40c9abce2d8de879e414fd377c933dccaab1e156"
|
|
# ^ tag "4.2"
|
|
# TODO test newer versions of pyinstaller for build-reproducibility.
|
|
# we are using this version for now due to change in code-signing behaviour
|
|
# (https://github.com/pyinstaller/pyinstaller/pull/5581)
|
|
(
|
|
if [ -f "$CACHEDIR/pyinstaller/PyInstaller/bootloader/Darwin-64bit/runw" ]; then
|
|
info "pyinstaller already built, skipping"
|
|
exit 0
|
|
fi
|
|
cd "$PROJECT_ROOT"
|
|
ELECTRUM_COMMIT_HASH=$(git rev-parse HEAD)
|
|
cd "$CACHEDIR"
|
|
rm -rf pyinstaller
|
|
mkdir pyinstaller
|
|
cd pyinstaller
|
|
# Shallow clone
|
|
git init
|
|
git remote add origin $PYINSTALLER_REPO
|
|
git fetch --depth 1 origin $PYINSTALLER_COMMIT
|
|
git checkout -b pinned "${PYINSTALLER_COMMIT}^{commit}"
|
|
rm -fv PyInstaller/bootloader/Darwin-*/run* || true
|
|
# add reproducible randomness. this ensures we build a different bootloader for each commit.
|
|
# if we built the same one for all releases, that might also get anti-virus false positives
|
|
echo "const char *electrum_tag = \"tagged by Electrum@$ELECTRUM_COMMIT_HASH\";" >> ./bootloader/src/pyi_main.c
|
|
pushd bootloader
|
|
# compile bootloader
|
|
python3 ./waf all CFLAGS="-static"
|
|
popd
|
|
# sanity check bootloader is there:
|
|
[[ -e "PyInstaller/bootloader/Darwin-64bit/runw" ]] || fail "Could not find runw in target dir!"
|
|
rm pyinstaller.py # workaround for https://github.com/pyinstaller/pyinstaller/pull/6701
|
|
) || fail "PyInstaller build failed"
|
|
info "Installing PyInstaller."
|
|
python3 -m pip install --no-build-isolation --no-dependencies --no-warn-script-location "$CACHEDIR/pyinstaller"
|
|
|
|
info "Using these versions for building $PACKAGE:"
|
|
sw_vers
|
|
python3 --version
|
|
echo -n "Pyinstaller "
|
|
pyinstaller --version
|
|
|
|
rm -rf ./dist
|
|
|
|
git submodule update --init
|
|
|
|
info "generating locale"
|
|
(
|
|
if ! which msgfmt > /dev/null 2>&1; then
|
|
brew install gettext
|
|
brew link --force gettext
|
|
fi
|
|
LOCALE="$PROJECT_ROOT/electrum/locale/"
|
|
# we want the binary to have only compiled (.mo) locale files; not source (.po) files
|
|
rm -rf "$LOCALE"
|
|
"$CONTRIB/build_locale.sh" "$CONTRIB/deterministic-build/electrum-locale/locale/" "$LOCALE"
|
|
) || fail "failed generating locale"
|
|
|
|
|
|
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-build-isolation --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-build-isolation --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-build-isolation --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-build-isolation --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"
|
|
|
|
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
|