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

问题描述

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

我有一个python GUI应用程序,它使用庞大且不断变化的c ++库,因此我不能只用python重新编写它。我已经成功地编写了使用c ++库的python代码,并使用了名为cppyy的惊人(甚至可能是神奇的)库,该库使您可以毫不费力地从python运行c ++代码。一切都可以在Linux,mac和Windows上很好地运行,但是我无法将其打包到运行时中,并且我已经尝试了上面的所有系统。它们全部都不会产生运行时问题(即没有错误),但是当您运行它们时它们会失败。从本质上讲,它们都给无法找到cppyy后端提供了某种错误(例如,运行bin时使用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,module.__dict__)
  File "cppyy/_cpython_cppyy.py",line 20,in <module>
  File "cppyy_backend/loader.py",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;
''')

解决方法

编辑:找出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)]

并将datas对象中的空Analysis替换为:

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

并添加到Analysis对象:

             datas=backend_files()+api_files(),

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

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

但是,loader.py中的cppyy_backend脚本使用的是sys.executable,它被冻结打破了(意味着,它是顶级脚本,而不是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

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

             runtime_hooks=['bootstrap.py'],

如上所述,bootstrap.py还是添加必要的更多包含路径的好地方,例如为Python.h

最后,像往常一样运行:

$ pyinstaller example.spec