我有一个 C++ 代码,通过
setuptools.Extension
生成 python 扩展。
在 setup.py 文件 中,我定义了一个 build_ext,并依赖 CMake 来构建扩展:
class BuildCMakeExtension(build_ext.build_ext):
"""Our custom build_ext command.
Uses CMake to build extensions instead of a bare compiler (e.g. gcc, clang).
"""
def run(self):
self._check_build_environment()
for ext in self.extensions:
self.build_extension(ext)
def _check_build_environment(self):
...
def build_extension(self, ext):
extension_dir = os.path.abspath(
os.path.dirname(self.get_ext_fullpath(ext.name)))
build_cfg = 'Debug' if self.debug else 'Release'
cmake_args = [
...
]
if platform.system() != 'Windows':
cmake_args.extend([
f'-DPython3_LIBRARY={sysconfig.get_paths()["stdlib"]}',
f'-DPython3_INCLUDE_DIR={sysconfig.get_paths()["include"]}',
])
if platform.system() == 'Darwin' and os.environ.get('ARCHFLAGS'):
osx_archs = []
if '-arch x86_64' in os.environ['ARCHFLAGS']:
osx_archs.append('x86_64')
if '-arch arm64' in os.environ['ARCHFLAGS']:
osx_archs.append('arm64')
cmake_args.append(f'-DCMAKE_OSX_ARCHITECTURES={";".join(osx_archs)}')
os.makedirs(self.build_temp, exist_ok=True)
subprocess.check_call(
['cmake', ext.source_dir] + cmake_args, cwd=self.build_temp)
subprocess.check_call(
['cmake', '--build', '.', f'-j{os.cpu_count()}', '--config', build_cfg],
cwd=self.build_temp)
# Force output to <extension_dir>/. Amends CMake multigenerator output paths
# on Windows and avoids Debug/ and Release/ subdirs, which is CMake default.
ct_dir = os.path.join(extension_dir, 'cytosim') # pylint:disable=unreachable
for cfg in ('Release', 'Debug'):
cfg_dir = os.path.join(extension_dir, cfg)
if os.path.isdir(cfg_dir):
for f in os.listdir(cfg_dir):
shutil.move(os.path.join(cfg_dir, f), ct_dir)
在项目的CMakeLists中,我通过
pybind11_add_module(cytosim ${PYCY_SOURCES})
和set_target_properties(cytosim PROPERTIES OUTPUT_NAME "${PY_LIBRARY_NAME}")
添加模块
这在某种程度上运作良好(不知道如何),一旦构建完成,我很高兴在正确的位置找到我的 cytosim.xxxxx.so 文件
site_packages/cytosim
。
但是,编译还会通过
add_executable(${SIM_TARGET} "${PROJECT_SOURCE_DIR}/../src/sim/sim.cc")
创建可执行文件;但它不包含在 site_packages 中。
我该怎么做才能到达那里?
我没有找到一个干净的解决方案,但我确实找到了一个基于以下内容的工作解决方案:在setuptools中分发二进制实用程序
我们无法将可执行文件列为脚本,因为脚本需要是文本。我们不能将可执行文件作为包数据,因为我们想要新编译的可执行文件(而不是可能为不同平台编译的陈旧可执行文件......)。
解决方案确实是分发一个脚本,该脚本只是已编译的可执行文件的包装器,并在构建期间将已编译的可执行文件移动到扩展目录。
就我而言,我将其移至构建扩展功能中:
def build_extension(self, ext):
extension_dir = os.path.abspath(
os.path.dirname(self.get_ext_fullpath(ext.name)))
...
parent = pathlib.Path(extension_dir).parent.absolute()
for p in os.listdir(parent):
pp = os.path.join(parent, p)
if os.path.isdir(pp) and p.startswith("temp."):
for f in os.listdir(pp):
ff = os.path.join(pp, f)
if os.access(ff, os.X_OK):
if f=="foo":
pybin = os.path.join(extension_dir, "foowrapper")
if not os.path.isdir(pybin):
os.mkdir(pybin)
shutil.move(ff, os.path.join(pybin, "wrapped_foo"))
这里我将可执行文件“foo”移动到文件夹“foowrapper”,并将其重命名为“wrapped_foo”;而“foo”将是一个包装脚本,它调用“wrapped_foo”:
#!python
import foowrapper as fw
import os,sys
import subprocess
""" A wrapper for foo """
path = os.path.dirname(fw.__file__)
exe = os.path.join(path, "wrapped_foo")
subprocess.call([exe] + sys.argv[1:])