是否有*任何*解决方案来打包使用 cppyy 的 python 应用程序?

Is there *any* solution to packaging a python app that uses cppyy?

创建 cross-platform 运行 次 python 桌面应用程序时,我不是新手。我使用 mostly pyinstaller、cxfreeze、有时是 fbs,有时是公文包,为我的本科生创建各种工具。任何定期执行此操作的人都知道,在使用任意集合 python 个模块,但到目前为止我已经设法弄清楚了所有内容。

我有一个 python GUI 应用程序,它使用一个巨大且不断变化的 c++ 库,所以我不能只在 python 中重写它。我已经成功编写了 python 使用 c++ 库的代码,使用令人惊叹的(和 possibly 神奇的)名为 cppyy 的库,它允许您 运行 来自 python 的 c++ 代码几乎没有任何努力。在 Linux、mac 和 windows 上 运行 的一切都很棒,但我无法将它打包成 运行 次,我已经尝试了上面的所有系统.它们都可以产生 运行 次(即没有错误),但是当您 运行 它们时它们会失败。本质上,它们都会给出一些关于无法找到 cppyy 后端的错误(例如,pyinstaller 和使用 pyinstaller 的 fbs 在您 运行 二进制文件时给出此消息):

/home/nogard/Desktop/cppyytest/target/MyApp/cppyy_backend/loader.py:113: UserWarning: No precompiled header available ([Errno 2] No such file or directory: '/home/nogard/Desktop/cppyytest/target/MyApp/cppyy_backend'); this may impact performance.
Traceback (most recent call last):
  File "main.py", line 5, in <module>
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "/home/nogard/Desktop/cppyytest/venv/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 628, in exec_module
    exec(bytecode, module.__dict__)
  File "cppyy/__init__.py", line 74, in <module>
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "/home/nogard/Desktop/cppyytest/venv/lib/python3.6/site-packages/PyInstaller/loader/pyimod03_importers.py", line 628, in exec_module
    exec(bytecode, module.__dict__)
  File "cppyy/_cpython_cppyy.py", line 20, in <module>
  File "cppyy_backend/loader.py", line 74, in load_cpp_backend
RuntimeError: could not load cppyy_backend library
[11195] Failed to execute script main

我真的很难过。通常,您使用 pip 安装 cppyy,这会安装 cppyy-backend 和其他包。我什至使用了cppyy docs方法来编译每个依赖项以及cppyy,但结果是一样的。

我会使用任何有效的构建系统...有人成功了吗?我知道我可以使用 docker,但我之前尝试过这个,我的许多学生在 docker 要求他们更改他们的 bios 设置以支持虚拟化时吓坏了所以我想使用生成某种 运行nable 二进制文件的普通打包系统。

如果您知道如何让 pyinstaller、cxfreeze、fbs 或公文包与 cppyy 一起工作(例如,如果您知道如何处理上述错误),请告诉我。但是,如果您获得了与其他系统打包在一起的 cppyy 应用程序,请告诉我,我会使用那个。

如果您正在寻找 运行 的一些代码,我一直在使用这个最少的代码测试打包方法:

import cppyy

print('hello world from python\n')

cppyy.cppexec('''
#include <string>
using namespace std;
string mystring("hello world from c++");
std::cout << mystring << std::endl;
''')

编辑:找出 pyinstaller 挂钩;一旦发布,这应该都是全自动的

请注意,我对包装没有任何经验 run-times,所以我可能遗漏了一些明显的东西,但我刚刚尝试 pyinstaller,以下似乎有效。

首先,将上面的脚本保存为 example.py,然后创建一个规范文件:

$ pyi-makespec example.py

然后,将 cppyy_backend 中的 header 和库添加为 datas(跳过默认添加的 python 文件)。最简单的似乎是从后端获取所有目录,因此通过在顶部添加来更改生成的 example.spec

def backend_files():
    import cppyy_backend, glob, os

    all_files = glob.glob(os.path.join(
        os.path.dirname(cppyy_backend.__file__), '*'))

    def datafile(path):
        return path, os.path.join('cppyy_backend', os.path.basename(path))

    return [datafile(filename) for filename in all_files if os.path.isdir(filename)]

并将 Analysis object 中的空 datas 替换为:

             datas=backend_files(),

如果您还需要 CPyCppyy 中的 API header,那么可以在例如像这样:

def api_files():
    import cppyy, os

    paths = str(cppyy.gbl.gInterpreter.GetIncludePath()).split('-I')
    for p in paths:
        if not p: continue
       
        apipath = os.path.join(p.strip()[1:-1], 'CPyCppyy')
        if os.path.exists(apipath):
            return [(apipath, os.path.join('include', 'CPyCppyy'))]

    return []

并添加到分析 object:

             datas=backend_files()+api_files(),

但是请注意,Python.h 还需要存在于将要部署包的系统上。如果需要,Python.h 可以通过模块 sysconfig 找到,其路径通过下面讨论的 bootstrap.py 文件中的 cppyy.add_include_path 提供。

接下来,考虑预编译的 header(文件 cppyy_backend/etc/allDict.cxx.pch):它包含 LLVM 中间表示形式的 C++ 标准 header。如果添加,则pre-empts 部署包的地方需要系统编译器。但是,如果有系统编译器,那么理想情况下,应该在部署后首次使用时重新创建 PCH。

但是,cppyy_backend 中的 loader.py 脚本使用 sys.executable,它被冻结破坏了(意思是,它是 top-level 脚本,而不是 python,导致无限递归)。即使当 PCH 可用时,它的时间戳也会与 include 目录的时间戳进行比较,如果更旧则重建。由于 PCH 和 include 目录都根据复制顺序而不是构建顺序获取新的时间戳,因此这是不可靠的并且可能导致虚假重建。因此,要么禁用 PCH,要么禁用时间戳检查。

为此,选择这两个选项之一并将其写入名为 bootstrap.py 的文件中,方法是取消注释所需的行为:

### option 1: disable the PCH altogether

# import os
# os.environ['CLING_STANDARD_PCH'] = 'none'

### option 2: force the loader to declare the PCH up-to-date

# import cppyy_backend.loader
#
# def _is_uptodate(*args):
#    return True
#
# cppyy_backend.loader._is_uptodate = _is_uptodate

然后将 bootstrap 作为钩子添加到 Analysis object 中的规范文件中:

             runtime_hooks=['bootstrap.py'],

如上所述,bootstrap.py 也是根据需要添加更多包含路径的好地方,例如Python.h.

最后,运行像往常一样:

$ pyinstaller example.spec