【问题标题】:Is there *any* solution to packaging a python app that uses cppyy?是否有*任何*解决方案来打包使用 cppyy 的 python 应用程序?
【发布时间】:2020-10-17 19:55:24
【问题描述】:

在为我的 python 桌面应用程序创建跨平台运行时时,我不是新手。我为我的本科生创建了各种工具,主要使用 pyinstaller、cxfreeze、有时是 fbs,有时是公文包。任何经常这样做的人都知道,在使用任意 Python 模块集合时,针对 Linux、Windows 和 macOS 需要进行许多怪癖和调整,但到目前为止,我已经设法弄清楚了一切。

我有一个 python GUI 应用程序,它使用一个庞大且不断变化的 c++ 库,所以我不能只用 python 重新编写它。我已经成功地编写了使用 c++ 库的 python 代码,它使用了一个名为 cppyy 的令人惊叹的(并且可能是神奇的)库,它允许您从 python 运行 c++ 代码而不需要任何努力。一切都在 Linux、mac 和 windows 上运行良好,但我无法将其打包到运行时中,并且我已经尝试了上述所有系统。它们都在生成运行时没有问题(即没有错误),但是当您运行它们时它们会失败。本质上,它们都给出了一些关于无法找到 cppyy-backend 的错误(例如,使用 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 设置以支持虚拟化所以我想使用一个普通的打包系统来生成某种可运行的二进制文件。

如果您知道如何让 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;
''')

【问题讨论】:

    标签: python pyinstaller packaging cppyy


    【解决方案1】:

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

    需要注意的是,我对打包运行时没有任何经验,所以我可能遗漏了一些明显的东西,但我刚刚尝试了pyinstaller,以下似乎可行。

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

    $ pyi-makespec example.py
    

    然后,将cppyy_backend 中的头文件和库添加为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 对象中的空datas 替换为:

                 datas=backend_files(),
    

    如果您还需要来自CPyCppyy 的 API 标头,则可以找到这些标头,例如像这样:

    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 []
    

    并添加到分析对象中:

                 datas=backend_files()+api_files(),
    

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

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

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

    然后将引导程序作为挂钩添加到 Analysis 对象中的规范文件:

                 runtime_hooks=['bootstrap.py'],
    

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

    最后,照常运行:

    $ pyinstaller example.spec
    

    【讨论】:

    • libcppyy.so 正在生成,但不是 libcppyy_backend.so
    • 这两个库位于不同的包中,并且是独立构建的。此外,libcppyy.so 是一个实际的扩展库。这可能是另一个区别:cppyy-clingCPyCppyy 包都包含 python 代码 cppyy-backend 不包含。 (意思是,如果“冻结”安装程序检查例如sys.modules,它将找到前两个,但不是后者来打包。)
    • 其实我刚试过pyinstaller,cppyy_backendall完全被忽略了。将其添加到规范文件的data 部分会有所改善,但会导致启动时无限递归。使用CLING_STANDARD_PCH=none 禁用它可以使其工作。让我弄清楚那个,然后我会更新答案。
    • 您是否建议类似datas=[('/home/tseymour/Desktop/cppyytest/venv/lib/python3.6/site-packages/cppyy_backend','cppyy_backend')] ?如果是这样,我肯定有某种递归尝试来重新构建预编译的头文件,但是使用set CLING_STANDARD_PCH=none 将环境变量 CLING_STANDARD_PCH 设置为 none 似乎没有效果。我会等待你的下一次更新(顺便说一句,这已经比我以前更接近解决方案了)
    • 好的,我想我赶上了你。 PLUS 上面的 datas 命令像这样运行它:CLING_STANDARD_PCH=none ./dist/app/app。虽然这有效,但它警告:UserWarning: CPyCppyy API not found (tried: /home/nogard/Desktop/cppyytest/dist/app/include); set CPPYY_API_PATH envar to the 'CPyCppyy' API directory to fix 我猜这意味着我也需要将此文件夹添加到数据中?我没有看到这样的文件夹,所以我猜没有。
    猜你喜欢
    • 2020-08-21
    • 1970-01-01
    • 1970-01-01
    • 2011-06-24
    • 2012-03-15
    • 2023-04-07
    • 2014-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多