是否有*任何*解决方案来打包使用 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
创建 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