问题描述
在创建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