将预编译的扩展放在非纯 Python Wheel 包的根文件夹中
Place pre-compiled extensions in root folder of non-pure Python Wheel package
TL;DR 如何让 distutils/setuptools 正确包含非纯数据文件?
我有一个项目使用自定义工具链生成一些重要的代码,然后用 SWIG 包装生成的代码,最后构建 Python 扩展。 Cmake 很好地封装了所有这些,最后我在项目的根目录中有一个文件夹,它与任何其他 Python 包完全一样工作。
我想要一个简单的 setup.py
这样我就可以将这个包打包成一个轮子并将其发送到 PyPI 这样普通的 Python 用户就不必处理构建过程。 SO 上有很多关于如何强制设置工具生成非纯轮子的答案,然后您可以使用 package_data
字段或 MANIFEST.in
文件捆绑扩展。
问题是此方法会导致轮子格式错误,因为扩展名包含在 purelib
而不是根目录下(它们属于 Root-Is-Pure: False
轮子)。一些工具和发行版依赖于这种正确的分离。
我不感兴趣的答案:从 setup.py
中对 运行 cmake 的自定义扩展(不想为配置项目添加另一层间接,不想在构建选项更改时维护它),修改生成的轮子,我宁愿避免向项目根目录添加任何文件,而不仅仅是 setup.py
这行得通。 distutils
和 setuptools
必须是现有中央 Python 基础设施中设计最差的部分。
from setuptools import setup, find_packages, Extension
from setuptools.command.build_ext import build_ext
import os
import pathlib
import shutil
suffix = '.pyd' if os.name == 'nt' else '.so'
class CustomDistribution(Distribution):
def iter_distribution_names(self):
for pkg in self.packages or ():
yield pkg
for module in self.py_modules or ():
yield module
class CustomExtension(Extension):
def __init__(self, path):
self.path = path
super().__init__(pathlib.PurePath(path).name, [])
class build_CustomExtensions(build_ext):
def run(self):
for ext in (x for x in self.extensions if isinstance(x, CustomExtension)):
source = f"{ext.path}{suffix}"
build_dir = pathlib.PurePath(self.get_ext_fullpath(ext.name)).parent
os.makedirs(f"{build_dir}/{pathlib.PurePath(ext.path).parent}",
exist_ok = True)
shutil.copy(f"{source}", f"{build_dir}/{source}")
def find_extensions(directory):
extensions = []
for path, _, filenames in os.walk(directory):
for filename in filenames:
filename = pathlib.PurePath(filename)
if pathlib.PurePath(filename).suffix == suffix:
extensions.append(CustomExtension(os.path.join(path, filename.stem)))
return extensions
setup(
# Stuff
ext_modules = find_extensions("PackageRoot"),
cmdclass = {'build_ext': build_CustomExtensions}
distclass = CustomDistribution
)
我正在将扩展复制到构建目录中,仅此而已。我们覆盖了分布以欺骗 egg-info 作者关于有任何扩展,一切都是肉汁。
TL;DR 如何让 distutils/setuptools 正确包含非纯数据文件?
我有一个项目使用自定义工具链生成一些重要的代码,然后用 SWIG 包装生成的代码,最后构建 Python 扩展。 Cmake 很好地封装了所有这些,最后我在项目的根目录中有一个文件夹,它与任何其他 Python 包完全一样工作。
我想要一个简单的 setup.py
这样我就可以将这个包打包成一个轮子并将其发送到 PyPI 这样普通的 Python 用户就不必处理构建过程。 SO 上有很多关于如何强制设置工具生成非纯轮子的答案,然后您可以使用 package_data
字段或 MANIFEST.in
文件捆绑扩展。
问题是此方法会导致轮子格式错误,因为扩展名包含在 purelib
而不是根目录下(它们属于 Root-Is-Pure: False
轮子)。一些工具和发行版依赖于这种正确的分离。
我不感兴趣的答案:从 setup.py
中对 运行 cmake 的自定义扩展(不想为配置项目添加另一层间接,不想在构建选项更改时维护它),修改生成的轮子,我宁愿避免向项目根目录添加任何文件,而不仅仅是 setup.py
这行得通。 distutils
和 setuptools
必须是现有中央 Python 基础设施中设计最差的部分。
from setuptools import setup, find_packages, Extension
from setuptools.command.build_ext import build_ext
import os
import pathlib
import shutil
suffix = '.pyd' if os.name == 'nt' else '.so'
class CustomDistribution(Distribution):
def iter_distribution_names(self):
for pkg in self.packages or ():
yield pkg
for module in self.py_modules or ():
yield module
class CustomExtension(Extension):
def __init__(self, path):
self.path = path
super().__init__(pathlib.PurePath(path).name, [])
class build_CustomExtensions(build_ext):
def run(self):
for ext in (x for x in self.extensions if isinstance(x, CustomExtension)):
source = f"{ext.path}{suffix}"
build_dir = pathlib.PurePath(self.get_ext_fullpath(ext.name)).parent
os.makedirs(f"{build_dir}/{pathlib.PurePath(ext.path).parent}",
exist_ok = True)
shutil.copy(f"{source}", f"{build_dir}/{source}")
def find_extensions(directory):
extensions = []
for path, _, filenames in os.walk(directory):
for filename in filenames:
filename = pathlib.PurePath(filename)
if pathlib.PurePath(filename).suffix == suffix:
extensions.append(CustomExtension(os.path.join(path, filename.stem)))
return extensions
setup(
# Stuff
ext_modules = find_extensions("PackageRoot"),
cmdclass = {'build_ext': build_CustomExtensions}
distclass = CustomDistribution
)
我正在将扩展复制到构建目录中,仅此而已。我们覆盖了分布以欺骗 egg-info 作者关于有任何扩展,一切都是肉汁。