尧图精选

Poetry 扩展模块构建指南:用 Build Script 在项目中编译 Cython、Meson 与 Maturin 原生扩展

🕒 发布时间:2026/9/6 20:42:38 📁 来源:尧图网络
Poetry 扩展模块构建指南用 Build Script 在项目中编译 Cython、Meson 与 Maturin 原生扩展【免费下载链接】poetryPython packaging and dependency management made easy项目地址: https://gitcode.com/GitHub_Trending/po/poetryPoetry 允许项目开发者在自己的项目中引入、构建并分发原生扩展native extension。本篇指南基于 Poetry 仓库的 Building extension modules 文档完整讲解声明构建依赖 → 编写构建脚本 → 指定分发文件三步流程并结合 Cython、Meson、Maturin 三套可直接复用的示例片段深入源码解释构建脚本究竟在何时、以何种隔离环境被 Poetry 执行。读完本篇你将能够为含 C/C/Rust 扩展的 Python 项目配置完整的 PEP 517 构建链路并理解poetry install/poetry build背后的隔离构建环境机制。功能定位与稳定性声明该功能自 Poetry 项目早期就存在且几乎无需改动但目前仍被视为不稳定特性文档明确以 warning 标注社区关于其稳定化的讨论见上游 issue #2740。这意味着配置语法未来可能发生变化用于生产项目时建议锁定 Poetry 版本并关注上游进展。在最高抽象层面构建原生扩展需要完成三步添加构建依赖build-system.requires添加构建脚本tool.poetry.build.script指定分发文件include/exclude第一步声明构建依赖此处的构建依赖指成功执行你的构建脚本所必需的 Python 包。常见例子包括cython、meson、maturin、setuptools等具体取决于你的扩展如何构建。关键前提构建环境中默认只有 Python 内置库。即使你觉得setuptools一定存在也必须显式声明——因为隔离构建环境是一个干净的空环境这一行为在源码 build_environment 中可见它会先创建一个临时虚拟环境再逐一安装build-system.requires中列出的依赖。构建依赖必须写入pyproject.toml的[build-system]段[build-system] requires [poetry-core, setuptools, cython] build-backend poetry.core.masonry.api建议为构建依赖添加版本约束文档建议为build-system.requires中的每一项都指定版本约束以避免依赖包引入破坏性变更时的意外。例如将cython约束为requires [poetry-core, setuptools, cython3.0.11,4.0.0]这样可确保构建环境不会用到不兼容的主版本。补充在虚拟环境中开发构建脚本如果你希望在项目的虚拟环境内开发调试构建脚本而不仅仅是在隔离构建环境中运行还必须把这些依赖显式加入某个依赖组组名随意例如poetry add --groupbuild setuptools cython原因是build-system.requires只在隔离构建环境中被安装项目自身的 venv 里并不会自动出现这些包。第二步添加构建脚本构建脚本是一个自由的 Python 脚本可以使用第一步声明的任何依赖。它需要满足三个硬性条件文件名可以任意惯例上叫build.py但不强制必须位于项目根目录内根目录本身或某个子目录中必须被打进 source distributionsdist因为其他构建前端从 sdist 触发 PEP 517 构建时也需要它。在pyproject.toml中通过[tool.poetry.build]段声明[tool.poetry.build] script relative/path/to/build-extension.py两个执行约定与源码行为一致构建脚本始终从项目根目录被启动。在 EditableBuilder._run_build_script 中可以看到Poetry 实际执行的是python 项目根/build.py所以脚本中可以使用相对路径操作项目文件也可以像示例那样用Path(__file__).parent定位自身。构建脚本负责把产物移动到pyproject.toml中include声明所期望的位置——Poetry 不会替你搬运文件脚本是搬运工。从源码结构看当pyproject.toml声明了build_script后构建链路会切换到隔离构建poetry build 命令 的_requires_isolated_build()逻辑表明只要存在构建脚本或构建依赖不止一个、或不是poetry-core就会走 isolated_build 隔离构建路径而不是直接用当前安装的poetry-core。仓库测试 fixture 中就有一个真实的完整配置可以参考——extended_with_no_setup/pyproject.toml[tool.poetry] name extended ... include [ # C extensions must be included in the wheel distributions {path extended/*.so, format wheel}, {path extended/*.pyd, format wheel}, ] [tool.poetry.build] script build.py generate-setup-file false [build-system] requires [poetry-core1.5.0, setuptools67.6.1] build-backend poetry.core.masonry.api注意其中的generate-setup-file选项当它生效时见 EditableBuilder.buildPoetry 会回退到生成临时setup.py并用 pip 以 editable 方式安装而不是执行你的构建脚本。对现代 PEP 517 项目通常应设为false。第三步指定分发文件文档给出的示例明确标注仅为示例并非完整[tool.poetry] ... include [ { path package/**/*.so, format wheel }, # sources must be present in sdist, can be ignored if you only have *.pyx sources { path package/**/*.c, format sdist }, ]各字段含义format取值wheel/sdist控制文件进入哪种分发格式的详细规则参见 pyproject.toml 文档的 exclude-and-include 章节。这一步的核心结论只有三条务必记牢把构建产物编译出的.so/.pydinclude 进wheel把构建输入如 Cython 生成的中间.c文件从 wheel 中exclude把构建输入.c或.pyx源文件include 进sdist——因为 sdist 用户需要能重新编译。示例片段一Cython这是文档中唯一给出三件套pyproject 构建脚本 目录结构的完整示例。pyproject.toml[build-system] requires [poetry-core, cython, setuptools] build-backend poetry.core.masonry.api [tool.poetry] ... packages [ { include package, from src}, ] include [ { path src/package/**/*.so, format wheel }, ] # if not already excluded via .gitignore exclude [ **/*.c ] [tool.poetry.build] script scripts/build-extension.pyscripts/build-extension.pyfrom __future__ import annotations import os import shutil from pathlib import Path from Cython.Build import cythonize from setuptools import Distribution from setuptools import Extension from setuptools.command.build_ext import build_ext COMPILE_ARGS [-marchnative, -O3, -msse, -msse2, -mfma, -mfpmathsse] LINK_ARGS [] INCLUDE_DIRS [] LIBRARIES [m] def build() - None: extensions [ Extension( *, [src/package/*.pyx], extra_compile_argsCOMPILE_ARGS, extra_link_argsLINK_ARGS, include_dirsINCLUDE_DIRS, librariesLIBRARIES, ) ] ext_modules cythonize( extensions, include_pathINCLUDE_DIRS, compiler_directives{binding: True, language_level: 3}, ) distribution Distribution({ name: package, ext_modules: ext_modules }) cmd build_ext(distribution) cmd.ensure_finalized() cmd.run() # Copy built extensions back to the project for output in cmd.get_outputs(): output Path(output) relative_extension Path(src) / output.relative_to(cmd.build_lib) shutil.copyfile(output, relative_extension) mode os.stat(relative_extension).st_mode mode | (mode 0o444) 2 os.chmod(relative_extension, mode) if __name__ __main__: build()脚本逻辑拆解用setuptools的build_ext命令在临时build/目录下编译.pyx→.so然后把产物拷贝回源码树src/package/这样第三步的include { path src/package/**/*.so, format wheel }才能命中末尾的os.chmod是把读权限提升为执行权限0o444 2技巧确保.so在打包后仍具备可执行位。源码树结构scripts/ └── build-extension.py src/ └── package ├── example.pyx └── __init__.py示例片段二Mesonpyproject.toml[tool.poetry.build] script build-extension.py [build-system] requires [poetry-core, meson] build-backend poetry.core.masonry.apibuild-extension.pyfrom __future__ import annotations import subprocess from pathlib import Path def meson(*args): subprocess.call([meson, *args]) def build(): build_dir Path(__file__).parent.joinpath(build) build_dir.mkdir(parentsTrue, exist_okTrue) meson(setup, build_dir.as_posix()) meson(compile, -C, build_dir.as_posix()) meson(install, -C, build_dir.as_posix()) if __name__ __main__: build()要点Meson 本身不在build-system.requires之外做任何事——它只是通过subprocess驱动meson setup / compile / install三条命令install步骤按meson.build中的配置把产物落到项目内供include规则拾取。requires里声明meson的作用就是保证隔离构建环境中有meson可执行文件。示例片段三MaturinRustpyproject.toml[tool.poetry.build] script build-extension.py [build-system] requires [poetry-core, maturin] build-backend poetry.core.masonry.apibuild-extension.pyimport os import shlex import shutil import subprocess import zipfile from pathlib import Path def maturin(*args): subprocess.call([maturin, *list(args)]) def build(): build_dir Path(__file__).parent.joinpath(build) build_dir.mkdir(parentsTrue, exist_okTrue) wheels_dir Path(__file__).parent.joinpath(target/wheels) if wheels_dir.exists(): shutil.rmtree(wheels_dir) cargo_args [] if os.getenv(MATURIN_BUILD_ARGS): cargo_args shlex.split(os.getenv(MATURIN_BUILD_ARGS, )) maturin(build, -r, *cargo_args) # We wont use the wheel built by maturin directly since # we want Poetry to build it, but we need to retrieve the # compiled extensions from the maturin wheel. wheel next(iter(wheels_dir.glob(*.whl))) with zipfile.ZipFile(wheel.as_posix()) as whl: whl.extractall(wheels_dir.as_posix()) for extension in wheels_dir.rglob(**/*.so): shutil.copyfile(extension, Path(__file__).parent.joinpath(extension.name)) shutil.rmtree(wheels_dir) if __name__ __main__: build()要点Maturin 会顺带产出一个 wheel但脚本刻意不使用它注释中明确说明我们要让 Poetry 来构建 wheel只从 maturin 的 wheel 中把编译好的.so抽取出来拷回项目根目录让 Poetry 自己打包——这样打包格式、元数据、文件名全部由poetry-core掌控符合 PEP 517 的职责划分。此外脚本支持通过环境变量MATURIN_BUILD_ARGS透传额外的 cargo 参数用shlex.split做安全分词构建产物目录target/wheels每次先清空保证幂等。FAQ构建脚本的运行时机与依赖保障构建脚本何时被执行只要项目声明了构建脚本它在以下场景都会被隐式执行poetry install时——在安装项目自身的根包之前执行poetry build时——在构建分发文件之前执行其他构建前端如 pip 以 PEP 517 方式从源码或 sdist 触发构建时。场景 1 对应源码路径EditableBuilder.build()在写.pth/scripts/dist-info 之前先调用_run_build_script场景 2 对应poetry build命令选择_isolated_build路径见前文_requires_isolated_build场景 3 则由poetry.core.masonry.api构建后端承接Poetry 侧的 IsolatedBuildBackendError 会给出指向构建后端而非 Poetry 本身的诊断提示。Poetry 如何保证构建脚本的依赖被满足执行构建脚本前Poetry 会创建一个临时虚拟环境使用项目的当前活跃 Python 版本把build-system.requires中的全部依赖安装进去。特别注意环境创建时是空的——没有任何预先存在的包。源码印证在 build_environment 上下文管理器一旦检测到poetry.package.build_script就调用ephemeral_environment建临时 venv并通过 IsolatedEnv 用项目自己的仓库池RepositoryPool解析并安装这些依赖安装失败时抛出带有完整 stdout/stderr 的IsolatedBuildInstallError。这就是必须显式声明 setuptools 等一切依赖的底层原因。构建脚本能否放在子目录中可以。只要路径相对于项目根目录正确即可。完整示例文档 FAQ 原文配置[tool.poetry] ... packages [ { include package, from src} ] include [ { path src/package/**/*.so, format wheel }, ] # exclude any intermediate source files exclude [ **/*.c ] [tool.poetry.build] script scripts/build-extension.py这正是上文 Cython 示例采用的布局scripts/build-extension.py也是文档建议的若可行应尽量使用子目录的做法——避免构建脚本与项目源码混在根目录。小结与检查清单把整条链路压缩成一张可自查的清单检查项位置验证方式poetry-core在 requires 中[build-system].requires缺省会触发 build 命令的 warning 并回退到内置 poetry-core每个构建依赖都有版本约束[build-system].requires建议防止破坏性升级脚本路径相对根目录且位于项目内[tool.poetry.build].scriptEditableBuilder 以根目录拼接执行脚本会把产物移到include命中的位置构建脚本本体脚本负责搬运Poetry 不代劳产物.so/.pyd进 wheelincludeformat wheel参考 extended_with_no_setup fixture中间文件.c进 sdist 且不进 wheelincludeexclude参考文档 Cython 片段构建脚本随 sdist 分发include默认根目录文件均入 sdist子目录脚本需确认未被排除需要再次强调的适用前提此特性目前仍被 Poetry 官方标记为不稳定配置语法可能随上游稳定化issue #2740 的讨论而调整隔离构建依赖项目的仓库池来安装requires因此私有源配置repositories/[[tool.poetry.source]]同样会作用到构建环境。【免费下载链接】poetryPython packaging and dependency management made easy项目地址: https://gitcode.com/GitHub_Trending/po/poetry创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →