Browse Source

pyinstaller build fixes

master
SomberNight 2 years ago
parent
commit
6c213aca17
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 3
      contrib/build-wine/build-electrum-git.sh
  2. 91
      contrib/build-wine/deterministic.spec
  3. 6
      contrib/build-wine/prepare-wine.sh
  4. 3
      contrib/osx/make_osx.sh
  5. 88
      contrib/osx/osx.spec
  6. 16
      electrum/gui/common_qt/__init__.py
  7. 8
      electrum/gui/common_qt/plugins.py
  8. 14
      electrum/gui/qml/__init__.py
  9. 12
      electrum/plugin.py
  10. 8
      electrum/plugins/trustedcoin/common_qt.py

3
contrib/build-wine/build-electrum-git.sh

@ -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

91
contrib/build-wine/deterministic.spec

@ -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))

6
contrib/build-wine/prepare-wine.sh

@ -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."

3
contrib/osx/make_osx.sh

@ -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

88
contrib/osx/osx.spec

@ -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',

16
electrum/gui/common_qt/__init__.py

@ -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=}")

8
electrum/gui/common_qt/plugins.py

@ -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

14
electrum/gui/qml/__init__.py

@ -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

12
electrum/plugin.py

@ -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

8
electrum/plugins/trustedcoin/common_qt.py

@ -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

Loading…
Cancel
Save