Skip to content

Commit 542e96a

Browse files
ulopefredo
authored andcommitted
Fix PyInstaller bundling
Our bundled binaries seem to have been not working for some time. We had two separate issues: - The `av` dependency (via `aiortc`) wasn't being properly discovered by PyInstaller. This has been fixed with a hook. - On Linux stripping of the libraries apparently causes some corruption See: pypa/manylinux#119 Therefore stripping is disabled for now. - The gevent runtime hook would attempt to start the aio event loop. This caused a warning about an already running loop. This also fixes: - Don't include unnecessary av libraries in the bundle - Remove no longer used GETH_URL and SOLC_URL build args from the `bundle-docker` make target. - On CI: Invoke the built binaries with `--help` as a minimal functionality sanity check.
1 parent 43b6bc9 commit 542e96a

File tree

6 files changed

+93
-54
lines changed

6 files changed

+93
-54
lines changed

.circleci/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,11 @@ jobs:
578578
source .circleci/get_archive_tag.sh
579579
make bundle-docker
580580
echo ${RELEASE_TYPE}/raiden-${ARCHIVE_TAG}-linux-<<parameters.architecture>>.tar.gz > dist/archive/_LATEST-${RELEASE_TYPE}-linux-<<parameters.architecture>>.txt
581+
- run:
582+
name: Test if Binary can be launched
583+
command: |
584+
tar -C /tmp -xf dist/archive/raiden-${ARCHIVE_TAG}-linux-<<parameters.architecture>>.tar.gz
585+
/tmp/raiden-${ARCHIVE_TAG}-linux-<<parameters.architecture>> --help
581586
- persist_to_workspace:
582587
root: "~"
583588
paths:
@@ -610,6 +615,10 @@ jobs:
610615
pyinstaller --noconfirm --clean raiden.spec
611616
zip -9 --junk-paths dist/archive/raiden-${ARCHIVE_TAG}-macOS-x86_64.zip dist/raiden-${ARCHIVE_TAG}-macOS-x86_64
612617
echo ${RELEASE_TYPE}/raiden-${ARCHIVE_TAG}-macOS-x86_64.zip > dist/archive/_LATEST-${RELEASE_TYPE}-macOS-x86_64.txt
618+
- run:
619+
name: Test if Binary can be launched
620+
command: |
621+
dist/raiden-${ARCHIVE_TAG}-macOS-x86_64 --help
613622
- persist_to_workspace:
614623
root: "~"
615624
paths:

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ endif
129129
bundle-docker: ARCHITECTURE_TAG = $(shell docker run --rm python:3.7 uname -m)
130130
bundle-docker: ARCHIVE_TAG ?= v$(shell python setup.py --version)
131131
bundle-docker:
132-
docker build -t pyinstallerbuilder --build-arg GETH_URL_LINUX=$(GETH_URL_LINUX) --build-arg SOLC_URL_LINUX=$(SOLC_URL_LINUX) --build-arg ARCHITECTURE_TAG=$(ARCHITECTURE_TAG) --build-arg ARCHIVE_TAG=$(ARCHIVE_TAG) $(GITHUB_ACCESS_TOKEN_ARG) -f docker/build.Dockerfile .
132+
docker build -t pyinstallerbuilder --build-arg ARCHITECTURE_TAG=$(ARCHITECTURE_TAG) --build-arg ARCHIVE_TAG=$(ARCHIVE_TAG) $(GITHUB_ACCESS_TOKEN_ARG) -f docker/build.Dockerfile .
133133
-(docker rm builder)
134134
docker create --name builder pyinstallerbuilder
135135
mkdir -p dist/archive

docker/build.Dockerfile

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
11
FROM python:3.7-stretch
22

3-
# these are defined in .circleci/config.yml and passed here in the makefile
4-
ARG SOLC_URL_LINUX
5-
ARG GETH_URL_LINUX
6-
73
# install dependencies
84
RUN apt-get update
95
RUN apt-get install -y git-core wget xz-utils build-essential \
106
automake pkg-config libtool libffi-dev python3-dev libgmp-dev \
117
libavdevice-dev libavfilter-dev libopus-dev libvpx-dev pkg-config \
128
libsrtp2-dev
139

14-
RUN wget -nv -O /usr/bin/solc ${SOLC_URL_LINUX} && \
15-
chmod +x /usr/bin/solc
16-
RUN wget -nv -O /tmp/geth.tar.gz ${GETH_URL_LINUX} && \
17-
cd /tmp && \
18-
tar xf geth.tar.gz && \
19-
mv geth-linux-amd64-*/geth /usr/bin/geth && \
20-
rm geth.tar.gz
21-
22-
2310
RUN python3 -m venv /venv
2411
ENV PATH="/venv/bin:$PATH"
2512

raiden.spec

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,40 @@ PyInstaller spec file to build single file or dir distributions
1212
"""
1313

1414
# Set to false to produce an exploded single-dir
15-
ONEFILE = int(os.environ.get('ONEFILE', True))
16-
17-
18-
def Entrypoint(dist, group, name, scripts=None, pathex=None, hiddenimports=None, hookspath=None,
19-
excludes=None, runtime_hooks=None, datas=None):
15+
ONEFILE = int(os.environ.get("ONEFILE", True))
16+
17+
# Hack: This is a list of prefixes to be removed from the `binaries`.
18+
# We do this to prevent including unnecessary libraries (pyav audio / video dependencies)
19+
BINARIES_PREFIX_BLOCKLIST = [
20+
"libav",
21+
"libaom",
22+
"libswscale",
23+
"libswrescale",
24+
"libvorbis",
25+
"libx264",
26+
"libx265",
27+
]
28+
29+
30+
def Entrypoint(
31+
dist,
32+
group,
33+
name,
34+
scripts=None,
35+
pathex=None,
36+
hiddenimports=None,
37+
hookspath=None,
38+
excludes=None,
39+
runtime_hooks=None,
40+
datas=None,
41+
):
2042
import pkg_resources
2143

2244
# get toplevel packages of distribution from metadata
2345
def get_toplevel(dist):
2446
distribution = pkg_resources.get_distribution(dist)
25-
if distribution.has_metadata('top_level.txt'):
26-
return list(distribution.get_metadata('top_level.txt').split())
47+
if distribution.has_metadata("top_level.txt"):
48+
return list(distribution.get_metadata("top_level.txt").split())
2749
else:
2850
return []
2951

@@ -42,54 +64,75 @@ def Entrypoint(dist, group, name, scripts=None, pathex=None, hiddenimports=None,
4264
# insert path of the egg at the verify front of the search path
4365
pathex = [ep.dist.location] + pathex
4466
# script name must not be a valid module name to avoid name clashes on import
45-
script_path = os.path.join(workpath, name + '-script.py')
67+
script_path = os.path.join(workpath, name + "-script.py")
4668
print("creating script for entry point", dist, group, name)
47-
with open(script_path, 'w') as fh:
69+
with open(script_path, "w") as fh:
4870
print("import", ep.module_name, file=fh)
49-
print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh)
71+
print("%s.%s()" % (ep.module_name, ".".join(ep.attrs)), file=fh)
5072
for package in packages:
5173
print("import", package, file=fh)
5274

53-
return Analysis(
75+
analysis = Analysis(
5476
[script_path] + scripts,
5577
pathex=pathex,
5678
hiddenimports=hiddenimports,
5779
hookspath=hookspath,
5880
excludes=excludes,
5981
runtime_hooks=runtime_hooks,
60-
datas=datas
82+
datas=datas,
6183
)
62-
63-
64-
if hasattr(pdb, 'pdb'):
84+
# `Analysis.binaries` behaves set-like and matches on the first tuple item (`name`).
85+
# Since library names include the version we first build a list of the concrete names
86+
# by prefix matching and then subtract that via the set-like behaviour.
87+
binaries_to_remove = [
88+
(name, None, None)
89+
for name, *_ in analysis.binaries
90+
if any(name.startswith(blocklist_item) for blocklist_item in BINARIES_PREFIX_BLOCKLIST)
91+
]
92+
analysis.binaries -= binaries_to_remove
93+
return analysis
94+
95+
96+
if hasattr(pdb, "pdb"):
6597
# pdbpp moves the stdlib pdb to the `pdb` attribute of it's own patched pdb module
6698
raise RuntimeError(
67-
'pdbpp is installed which causes broken PyInstaller builds. Please uninstall it.',
99+
"pdbpp is installed which causes broken PyInstaller builds. Please uninstall it.",
68100
)
69101

70102

71103
# We don't need Tk and friends
72-
sys.modules['FixTk'] = None
104+
sys.modules["FixTk"] = None
73105

74-
executable_name = 'raiden-{}-{}-{}'.format(
75-
os.environ.get('ARCHIVE_TAG', 'v' + get_system_spec()['raiden']),
76-
'macOS' if platform.system() == 'Darwin' else platform.system().lower(),
77-
platform.machine()
106+
executable_name = "raiden-{}-{}-{}".format(
107+
os.environ.get("ARCHIVE_TAG", "v" + get_system_spec()["raiden"]),
108+
"macOS" if platform.system() == "Darwin" else platform.system().lower(),
109+
platform.machine(),
78110
)
79111

80112
a = Entrypoint(
81-
'raiden',
82-
'console_scripts',
83-
'raiden',
84-
hookspath=['tools/pyinstaller_hooks'],
113+
"raiden",
114+
"console_scripts",
115+
"raiden",
116+
hookspath=["tools/pyinstaller_hooks"],
85117
runtime_hooks=[
86-
'tools/pyinstaller_hooks/runtime_gevent_monkey.py',
87-
'tools/pyinstaller_hooks/runtime_encoding.py',
88-
'tools/pyinstaller_hooks/runtime_raiden_contracts.py',
118+
"tools/pyinstaller_hooks/runtime_gevent_monkey.py",
119+
"tools/pyinstaller_hooks/runtime_encoding.py",
120+
"tools/pyinstaller_hooks/runtime_raiden_contracts.py",
89121
],
90-
hiddenimports=['scrypt', 'eth_tester'],
122+
hiddenimports=[],
91123
datas=[],
92-
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
124+
excludes=[
125+
"_tkinter",
126+
"FixTk",
127+
"ipython",
128+
"jupyter",
129+
"jupyter_core",
130+
"notebook",
131+
"tcl",
132+
"tk",
133+
"tkinter",
134+
"Tkinter",
135+
],
93136
)
94137

95138
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
@@ -103,10 +146,10 @@ if ONEFILE:
103146
a.datas,
104147
name=executable_name,
105148
debug=False,
106-
strip=True,
149+
strip=False,
107150
upx=False,
108151
runtime_tmpdir=None,
109-
console=True
152+
console=True,
110153
)
111154
else:
112155
exe = EXE(
@@ -117,7 +160,7 @@ else:
117160
debug=True,
118161
strip=False,
119162
upx=False,
120-
console=True
163+
console=True,
121164
)
122165
coll = COLLECT(
123166
exe,
@@ -126,5 +169,5 @@ else:
126169
a.datas,
127170
strip=False,
128171
upx=False,
129-
name='raiden'
172+
name="raiden",
130173
)

tools/pyinstaller_hooks/hook-av.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from PyInstaller.utils.hooks import collect_submodules
2+
3+
4+
hiddenimports = collect_submodules("av")
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
from gevent.monkey import patch_all # isort:skip # noqa
1+
from gevent.monkey import patch_all
22

3-
patch_all() # isort:skip # noqa
4-
5-
from raiden.network.transport.matrix.rtc.utils import setup_asyncio_event_loop
6-
7-
setup_asyncio_event_loop()
3+
patch_all()

0 commit comments

Comments
 (0)