0%

PyInstaller使用手记

  1. 安装
  2. 将.py脚本打包为exe
  3. 利用.spec文件导入静态资源&动态链接库

使用PyInstaller在Windows系统下打包了一个带有mxnet-gpu的项目,遇到了一些依赖库和静态文件的问题,记录一下使用过程和遇到的问题及解决方法

安装

参考官方教程,直接通过pip命令安装,我使用的是3.6版本的python,直接安装成功。

1
pip install pyinstaller

将.py脚本打包为exe

输入命令

1
pyinstaller main.py

就可以将main.py文件打包成一个.exe文件,位于命令执行目录的./dist文件夹下,同时会在当前目录中生成一个./build文件夹,存放构建时产生的临时文件,并且创建一个./main.spec文件,记录了本次打包的一些配置参数

如果希望进行更多样的配置,可以通过参数来控制pyinstaller的行为。例如通过--distpath设置输出路径,--onefile设置生成单文件等

1
2
pyinstaller main.py --distpath "./output"  # 设置生成目录
pyinstaller main.py --onefile # 仅生成一个exe文件

一些常用的命令参数及帮助说明如下表所示(官网):

参数 帮助说明
-h, —help show this help message and exit
-v, —version Show program version info and exit.
—distpath DIR Where to put the bundled app (default: ./dist)
—workpath WORKPATH Where to put all the temporary work files, .log, .pyz and etc. (default: ./build)
-y, —noconfirm Replace output directory (default: SPECPATH/dist/SPECNAME) without asking for confirmation
—upx-dir UPX_DIR Path to UPX utility (default: search the execution path)
-a, —ascii Do not include unicode encoding support (default: included if available)
—clean Clean PyInstaller cache and remove temporary files before building.
—log-level LEVEL Amount of detail in build-time console messages. LEVEL may be one of TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL (default: INFO).
-D, —onedir Create a one-folder bundle containing an executable (default)
-F, —onefile Create a one-file bundled executable.
—specpath DIR Folder to store the generated spec file (default: current directory)
-n NAME, —name NAME Name to assign to the bundled app and spec file (default: first script’s basename)
—add-data Additional non-binary files or folders to be added to the executable. The path separator is platform specific, os.pathsep (which is ; on Windows and : on most unix systems) is used. This option can be used multiple times.
—add-binary Additional binary files to be added to the executable. See the --add-data option for more details. This option can be used multiple times.

利用.spec文件导入静态资源&动态链接库

除了直接对.py文件打包之外,还可以直接根据.spec文件进行打包:

1
pyinstaller main.spec

该文件在运行pyinstaller main.py命令时自动创建

存在一种情况,pyinstaller并不总能找到你在.py文件中import的包,例如,mxnet有CPU版本和GPU版本,GPU版本又是和CUDA对应的,如mxnet-cu101,在打包时其相关的dll文件并不能被找到并包含到生成的输出路径中,因此执行main.exe时会报错:

1
2
3
4
5
6
7
8
RuntimeError: Cannot find the MXNet library.
List of candidates:
...省略...\mxnet\libmxnet.dll
...省略...\mxnet\../../lib/libmxnet.dll
...省略...\mxnet\../../build/libmxnet.dll
...省略...\mxnet\../../build\libmxnet.dll
...省略...\mxnet\../../build\Release\libmxnet.dll
...省略...\mxnet\../../windows/x64\Release\libmxnet.dll

此时需要手动把对应的dll文件加入到输出路径的指定文件夹下,找到所需的dll文件路径为D:\Anaconda\anaconda3\envs\mxnet-gpu\Lib\site-packages\mxnet\libmxnet.dll(也可以是相对路径),将其单斜杠改为双斜杠,通过--add-binary参数加入到输出路径下的mxnet文件夹中:

1
pyinstaller main.py --add-binary "D:\\Anaconda\\anaconda3\\envs\\mxnet-gpu\\Lib\\site-packages\\mxnet\\libmxnet.dll;.\\mxnet"

--add-binary参数可以出现多次,用于应对多个dll文件的情况,就像这样:

1
pyinstaller main.py --add-binary "D:\\Anaconda\\anaconda3\\envs\\mxnet-gpu\\Lib\\site-packages\\mxnet\\libmxnet.dll;.\\mxnet" --add-binary "SRC;DEST" --add-binary "SRC;DEST"

这里面libmxnet.dll如果写成*.dll,就表示文件夹下的所有.dll文件;分号隔开的下一项表示拷贝的目标位置,相对于输出的.exe文件所在路径而定。同理,参数--add-data用于引入静态文件,可以引入一个文件夹、一个文件、一个目录下的文件。

如果不需要引入路径下的所有dll文件,只需要其中几个,导致最终的命令参数过长且不容易保存,那么使用.spec文件进行打包会是一个好的选择。运行一次命令pyinstaller main.py会生成一个main.spec文件,在这个文件中找到Analysis,修改binariesdatas数组,可以手动引入dll和静态文件。数组的每一项是一个元组,作为pyinstaller调用函数获取文件的参数,元组的第一个参数为源目录,第二个为目标目录,相对或绝对路径均可。一个完整的.spec配置文件示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None


a = Analysis(['hello_pyinstaller.py'],
pathex=['D:\\codes\\Python\\hello_pyinstaller'],
binaries=[
('D:\\Anaconda\\anaconda3\\envs\\mxnet-gpu\\Lib\\site-packages\\mxnet\\*.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\cublas64_10.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\cublasLt64_10.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\cufft64_10.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\curand64_10.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\cusolver64_10.dll', '.\\mxnet'),
('D:\\NVIDIA\\NVIDA GPU Computing Toolkit\\CUDA\\v10.1\\bin\\nvrtc64_101_0.dll', '.\\mxnet'),
],
datas=[
('.\\models', '.\\models'),
('.\\model_list.json', '.')
],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='object_detection',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='object_detection')

更多.spec文件的用法请前往官网:Using Spec Files — PyInstaller 4.2 documentation