Python程序的打包
pyinstaller和py2app都可以用来打包Python程序,pyinstaller可以打包为任意平台的软件,但在MAC电脑上,更推荐使用py2app进行打包。
不过在使用py2app打包的时候,遇到不少的坑,记录一下。
打包使用venv环境
在进行Python相关的开发,我更多使用的是conda,但是在conda环境下打包,总是报一个奇怪的错误:
查阅很多资料,都无法解决,后来更换为venv后,才正常运行。
如何排查这种错误呢?
右键->显示包内容->MacOS->双击xx软件名,这样可以在终端运行,可以显示出错误的原因:
ImportError: dlopen(/Users/alien/PycharmProjects/PythonTest/dist/test.app/Contents/Resources/lib/python3.9/lib-dynload/_ctypes.so, 0x0002): Library not loaded: @rpath/libffi.8.dylib
ImportError: dlopen(/Users/alien/PycharmProjects/PythonTest/dist/test.app/Contents/Resources/lib/python3.9/lib-dynload/_ctypes.so, 0x0002): Library not loaded: @rpath/libffi.8.dylib
很明显是缺少某些库依赖导致的,这明显是没有把Python环境的某些库正常打包进来,网上有些解决方案是手动移动库到包里面,不建议,换用venv环境重新打包即可。
安装依赖
requirements.txt
python
py2app
flask
pywebview
py2app
flask
pywebview
这里用到了py2app,flask和pywebview,所以要先安装相关依赖。
生成setup.py文件
py2applet --make-setup 程序名称.py
这会生成一个默认的setup.py
python
"""
This is a setup.py script generated by py2applet
Usage:
python setup.py py2app
"""
from setuptools import setup
APP = ['main.py']
DATA_FILES = []
OPTIONS = {}
setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
"""
This is a setup.py script generated by py2applet
Usage:
python setup.py py2app
"""
from setuptools import setup
APP = ['main.py']
DATA_FILES = []
OPTIONS = {}
setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
打包
python setup.py py2app
python setup.py py2app -A
添加 -A 参数进行测试打包,速度较快,但是不会将依赖打包进去。
排坑时刻一
我的打包应用是基于Vue3 + Flask + pywebview + py2app 打包的,而这个py2app打包远没有pyinstaller顺利。
我的完整setup.py的配置,各位可以直接拿去用:
python
"""
This is an example of py2app setup.py script for freezing your pywebview
application
Usage:
python setup.py py2app
"""
import os
from setuptools import setup
ENTRY_POINT = ['main.py']
# 自写模块放在DATA_FILES列表中
DATA_FILES = []
dest_dir = "dist/main.app/Contents/Resources/"
# 第三方库放在OPTIONS下的includes对应的列表中
OPTIONS = {
'argv_emulation': False,
'strip': False, # 不去除debug信息
'iconfile': 'logo.icns', # 应用图标
'includes': ['WebKit', 'Foundation', 'webview'] # 需要打包的第三方库
}
setup(
app=ENTRY_POINT,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
# -rf 是cp命令的选项,分别表示递归复制、保留文件属性和保留目录结构。
os.system("cp -rf h5 " + dest_dir + "h5")
os.system("cp -rf server.py " + dest_dir + "server.py")
os.system("cp -rf api.py " + dest_dir + "api.py")
os.system("cp -rf app.py " + dest_dir + "app.py")
os.system("cp -rf logo.icns " + dest_dir + "logo.icns")
"""
This is an example of py2app setup.py script for freezing your pywebview
application
Usage:
python setup.py py2app
"""
import os
from setuptools import setup
ENTRY_POINT = ['main.py']
# 自写模块放在DATA_FILES列表中
DATA_FILES = []
dest_dir = "dist/main.app/Contents/Resources/"
# 第三方库放在OPTIONS下的includes对应的列表中
OPTIONS = {
'argv_emulation': False,
'strip': False, # 不去除debug信息
'iconfile': 'logo.icns', # 应用图标
'includes': ['WebKit', 'Foundation', 'webview'] # 需要打包的第三方库
}
setup(
app=ENTRY_POINT,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
# -rf 是cp命令的选项,分别表示递归复制、保留文件属性和保留目录结构。
os.system("cp -rf h5 " + dest_dir + "h5")
os.system("cp -rf server.py " + dest_dir + "server.py")
os.system("cp -rf api.py " + dest_dir + "api.py")
os.system("cp -rf app.py " + dest_dir + "app.py")
os.system("cp -rf logo.icns " + dest_dir + "logo.icns")
使用os.system的方式直接把文件拷贝到包目录下,相比配置一些文件要简单的多,官方的案例提供了一个方法并不易用。
排坑时刻二
防坑指南:注意pyinstaller和py2app打包路径的不同,如果配置错误,会找不到h5文件夹。
python
h5_folder = os.path.join(os.getcwd(), 'h5')
print("MAC执行H5路径 = ", h5_folder)
server = Flask(__name__, static_folder=h5_folder, static_url_path="/", template_folder=None)
server.config['SEND_FILE_MAX_AGE_DEFAULT'] = 1 # disable caching
h5_folder = os.path.join(os.getcwd(), 'h5')
print("MAC执行H5路径 = ", h5_folder)
server = Flask(__name__, static_folder=h5_folder, static_url_path="/", template_folder=None)
server.config['SEND_FILE_MAX_AGE_DEFAULT'] = 1 # disable caching