Kivy的PyInstaller打包

0 投票
1 回答
3925 浏览
提问于 2025-04-18 11:09

最近,我尝试用 PyInstaller 把我的 kivy 应用打包成一个可以在我的 Linux 系统上运行的程序(我用的是 Ubuntu 14.04 64位),并希望能把它复制到其他 Linux 系统上。

我整整花了一晚上时间来正确执行 PyInstaller,尝试了各种方法,最后终于成功为我的系统创建了一个包。首先,我想弄清楚我到底做了什么,才能把我的应用打包成功。

我的 main.spec 文件长这样:

# Part A
from kivy.tools.packaging.pyinstaller_hooks import install_hooks
install_hooks(globals())
# -*- mode: python -*-

# Part B
from PyInstaller.hooks.hookutils import exec_statement
hiddenimports = ['pysqlite2', 'MySQLdb', 'psycopg2']
databases = exec_statement("import sqlalchemy.databases;
print sqlalchemy.databases.__all__")
databases = eval(databases.strip())
for n in databases:
    hiddenimports.append("sqlalchemy.databases." + n)
version = exec_statement('import sqlalchemy; print sqlalchemy.__version__')
is_alch06 = version >= '0.6'

if is_alch06:
    dialects = exec_statement("import sqlalchemy.dialects; print sqlalchemy.dialects.__all__")
    dialects = eval(dialects.strip())

    for n in databases:
        hiddenimports.append("sqlalchemy.dialects." + n)

block_cipher = None

# Part C
a = Analysis(['main.py'],
         pathex=['/home/grafgustav/Python/ICCHP/accessmail/src','/'],
         hiddenimports=[],
         cipher=block_cipher)
pyz = PYZ(a.pure,
         cipher=block_cipher)
exe = EXE(pyz,
      a.scripts,
      exclude_binaries=True,
      name='main',
      debug=False,
      strip=None,
      upx=True,
      console=True )


coll = COLLECT(exe, Tree("/home/grafgustav/Python/ICCHP/accessmail"),
           a.binaries, #...,
           a.zipfiles,
           a.datas,
           strip=None,
           upx=True,
           name='main',
           )

我明白为什么 B 部分主要是多余的(我只是从网上某个地方复制的),而 A 部分则为 kivy 提供了一些现成的钩子,但我不太明白为什么这并没有把整个应用打包成我想要的样子。

根据我的理解,钩子是用来包含那些没有直接导入的库,而是隐式导入或者在运行时导入的,就像隐藏导入一样。

我遇到的主要问题是我打包后的程序缺少一些部分。在我的整个源代码目录中,有一个名为 "GUI" 的文件夹,里面包含了 kivy 特有的 *.kv 文件。虽然包含了这个 GUI 文件夹的 src 文件夹被打包进去了,但当我尝试启动程序时,*.kv 文件还是缺失的。所以我试着把 GUI 文件夹复制到打包后的 "main" 文件所在的同一文件夹,结果 *.kv 文件就不缺了。为什么 PyInstaller 能把整个 src 文件夹复制过去,却没能正确识别 GUI 文件夹呢?

接下来我遇到的错误与 sqlalchemy 有关。因为我已经添加了一些 sqlalchemy 的钩子,我发现打包后的应用找不到我用来存储数据的 data.db 文件。这个文件位于 main.py 的上一级文件夹。它被包含在结果中,但位置不对。如果我把 data.db 文件复制到打包后的 main 文件上一级文件夹,这个错误就解决了。

最后一个错误是由于一些 kivy 特有的东西导致的,阻止了程序的运行。错误信息显示,缺少一个文件 [Package directory]/kivy/data/style.kv。实际上,连 "kivy" 文件夹都没有。我在 kivy 源代码中找到了这个文件,把它复制到正确的目录,现在在我的系统上可以正常工作了。

现在我在想,为什么我必须手动把这些文件复制到打包后的目录里。我该如何告诉 PyInstaller 事先包含这些文件呢?在我打包的系统上可以正常工作,但在我的第二台系统(Ubuntu 12.04LTS 64位,出现了关于错误的 OpenGL 版本的错误)、测试系统(Fedora,没有错误,只是 main could not be executed)或我大学的电脑(Debian 3.2.57 64位,出现了 `./main: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.14 not found (required by /libz.so.1)` 的错误)上都无法运行。

我的朋友在他的 Windows 系统上使用 PyInstaller,也遇到了不少问题(他也必须手动将上述文件/文件夹添加到包中),但最后在 Win7 和 Win8 上都能正常工作。

我该如何在自己的系统上使用 PyInstaller 为其他 Linux 系统制作可执行文件?我到底对 main.spec 文件做了什么?或者有没有更好的方法来为 Python 应用创建可执行文件呢?

1 个回答

0

我不是专家,但我会分享我所知道的。

将文件夹添加到生成的包中:

我现在在想,为什么我必须手动把这些文件复制到我的输出目录。怎么才能让PyInstaller提前包含这些文件呢?

在定义你的二进制文件时,在COLLECT部分,你需要调用Tree(),并且要加上一个prefix参数,这样PyInstaller才能知道把accessmail文件夹放在'src'文件夹的哪个位置:

Tree("/home/grafgustav/Python/ICCHP/accessmail", prefix='..')

不过我从来没有试过用'..'作为前缀,得试试看。

将文件添加到生成的包中:

打包后的应用程序找不到我用来存储数据的data.db。这个文件在我打包的main.py的上一级文件夹里。它被包含在结果中,但位置不对。

要将任意文件添加到二进制文件中,你只需要创建一个包含3个值的元组,像这样:

a.binaries += [(file_address, file_result_address, 'DATA')]

file_result_address是文件在生成包中的存放位置。

如果想添加更多文件,只需在"[ ]"之间再添加更多元组:

[(addr,addr,type), (addr,addr,type), ...]

让它在多个系统上运行:

我能给出的第一个建议是,尝试在32位系统上编译,这样它就能在32位和64位系统上运行。

正如在这篇文章的第3部分中建议的,你可以尝试从你的包中移除系统库,并将它们作为依赖项添加,这样它就会使用本地库。上面的链接提供了一个Python函数,可以在.spec文件中完成这个工作。

制作一个单独的二进制文件,而不是一个包含所有依赖项的文件夹:

我怎么能在自己的系统上使用PyInstaller为其他Linux系统制作可执行文件呢?

如果你想要一个单独的二进制文件作为输出,而不是一个包含所有依赖项的文件夹,你需要创建一个带有--onefile选项的.spec文件。之后你可以把相关部分复制到你的代码中:

pyinstaller --onefile myscript.py

你可以在官方的PyInstaller文档中获取更多信息,这对我帮助很大。

希望这能帮到你。

撰写回答