PEP 725 – 在 pyproject.toml 中指定外部依赖项

猫勺猫勺 03-21 2.43 K 阅读

抽象

PEP 指定如何编写项目的外部或非 PyPI 构建和 打包相关工具文件中的运行时依赖项 消费。pyproject.toml

此 PEP 建议将表添加到 三个键:“build-requires”、“host-requires”和“dependencies”。这些 用于指定三种类型的依赖项:[external]pyproject.toml

build-requires,生成工具以在生成计算机上运行

host-requires,构建主机所需的依赖项,但在构建时也需要。

dependencies,在主机上运行时需要,但在生成时不需要。

通过区分构建和主机依赖关系来考虑交叉编译。 在某种程度上,还支持可选的构建时和运行时依赖项 表中的支持方式。[project]

赋予动机

Python 包可能依赖于构建工具、库、命令行 工具或 PyPI 上不存在的其他软件。目前没有办法 在标准化元数据中表达这些依赖关系。关键激励因素 该 PEP 将:

使工具能够自动将外部依赖项映射到其他 打包存储库,

使在发出的错误消息中包含所需的依赖项成为可能 Python 包安装程序和构建前端,

为包作者提供一个规范位置来记录此依赖项 信息。

Linux 发行版、Conda、Homebrew、Spack 和 Nix 等打包生态系统需要 Python 包的完整依赖集,并具有 pyp2spec (Fedora)、Grayskull (Conda) 和 dh_python (Debian) 等工具,它们试图 从 上游 Python 包。外部依赖项目前是手动处理的, 因为没有此内容或任何其他元数据 标准位置。启用自动转换是 这个 PEP,使为发行版打包 Python 包更容易、更可靠。此外, 作者设想了其他类型的工具利用这些信息,例如, 依赖分析工具,如 Repology、Dependabot 和 libraries.io。 软件物料清单 (SBOM) 生成工具也可以使用它 信息,例如用于标记 wheel 元数据中列出但未包含在 wheel 元数据中的外部依赖项可能是供应商的信息 在轮子内。pyproject.tomlpyproject.toml

具有外部依赖项的包通常很难从源代码构建, 并且来自构建失败的错误消息往往很难最终破译 用户。对最终用户系统缺少的外部依赖性是最多的 生成失败的可能原因。如果安装程序可以显示所需的外部 依赖项作为其错误消息的一部分,这可能会为用户节省大量时间。

目前,有关外部依赖项的信息仅在 单个软件包的安装文档。很难维护 包作者,往往会过时。这对用户来说也很难,而且 发行版打包器来找到它。有一个规范的位置来记录这种依赖关系 信息将改善这种情况。

这个 PEP 并没有试图指定应该如何使用外部依赖项, 也不是从单个包的名称实现名称映射的机制 对于在 PyPI 上发布的 Python 项目,这些项目与其他项目的 Python 项目是规范的 包装生态系统。这些主题应在单独的 PEP 中解决。

理由

外部依赖项的类型

可以区分多种类型的外部依赖关系:

可按名称标识并具有规范的混凝土包 位于另一个特定于语言的包存储库中。例如,Rust crates.io 上的包,CRAN 上的 R 包,npm 注册表上的 javaScript 包。

可以通过名称识别但没有明确的具体包装 规范位置。库和工具通常就是这种情况 用C,C++,fortran,CUDA和其他低级语言编写。例如, Boost、OpenSSL、Protobuf、Intel MKL、GCC。

“虚拟”包,即概念、工具类型或 接口。这些通常有多个实现,这些实现是具体的包。例如,C++编译器,BLAS,LAPACK,OpenMP,MPI。

具体包很容易理解,并且是一个存在的概念 几乎在每个包管理系统中。虚拟包是一个概念 也存在于许多包装系统中——但并非总是如此,而且 其实施的细节各不相同。

交叉编译

交叉编译尚未得到 stdlib 的良好支持(截至 2023 年 8 月) 模块和元数据。然而,重要的是,当 将外部依赖关系转换为其他打包系统的依赖关系(使用 像这样的工具)。立即引入对交叉编译的支持 在这个 PEP 中比将来扩展要容易得多,因此 作者现在选择包括这一点。pyproject.tomlpyp2spec[external]

术语

此 PEP 使用以下术语:

  • build machine:正在其上进行包构建过程的计算机 执行

  • 主机:将安装生成的工件的计算机 并运行

  • build dependency:构建需要的包的依赖 在构建时存在,并且本身是为构建计算机的操作系统构建的,并且 建筑

  • host dependency:构建需要的包的依赖项 在构建时存在,并且本身是为主机的操作系统构建的,并且 建筑

请注意,此术语在构建和打包工具之间并不一致, 因此,在将构建/主机依赖项与其他包管理器的依赖项进行比较时必须小心。pyproject.toml

请注意,此 PEP 中不使用“目标计算机”或“目标依赖项”。那 通常只与交叉编译器或其他此类高级编译器相关 场景这超出了 这个 PEP。

最后,请注意,虽然“依赖”是软件包中使用最广泛的术语 在构建时需要,PyPI 的现有密钥 构建时依赖项为 。因此,此 PEP 使用键和 under 以保持一致性。pyproject.tomlbuild-requiresbuild-requireshost-requires[external]

构建和托管依赖项

与生成和目标定义相关的元数据的清晰分离 平台,而不是假设构建和目标平台将始终是 同样,也很重要。

生成依赖项通常在生成过程中运行 - 它们可能是 编译器、代码生成器或其他此类工具。如果使用构建 依赖关系意味着运行时依赖关系,而运行时依赖关系没有 明确声明。例如,在将 Fortran 代码编译到 Python 扩展模块中时,该包可能会产生 对运行时库的依赖关系。不的理由 显式列出此类运行时依赖关系有两个方面:(1) 它可能依赖于 编译器/链接器标志或构建环境的详细信息,无论是 存在依赖关系,并且 (2) 可以检测到这些运行时依赖关系,并且 由类似 .gfortranlibgfortranauditwheel

主机依赖项通常不会在生成过程中运行,而只会使用 用于链接反对。不过,这不是一个规则——它可能是可能的,或者 在仿真器下或通过自定义工具运行主机依赖项所必需 像 crossenv 一样。当主机依赖关系隐含运行时依赖关系时,该运行时 也不必声明依赖项,就像生成依赖项一样。

当声明了主机依赖关系并且工具无法识别交叉编译时 并且必须对外部依赖项执行某些操作,该工具可能会将列表合并到 .例如,如果出现以下情况,可能会发生这种情况 安装程序(如)开始报告外部依赖项作为可能 当包无法从 SDIST 生成时,生成失败的原因。host-requiresbuild-requirespip

指定外部依赖关系

通过PURL的混凝土包装规范

PURL(包 URL)支持这两种类型的具体包,它 实现用于标识可移植包的方案 跨包装生态系统。其设计是:

scheme:type/namespace/name@version?qualifiers#subpath

该组件是固定字符串 和 另一个 仅组件,并且是必需的。例如,一个包 PyPI 上包的 URL 为:schemepkgtypenamerequests

pkg:pypi/requests

采用 PURL 来指定外部依赖关系可以解决 一次出现多个问题 - 并且已经有 Python 和多种语言的规范。PURL 也已受支持 通过与依赖相关的工具(如 SPDX)(请参阅 SPDX 2.3 规范中的外部存储库标识符), 开源漏洞格式, 和 Sonatype OSS 索引; 不必等待数年才能获得此类工具的支持,这一点很有价值。pyproject.toml

对于没有规范包管理器可参考的具体包,可以使用,也可以直接引用 VCS 系统 包在 (例如, ) 中维护。以下哪一项更合适 取决于具体情况。此 PEP 建议在 软件包名称是明确且众所周知的(例如,或),否则使用 VCS 作为 PURL 类型。pkg:generic/pkg-namepkg:Github/user-or-org-name/pkg-namepkg:genericpkg:generic/gitpkg:generic/openblas

虚拟软件包规范

PURL 或其他 PURL 中没有现成的虚拟包支持 标准。不过,此类依赖项的数量相对有限, 采用类似于 PURL 但使用 instead not 方案的方案似乎是可以理解的,并且可以很好地映射到 Linux 带有虚拟软件包的发行版以及 Conda 和 Spack 等。virtual:pkg:

两种已知的虚拟包类型是 和 。compilerinterface

版本控制

PURL 中对版本表达式和超出固定版本范围的支持是 仍处于待定状态,请参阅“未解决的问题”部分。

依赖项说明符

常规的 Python 依赖项说明符(最初在 PEP 508 中定义)可以 在 PURL 后面使用。PURL 限定符,后跟一个包 不得使用特定于类型的依赖项说明符组件。原因 这是实用的:依赖项说明符已经用于 中的其他元数据,任何与 一起使用的工具都可能 已经有一个健壮的实现来解析它。我们不希望 需要 PURL 限定符提供的额外可能性(例如,指定 Conan 或 Conda 频道,或 RubyGems 平台)。?pyproject.tomlpyproject.toml

核心元数据字段的使用

核心元数据规范包含一个相关字段,即 。这在核心元数据 2.1 中没有明确定义的语义; 此 PEP 选择将该字段重用于外部运行时依赖项。核心 元数据规范不包含表中任何元数据的字段。因此,和 内容也不需要反映在核心中 元数据字段。来自的内容需要重用或需要新字段。两者似乎都不可取。Requires-Externalpyproject.toml[build-system]build-requireshost-requiresoptional-dependencies[external]Provides-ExtraProvides-External-Extra

sdist 和 wheel 元数据之间的差异

轮子可以提供其外部依赖关系。这种情况尤其发生在以下情况下 在 PyPI 或其他 Python 包索引上分发 wheel,以及 auditwheel、delvewheel 和 delocate 等工具可以自动执行此过程。因此,sdist 中的条目可能会从构建的轮子中消失 那个 sdist。条目也可能保留在 轮子,要么不变,要么具有更窄的约束。 不 默认情况下,供应商某些允许列出的依赖项,例如 OpenGL。在 添加,并允许用户手动排除 依赖项,通过 或 命令行标志。这是 用于避免出售大型共享库,例如来自 CUDA 的库。Requires-ExternalRequires-Externalauditwheelauditwheeldelvewheel--exclude--no-dll

Requires-External因此,在 In a wheel 中从外部依赖项生成的条目允许比那些 对于相应的 SDIST。它们不能更宽,即约束不能 允许 sdist 不允许的 wheel 依赖项版本, 也不包含未在 SDIST 的元数据中列出的新依赖项,网址为 都。pyproject.toml

依赖项和拆分包的规范名称

发行版将一个包拆分为两个或多个包是相当常见的。 特别是,运行时组件通常可单独安装 开发组件(标头、pkg-config 和 CMake 文件等)。后者 然后通常有一个带有或附加到 项目/库名称。此拆分是每个发行版的责任 维护,不应反映在表中。事实并非如此 可以以适用于跨发行版的合理方式指定此值,因此 中只应使用规范名称。-dev-devel[external][external]

使用 PURL 或虚拟依赖项的预期含义是“完整的包 并指定名称”。这将取决于元数据所处的上下文 用于拆分是否相关。例如,if 是主机 依赖关系和工具想要准备一个环境来构建轮子, 然后,如果发行版已将 的标头拆分为一个包,则该工具必须同时安装 和 。libffilibffilibffi-devellibffilibffi-devel

Python 开发标头

Python 标头和其他生成支持文件也可以拆分。这是 与上一节中的情况相同(因为 Python 只是一个常规 发行版中的包)。但是,依赖项是特殊的,因为 在 Python 中,它本身是隐式的而不是显式的 屬地。因此,这里需要做出选择——隐含地添加, 或者让每个包作者在 下显式添加它。为 Python 依赖和外部依赖之间的一致性,我们选择 隐式添加它。必须假定 Python 开发标头是必需的 当表包含一个或多个编译器包时。python-dev|develpyproject.tomlpython-dev[external][external]

规范

如果元数据指定不正确,则工具必须引发错误以通知 用户关于他们的错误。

请注意,内容的格式与 PEP 621 中的格式相同。pyproject.toml

表名

工具必须在名为 的表中指定此 PEP 定义的字段。 任何工具都不得向此表添加未由此 PEP 或 随后的 PEP。缺少表格意味着包 没有任何外部依赖关系,或者假定它确实具有依赖关系 已经存在于系统上。[external][external]

build-requires/optional-build-requires

格式:PURL 字符串数组 () 和表格 替换为 PURL 字符串数组的值 (build-requiresoptional-build-requires)

核心元数据:不适用

生成项目所需的(可选)外部生成要求。

对于 ,它是一个值为字符串数组的键。每 string 表示项目的构建要求,并且必须格式化为 有效的 PURL 字符串或字符串。build-requiresvirtual:

对于 ,它是一个表,其中每个键指定一个 额外的生成要求集,其值是字符串数组。这 数组的字符串必须是有效的 PURL 字符串。optional-build-requires

host-requires/optional-host-requires

格式:PURL 字符串数组 () 和表格 替换为 PURL 字符串数组的值 (host-requiresoptional-host-requires)

核心元数据:不适用

生成项目所需的(可选)外部主机要求。

对于 ,它是一个值为字符串数组的键。每 string 表示项目的主机要求,并且必须格式化为 有效的 PURL 字符串或字符串。host-requiresvirtual:

对于 ,它是一个表,其中每个键指定一个 额外的主机要求集,其值是字符串数组。这 数组的字符串必须是有效的 PURL 字符串。optional-host-requires

dependencies/optional-dependencies

格式:PURL 字符串数组 () 和表格 替换为 PURL 字符串数组的值 (dependenciesoptional-dependencies)

核心元数据:, N/ARequires-External

项目的(可选)运行时依赖项。

对于 ,它是一个值为字符串数组的键。每 string 表示项目的依赖项,并且必须格式化为 有效的 PURL 字符串或字符串。每个字符串都直接映射到核心元数据中的一个条目。dependenciesvirtual:Requires-External

对于 ,它是一个表,其中每个键指定一个额外的 其值是字符串数组。数组的字符串必须是有效的 PURL 字符串。可选依赖项不会映射到核心元数据字段。optional-dependencies

例子

这些示例显示了许多包的内容 预计会。[external]

密码学 39.0:

[external
]build-requires = [
  "virtual:compiler/c",
    "virtual:compiler/rust", 
     "pkg:generic/pkg-config",
     ]
     host-requires = [  
     "pkg:generic/openssl",  
     "pkg:generic/libffi",
     ]

SciPy 1.10:

[external
]build-requires = [  
"virtual:compiler/c",  
"virtual:compiler/cpp",  
"virtual:compiler/fortran",  
"pkg:generic/ninja",  
"pkg:generic/pkg-config",
]host-requires = 
[  
"virtual:interface/blas",  
"virtual:interface/lapack",  # >=3.7.1 (can't express version ranges with PURL yet)
]

枕头 10.1.0:

[external
]build-requires = [  
"virtual:compiler/c",
]
host-requires = [  
"pkg:generic/libjpeg",  
"pkg:generic/zlib",
]
[external.optional-host-requires]
extra = [  
"pkg:generic/lcms2",  
"pkg:generic/freetype",  
"pkg:generic/libimagequant",  
"pkg:generic/libraqm",  
"pkg:generic/libtiff",  
"pkg:generic/libxcb",  
"pkg:generic/libwebp",  
"pkg:generic/openjpeg",  # add >=2.0 once we have version specifiers  
"pkg:generic/tk",
]

导航 1.4.0:

[project.optional-dependencies]
r = ["rpy2"]

[external
]build-requires = [  
"pkg:generic/XCB; platform_system=='Linux'",
]
[external.optional-dependencies]
nat = [
  "pkg:cran/nat",
    "pkg:cran/nat.nblast",
]

Spyder 6.0:

[external]
dependencies = [  
"pkg:cargo/ripgrep",
  "pkg:cargo/tree-sitter-cli",  
  "pkg:golang/github.com/junegunn/fzf",
  ]

jupyterlab-git 0.41.0:

[external]
dependencies = [
  "pkg:generic/git",
  ]
  [external.optional-build-requires]
  dev = [  
  "pkg:generic/nodejs",
  ]

PyEnchant 3.2.2:

[external]
dependencies = [  
# libenchant is needed on all platforms but only vendored into wheels on  
# Windows, so on Windows the build backend should remove this external  
# dependency from wheel metadata.  
"pkg:github/AbiWord/enchant",
]

向后兼容

对向后兼容性没有影响,因为此 PEP 仅添加了新的, 可选元数据。如果没有此类元数据,则包不会发生任何更改 作者或打包工具。

安全隐患

没有直接的安全问题,因为这个 PEP 涵盖了如何静态地 定义外部依赖项的元数据。任何安全问题都源于 工具如何使用元数据并选择对其采取行动。

如何教这个

外部依赖关系以及是否以及如何出售这些外部依赖关系 是 Python 包通常无法详细理解的主题 作者。我们打算从如何定义外部依赖关系开始, 它可以依赖不同的方式 - 从仅运行时或调用它作为链接的构建依赖项 - 在讨论如何在元数据中声明外部依赖项之前。这 文档应明确说明与包作者相关的内容,以及 发行版打包器有什么用。ctypessubprocess

有关此主题的材料将被添加到最相关的打包教程中, 主要是 Python 打包用户指南。此外,我们期望任何 构建后端,添加对外部依赖项元数据的支持,包括 有关这方面的信息,以及像 .auditwheel

参考实现

此 PEP 包含元数据规范,而不是代码功能 - 因此 不会有代码将元数据规范作为一个整体实现。然而 有些部件确实具有参考实现:

该表必须是有效的 TOML,因此可以加载 跟。[external]tomllib

PURL 规范作为该规范的关键部分,有一个 Python 包 带有用于构造和解析 PURL 的参考实现:packageurl-python。

此元数据有多个可能的使用者和用例,一次 该元数据被添加到 Python 包中。所有 PyPI 下载次数最多的 150 个包,其中已发布特定于平台 轮子可以在 rgommers/external-deps-build 中找到。此元数据具有 通过使用它从修补了该补丁的 SDIST 构建车轮来验证 干净的 Docker 容器中的元数据。

被拒绝的想法

外部依赖项的特定语法,这些依赖项也打包在 PyPI 上

有一些非 Python 包打包在 PyPI 上,例如 Ninja、 patchelf 和 CMake。通常需要的是使用 那些,如果它不在系统上,则安装 PyPI 包 它。作者认为,对这种情况的具体支持不是 必要(或过于复杂,无法证明这种支持是合理的);依赖项提供程序 外部依赖项可以将 PyPI 视为获取 包。

使用库和标头名称作为外部依赖项

以前的 PEP 草案(“外部依赖”(2015 年)) 建议使用特定的库和标头名称作为外部依赖项。这 太细化;使用包名称是一种成熟的模式,跨 包装生态系统,应该是首选。

未解决的问题

PURL 的版本说明符

PURL 中对版本表达式和范围的支持仍处于待定状态。拉力 PURL 的 vers 实现请求似乎即将被合并,在 这个 PEP 可以采用哪一点。

虚拟依赖项的版本控制

一旦 PURL 支持版本表达式,就可以对虚拟依赖项进行版本控制 使用相同的语法。但是,必须更好地指定版本 scheme 是,因为这对于虚拟依赖项并不像 PURLs(例如,可以有多个实现,抽象接口可以 没有明确的版本)。例如:

OpenMP:有其标准的常规版本,所以看起来 喜欢。MAJOR.MINOR>=4.5

BLAS/LAPACK:应使用参考 LAPACK 使用的版本控制,该版本控制 定义了标准 API 是什么。用途 ,所以看起来 喜欢。MAJOR.MINOR.MICRO>=3.10.0

编译器:这些实现语言标准。对于 C、C++ 和 Fortran,这些 按年份进行版本控制。为了使版本正确排序,我们选择 使用整年(四位数)。所以“至少 C99”将是 ,并且 选择 C++ 14 或 Fortran 77 将分别为 或。 其他语言可能使用不同的版本控制方案。这些应该是 在 中使用它们之前在某处进行了描述。>=1999==2014==1977pyproject.toml

一个后勤挑战是在哪里描述版本控制 - 鉴于 会随着时间的推移而发展,这个 PEP 本身并不是它的正确位置。 相反,此 PEP 应指向该(要创建的)位置。

谁定义规范名称和规范包结构

与版本控制的后勤工作类似,是关于什么名称的问题 是允许的,以及它们被描述的地方。然后谁在控制它 描述并负责维护它。我们的初步答案是:有 应该是虚拟依赖项和 PURL 的中心列表, 作为 PyPA 项目进行维护。请参见 https://discuss.python.org/t/pep-725-specifying-external-dependencies-in-pyproject-toml/31888/62。 待办事项:一旦该列表/项目被原型化,将其包含在 PEP 中并关闭 这个悬而未决的问题。pkg:generic

虚拟依赖项的语法

此 PEP 用于虚拟依赖项的当前语法是 ,它类似于 PURL 规范,但不是 PURL 规范的一部分。 这个悬而未决的问题讨论了在 PURL 中支持虚拟依赖项:purl-spec#222。virtual:type/name

是否应该在 ?

添加 PyPI 上的主机依赖项,以便 更好地支持名称映射到其他包装系统,并支持 交叉编译可能是有意义的。本期跟踪此主题 并有赞成和反对添加 under 作为此 PEP 一部分的论据。host-requireshost-requires[build-system]

版权

本文档被置于公共领域或位于 CC0-1.0-通用许可证,以更宽松的许可证为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/71.html

上一篇 下一篇

相关阅读

取消
微信二维码
微信二维码
支付宝二维码