首先,当然是给一个目标系统安装 cx_freeze。虽然 cx_freeze 是跨平台的,但没发现它支持在一个平台上打包出另一个平台的二进制文件,而且那样还得准备那个平台上的库文件。我的目标平台是 Windows XP,所以还要准备一个 Dependency Walker。
其次,使用cxfreeze-quickstart
向导生成配置文件setup.py
。当然,如果已经有setup.py
文件的话直接修改就是了。下边是一个示例:
import sys from cx_Freeze import setup, Executable # Dependencies are automatically detected, but it might need # fine tuning. buildOptions = dict( packages = [], excludes = [], include_files = ['images', 'data.sqlite'], ) name = 'example' if sys.platform == 'win32': name = name + '.exe' base = None if sys.platform == "win32": base = "Win32GUI" executables = [ Executable('main.py', base = base, targetName = name, compress = True, ) ] setup(name='Example', version = '1.0', description = 'An example program', options = dict(build_exe = buildOptions), executables = executables)
当然,这里有不少我改过的地方。在buildOptions
变量中我加了data.sqlite
文件和images
目录到include_files
中去。它们会被放到生成的二进制文件相同的目录。
cx_freeze 在打包 Windows 可执行文件时并不会像 gcc 那样自动添加.exe
后缀,所以我要手动加上。
在Executable
的调用中,要写成base='Win32GUI'
这样子。cxfreeze-quickstart
目前直接写在第二个参数的位置上的方法是不对的。base
的默认值是Console
,在 Windows 下运行时是会出现黑色的cmd.exe
窗口的。参见StackOverflow: Hide console window with wxPython and cxFreeze。
这样还没有完成。打包后测试发现PyQt4.QtNetwork
的库文件没有打包进去,可能是因为它是从共享库中引用的,cx_freeze 没有检测到这个依赖。在程序中 import 一下就可以了。另外一个问题是,在没有安装相关库的干净的目标系统上执行时还遇到以下错误信息:
DLL load failed: 找不到指定的模块 DLL load failed: The specified module could not be found.
其上还有一个 Traceback。这是因为有些(据说主要是 Microsoft Visual C++ Redistributable 的) DLL (非 Python 模块)没有被打包进去。从 Traceback 中找到引发这个错误的 DLL(或者 pyd)文件名,将其在打包系统中使用前边提到的 Dependency Walker 打开,在左边的树形库列表中找到目标系统上可能没有的库文件,将其复制到 cx_freeze 生成二进制文件的目录中即可。比如我这里需要手动添加msvcr100.dll
和msvcp100.dll
。
最后,打包过的程序执行时__main__
模块是没有__file__
属性的,所以无法通过这个变量来切换到程序所在的目录,进而读取自己的数据文件。但是,打包过的程序有sys.frozen
属性,程序自身的路径存放在sys.executable
中,所以程序中需要作下判断:
import os import sys if hasattr(sys, 'frozen'): me = sys.executable else: me = __file__ mydir = os.path.dirname(me)
参见StackOverflow: How do I get the path of the current executed file in python?。
最终打出来的可执行文件和库文件比较大,PyQt 程序总共有 40M 之多。使用 7z 压缩之后能减小到 10M 多。