1
0

pyinstaller build fixes

This commit is contained in:
SomberNight
2023-11-30 14:08:06 +00:00
10 changed files with 130 additions and 119 deletions

View File

@@ -50,6 +50,9 @@ pushd $WINEPREFIX/drive_c/electrum
# see https://github.com/pypa/pip/issues/2195 -- pip makes a copy of the entire directory
info "Pip installing Electrum. This might take a long time if the project folder is large."
$WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-warn-script-location .
# pyinstaller needs to be able to "import electrum", for which we need libsecp256k1:
# (or could try "pip install -e" instead)
cp electrum/libsecp256k1-*.dll "$WINEPREFIX/drive_c/python3/Lib/site-packages/electrum/"
popd

View File

@@ -4,47 +4,40 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules, coll
import sys, os
PYPKG="electrum"
MAIN_SCRIPT="run_electrum"
PROJECT_ROOT = "C:/electrum"
ICONS_FILE=f"{PROJECT_ROOT}/{PYPKG}/gui/icons/electrum.ico"
cmdline_name = os.environ.get("ELECTRUM_CMDLINE_NAME")
if not cmdline_name:
raise Exception('no name')
home = 'C:\\electrum\\'
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('pkg_resources') # workaround for https://github.com/pypa/setuptools/issues/1963
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('safetlib')
hiddenimports += collect_submodules('btchip') # device plugin: ledger
hiddenimports += collect_submodules('ledger_bitcoin') # device plugin: ledger
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
hiddenimports += collect_submodules('bitbox02')
hiddenimports += ['electrum.plugins.jade.jade']
hiddenimports += ['electrum.plugins.jade.jadepy.jade']
hiddenimports += ['PyQt5.QtPrintSupport'] # needed by Revealer
hiddenimports += collect_submodules(f"{PYPKG}.plugins")
binaries = []
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'qwindowsvista' in b[0]]
# add libsecp256k1, libusb, etc:
binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dll", '.')]
binaries += [('C:/tmp/libsecp256k1-2.dll', '.')]
binaries += [('C:/tmp/libusb-1.0.dll', '.')]
binaries += [('C:/tmp/libzbar-0.dll', '.')]
datas = [
(home+'electrum/*.json', 'electrum'),
(home+'electrum/lnwire/*.csv', 'electrum/lnwire'),
(home+'electrum/wordlist/english.txt', 'electrum/wordlist'),
(home+'electrum/wordlist/slip39.txt', 'electrum/wordlist'),
(home+'electrum/locale', 'electrum/locale'),
(home+'electrum/plugins', 'electrum/plugins'),
(home+'electrum/gui/icons', 'electrum/gui/icons'),
(f"{PROJECT_ROOT}/{PYPKG}/*.json", PYPKG),
(f"{PROJECT_ROOT}/{PYPKG}/lnwire/*.csv", f"{PYPKG}/lnwire"),
(f"{PROJECT_ROOT}/{PYPKG}/wordlist/english.txt", f"{PYPKG}/wordlist"),
(f"{PROJECT_ROOT}/{PYPKG}/wordlist/slip39.txt", f"{PYPKG}/wordlist"),
(f"{PROJECT_ROOT}/{PYPKG}/locale", f"{PYPKG}/locale"),
(f"{PROJECT_ROOT}/{PYPKG}/plugins", f"{PYPKG}/plugins"),
(f"{PROJECT_ROOT}/{PYPKG}/gui/icons", f"{PYPKG}/gui/icons"),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files(f"{PYPKG}.plugins")
datas += collect_data_files('trezorlib') # TODO is this needed? and same question for other hww libs
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
@@ -52,29 +45,19 @@ datas += collect_data_files('ckcc')
datas += collect_data_files('bitbox02')
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([home+'run_electrum',
home+'electrum/gui/qt/main_window.py',
home+'electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py',
home+'electrum/gui/text.py',
home+'electrum/util.py',
home+'electrum/wallet.py',
home+'electrum/simple_config.py',
home+'electrum/bitcoin.py',
home+'electrum/dnssec.py',
home+'electrum/commands.py',
home+'electrum/plugins/cosigner_pool/qt.py',
home+'electrum/plugins/trezor/qt.py',
home+'electrum/plugins/safe_t/client.py',
home+'electrum/plugins/safe_t/qt.py',
home+'electrum/plugins/keepkey/qt.py',
home+'electrum/plugins/ledger/qt.py',
home+'electrum/plugins/coldcard/qt.py',
home+'electrum/plugins/jade/qt.py',
#home+'packages/requests/utils.py'
a = Analysis([f"{PROJECT_ROOT}/{MAIN_SCRIPT}",
f"{PROJECT_ROOT}/{PYPKG}/gui/qt/main_window.py",
f"{PROJECT_ROOT}/{PYPKG}/gui/qt/qrreader/qtmultimedia/camera_dialog.py",
f"{PROJECT_ROOT}/{PYPKG}/gui/text.py",
f"{PROJECT_ROOT}/{PYPKG}/util.py",
f"{PROJECT_ROOT}/{PYPKG}/wallet.py",
f"{PROJECT_ROOT}/{PYPKG}/simple_config.py",
f"{PROJECT_ROOT}/{PYPKG}/bitcoin.py",
f"{PROJECT_ROOT}/{PYPKG}/dnssec.py",
f"{PROJECT_ROOT}/{PYPKG}/commands.py",
],
binaries=binaries,
datas=datas,
#pathex=[home+'lib', home+'gui', home+'plugins'],
hiddenimports=hiddenimports,
hookspath=[])
@@ -125,11 +108,11 @@ exe_standalone = EXE(
a.scripts,
a.binaries,
a.datas,
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + ".exe"),
name=os.path.join("build", "pyi.win32", PYPKG, f"{cmdline_name}.exe"),
debug=False,
strip=None,
upx=False,
icon=home+'electrum/gui/icons/electrum.ico',
icon=ICONS_FILE,
console=False)
# console=True makes an annoying black box pop up, but it does make Electrum output command line commands, with this turned off no output will be given but commands can still be used
@@ -138,11 +121,11 @@ exe_portable = EXE(
a.scripts,
a.binaries,
a.datas + [('is_portable', 'README.md', 'DATA')],
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name + "-portable.exe"),
name=os.path.join("build", "pyi.win32", PYPKG, f"{cmdline_name}-portable.exe"),
debug=False,
strip=None,
upx=False,
icon=home+'electrum/gui/icons/electrum.ico',
icon=ICONS_FILE,
console=False)
#####
@@ -152,22 +135,22 @@ exe_inside_setup_noconsole = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name),
name=os.path.join("build", "pyi.win32", PYPKG, f"{cmdline_name}.exe"),
debug=False,
strip=None,
upx=False,
icon=home+'electrum/gui/icons/electrum.ico',
icon=ICONS_FILE,
console=False)
exe_inside_setup_console = EXE(
pyz,
a.scripts,
exclude_binaries=True,
name=os.path.join('build\\pyi.win32\\electrum', cmdline_name+"-debug"),
name=os.path.join("build", "pyi.win32", PYPKG, f"{cmdline_name}-debug.exe"),
debug=False,
strip=None,
upx=False,
icon=home+'electrum/gui/icons/electrum.ico',
icon=ICONS_FILE,
console=True)
coll = COLLECT(
@@ -179,6 +162,6 @@ coll = COLLECT(
strip=None,
upx=True,
debug=False,
icon=home+'electrum/gui/icons/electrum.ico',
icon=ICONS_FILE,
console=False,
name=os.path.join('dist', 'electrum'))
name=os.path.join('dist', PYPKG))

View File

@@ -53,9 +53,9 @@ $WINE_PYTHON -m pip install --no-build-isolation --no-dependencies --no-binary :
# copy already built DLLs
cp "$DLL_TARGET_DIR"/libsecp256k1-*.dll $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libsecp to its destination"
cp "$DLL_TARGET_DIR/libzbar-0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libzbar to its destination"
cp "$DLL_TARGET_DIR/libusb-1.0.dll" $WINEPREFIX/drive_c/tmp/ || fail "Could not copy libusb to its destination"
cp "$DLL_TARGET_DIR"/libsecp256k1-*.dll $WINEPREFIX/drive_c/electrum/electrum/ || fail "Could not copy libsecp to its destination"
cp "$DLL_TARGET_DIR/libzbar-0.dll" $WINEPREFIX/drive_c/electrum/electrum/ || fail "Could not copy libzbar to its destination"
cp "$DLL_TARGET_DIR/libusb-1.0.dll" $WINEPREFIX/drive_c/electrum/electrum/ || fail "Could not copy libusb to its destination"
info "Building PyInstaller."

View File

@@ -223,6 +223,9 @@ python3 -m pip install --no-build-isolation --no-dependencies --no-binary :all:
info "Building $PACKAGE..."
python3 -m pip install --no-build-isolation --no-dependencies \
--no-warn-script-location . > /dev/null || fail "Could not build $PACKAGE"
# pyinstaller needs to be able to "import electrum", for which we need libsecp256k1:
# (or could try "pip install -e" instead)
cp "$PROJECT_ROOT/electrum"/libsecp256k1.*.dylib "$VENV_DIR/lib/python$PY_VER_MAJOR/site-packages/electrum/"
# strip debug symbols of some compiled libs
# - hidapi (hid.cpython-39-darwin.so) in particular is not reproducible without this

View File

@@ -4,83 +4,67 @@ from PyInstaller.utils.hooks import collect_data_files, collect_submodules, coll
import sys, os
PACKAGE='Electrum'
PACKAGE_NAME='Electrum.app'
PYPKG='electrum'
MAIN_SCRIPT='run_electrum'
ICONS_FILE=PYPKG + '/gui/icons/electrum.icns'
PROJECT_ROOT = os.path.abspath(".")
ICONS_FILE=f"{PROJECT_ROOT}/{PYPKG}/gui/icons/electrum.icns"
VERSION = os.environ.get("ELECTRUM_VERSION")
if not VERSION:
raise Exception('no version')
electrum = os.path.abspath(".") + "/"
block_cipher = None
# see https://github.com/pyinstaller/pyinstaller/issues/2005
hiddenimports = []
hiddenimports += collect_submodules('pkg_resources') # workaround for https://github.com/pypa/setuptools/issues/1963
hiddenimports += collect_submodules('trezorlib')
hiddenimports += collect_submodules('safetlib')
hiddenimports += collect_submodules('btchip') # device plugin: ledger
hiddenimports += collect_submodules('ledger_bitcoin') # device plugin: ledger
hiddenimports += collect_submodules('keepkeylib')
hiddenimports += collect_submodules('websocket')
hiddenimports += collect_submodules('ckcc')
hiddenimports += collect_submodules('bitbox02')
hiddenimports += ['electrum.plugins.jade.jade']
hiddenimports += ['electrum.plugins.jade.jadepy.jade']
hiddenimports += ['PyQt5.QtPrintSupport'] # needed by Revealer
hiddenimports += collect_submodules(f"{PYPKG}.plugins")
binaries = []
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
# add libsecp256k1, libusb, etc:
binaries += [(f"{PROJECT_ROOT}/{PYPKG}/*.dylib", ".")]
datas = [
(electrum + PYPKG + '/*.json', PYPKG),
(electrum + PYPKG + '/lnwire/*.csv', PYPKG + '/lnwire'),
(electrum + PYPKG + '/wordlist/english.txt', PYPKG + '/wordlist'),
(electrum + PYPKG + '/wordlist/slip39.txt', PYPKG + '/wordlist'),
(electrum + PYPKG + '/locale', PYPKG + '/locale'),
(electrum + PYPKG + '/plugins', PYPKG + '/plugins'),
(electrum + PYPKG + '/gui/icons', PYPKG + '/gui/icons'),
(f"{PROJECT_ROOT}/{PYPKG}/*.json", PYPKG),
(f"{PROJECT_ROOT}/{PYPKG}/lnwire/*.csv", f"{PYPKG}/lnwire"),
(f"{PROJECT_ROOT}/{PYPKG}/wordlist/english.txt", f"{PYPKG}/wordlist"),
(f"{PROJECT_ROOT}/{PYPKG}/wordlist/slip39.txt", f"{PYPKG}/wordlist"),
(f"{PROJECT_ROOT}/{PYPKG}/locale", f"{PYPKG}/locale"),
(f"{PROJECT_ROOT}/{PYPKG}/plugins", f"{PYPKG}/plugins"),
(f"{PROJECT_ROOT}/{PYPKG}/gui/icons", f"{PYPKG}/gui/icons"),
]
datas += collect_data_files('trezorlib')
datas += collect_data_files(f"{PYPKG}.plugins")
datas += collect_data_files('trezorlib') # TODO is this needed? and same question for other hww libs
datas += collect_data_files('safetlib')
datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
datas += collect_data_files('bitbox02')
# Add libusb so Trezor and Safe-T mini will work
binaries = [(electrum + "electrum/libusb-1.0.dylib", ".")]
binaries += [(electrum + "electrum/libsecp256k1.2.dylib", ".")]
binaries += [(electrum + "electrum/libzbar.0.dylib", ".")]
# Workaround for "Retro Look":
binaries += [b for b in collect_dynamic_libs('PyQt5') if 'macstyle' in b[0]]
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([electrum+ MAIN_SCRIPT,
electrum+'electrum/gui/qt/main_window.py',
electrum+'electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py',
electrum+'electrum/gui/text.py',
electrum+'electrum/util.py',
electrum+'electrum/wallet.py',
electrum+'electrum/simple_config.py',
electrum+'electrum/bitcoin.py',
electrum+'electrum/dnssec.py',
electrum+'electrum/commands.py',
electrum+'electrum/plugins/cosigner_pool/qt.py',
electrum+'electrum/plugins/trezor/qt.py',
electrum+'electrum/plugins/safe_t/client.py',
electrum+'electrum/plugins/safe_t/qt.py',
electrum+'electrum/plugins/keepkey/qt.py',
electrum+'electrum/plugins/ledger/qt.py',
electrum+'electrum/plugins/coldcard/qt.py',
electrum+'electrum/plugins/jade/qt.py',
a = Analysis([f"{PROJECT_ROOT}/{MAIN_SCRIPT}",
f"{PROJECT_ROOT}/{PYPKG}/gui/qt/main_window.py",
f"{PROJECT_ROOT}/{PYPKG}/gui/qt/qrreader/qtmultimedia/camera_dialog.py",
f"{PROJECT_ROOT}/{PYPKG}/gui/text.py",
f"{PROJECT_ROOT}/{PYPKG}/util.py",
f"{PROJECT_ROOT}/{PYPKG}/wallet.py",
f"{PROJECT_ROOT}/{PYPKG}/simple_config.py",
f"{PROJECT_ROOT}/{PYPKG}/bitcoin.py",
f"{PROJECT_ROOT}/{PYPKG}/dnssec.py",
f"{PROJECT_ROOT}/{PYPKG}/commands.py",
],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[])
# http://stackoverflow.com/questions/19055089/pyinstaller-onefile-warning-pyconfig-h-when-importing-scipy-or-scipy-signal
for d in a.datas:
if 'pyconfig' in d[0]:
@@ -106,7 +90,7 @@ exe = EXE(
debug=False,
strip=False,
upx=True,
icon=electrum+ICONS_FILE,
icon=ICONS_FILE,
console=False,
target_arch='x86_64', # TODO investigate building 'universal2'
)
@@ -116,9 +100,9 @@ app = BUNDLE(
a.binaries,
a.zipfiles,
a.datas,
version = VERSION,
name=PACKAGE + '.app',
icon=electrum+ICONS_FILE,
version=VERSION,
name=PACKAGE_NAME,
icon=ICONS_FILE,
bundle_identifier=None,
info_plist={
'NSHighResolutionCapable': 'True',

View File

@@ -0,0 +1,16 @@
# Copyright (C) 2023 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import sys
# FIXME: remove when both desktop and mobile are Qt6
def get_qt_major_version() -> int:
_GUI_QT_VERSION = getattr(sys, '_GUI_QT_VERSION', None)
if _GUI_QT_VERSION is None:
# used by pyinstaller when building (analysis phase)
_GUI_QT_VERSION = 5
if _GUI_QT_VERSION in (5, 6):
return _GUI_QT_VERSION
raise Exception(f"unexpected {_GUI_QT_VERSION=}")

View File

@@ -1,9 +1,13 @@
import sys
if getattr(sys, '_GUI_QT_VERSION') == 5: # FIXME: remove when both desktop and mobile are Qt6
from . import get_qt_major_version
if (qt_ver := get_qt_major_version()) == 5:
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject
else:
elif qt_ver == 6:
from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject
else:
raise Exception(f"unexpected {qt_ver=}")
from electrum.logging import get_logger

View File

@@ -6,13 +6,19 @@ from typing import TYPE_CHECKING
try:
import PyQt6
except Exception:
sys.exit("Error: Could not import PyQt6. On Linux systems, you may try 'sudo apt-get install python3-pyqt6'")
except Exception as e:
from electrum import GuiImportError
raise GuiImportError(
"Error: Could not import PyQt6. On Linux systems, "
"you may try 'sudo apt-get install python3-pyqt6'") from e
try:
import PyQt6.QtQml
except Exception:
sys.exit("Error: Could not import PyQt6.QtQml. On Linux systems, you may try 'sudo apt-get install python3-pyqt6.qtquick'")
except Exception as e:
from electrum import GuiImportError
raise GuiImportError(
"Error: Could not import PyQt6.QtQml. On Linux systems, "
"you may try 'sudo apt-get install python3-pyqt6.qtquick'") from e
from PyQt6.QtCore import (Qt, QCoreApplication, QLocale, QTranslator, QTimer, QT_VERSION_STR, PYQT_VERSION_STR)
from PyQt6.QtGui import QGuiApplication

View File

@@ -80,7 +80,13 @@ class Plugins(DaemonThread):
"""
if cls._all_found_plugins is None:
cls._all_found_plugins = dict()
for loader, name, ispkg in pkgutil.iter_modules([cls.pkgpath]):
iter_modules = list(pkgutil.iter_modules([cls.pkgpath]))
for loader, name, ispkg in iter_modules:
# FIXME pyinstaller binaries are packaging each built-in plugin twice:
# once as data and once as code. To honor the "no duplicates" rule below,
# we exclude the ones packaged as *code*, here:
if loader.__class__.__qualname__ == "FrozenImporter":
continue
full_name = f'electrum.plugins.{name}'
spec = importlib.util.find_spec(full_name)
if spec is None: # pkgutil found it but importlib can't ?!
@@ -94,7 +100,9 @@ class Plugins(DaemonThread):
except Exception as e:
raise Exception(f"Error pre-loading {full_name}: {repr(e)}") from e
d = module.__dict__
assert name not in cls._all_found_plugins
if name in cls._all_found_plugins:
_logger.info(f"Found the following plugin modules: {iter_modules=}")
raise Exception(f"duplicate plugins? for {name=}")
cls._all_found_plugins[name] = d
return cls._all_found_plugins

View File

@@ -4,10 +4,14 @@ import base64
import sys
from typing import TYPE_CHECKING
if getattr(sys, '_GUI_QT_VERSION') == 5: # FIXME: remove when both desktop and mobile are Qt6
from electrum.gui.common_qt import get_qt_major_version
if (qt_ver := get_qt_major_version()) == 5:
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
else:
elif qt_ver == 6:
from PyQt6.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
else:
raise Exception(f"unexpected {qt_ver=}")
from electrum.i18n import _
from electrum.bip32 import BIP32Node