PEP 639 – 通过改进软件包元数据提升许可证清晰度
- 作者:
- Philippe Ombredanne <pombredanne at nexb.com>,C.A.M. Gerlach <CAM.Gerlach at Gerlach.CAM>,Karolina Surma <karolina.surma at gazeta.pl>
- PEP 代表:
- Brett Cannon <brett at python.org>
- 讨论列表:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2019年8月15日
- 更新历史:
- 2019年8月15日,2021年12月17日,2024年5月10日
- 决议:
- Discourse 消息
草案接受
本 PEP 已 **草案接受**,在 PEP 成为最终版本之前,需要满足以下条件:
- 在两个构建后端中实现 PEP。
- 在 PyPI 中实现 PEP。
摘要
本 PEP 定义了 Python 项目中许可证文档的规范。
为了实现这一目标,它
- 采用 SPDX 许可证表达式语法 作为表达 Python 项目许可证的方式。
- 定义如何在项目、源代码和构建分发版中包含许可证文件。
- 指定对 核心元数据 和相应的 Pyproject 元数据键 的必要更改。
- 描述对 源代码分发 (sdist)、构建分发 (wheel) 和 已安装项目 标准的必要更改。
这将使许可证声明对于软件包作者更容易创建、最终用户更容易理解以及工具更容易以编程方式处理,从而减少歧义。
这些更改将 核心元数据规范 更新到 2.4 版。
目标
本 PEP 的范围仅限于涵盖用于记录 分发包 许可证的新机制,具体定义
本 PEP 所需的更改旨在最大程度地减少影响并最大程度地提高向后兼容性。
非目标
本 PEP 并不建议任何特定软件包作者选择任何特定许可证。
如果项目决定不使用新字段,则本 PEP 在上传到 PyPI 时不会施加任何其他限制。
本 PEP 也与单个文件的许可证文档无关,尽管这是附录中 调查的主题,也不打算涵盖 源代码分发 和 二进制分发 包 许可证不同 的情况。
动机
软件必须获得许可,以便除创建者以外的任何人可以下载、使用、共享和修改它。如今,在 核心元数据 中有多个字段用于记录许可证,并且每个字段所能表达的内容都存在限制。这经常导致软件包作者和最终用户(包括分发重新打包者)感到困惑。
这引发了许多与许可证相关的讨论和问题,包括 过时且模棱两可的 PyPI 分类器、与其他生态系统的许可证互操作性、太多令人困惑的许可证元数据选项、Wheel 项目对许可证文件的支持有限 以及 缺少精确的许可证元数据。
结果,平均而言,Python 软件包的许可证信息比其他常见生态系统更加模糊和缺失。这得到了 ClearlyDefined 项目(开源计划 旨在帮助提高其他 FOSS 项目的许可证清晰度)的 统计页面 的支持,该项目涵盖了来自 PyPI、Maven、npm 和 Rubygems 的所有软件包。
当前的许可证分类器可以扩展为包含 SPDX 标识符的完整范围,同时弃用模棱两可的分类器(例如 License :: OSI Approved :: BSD License
)。
但是,有许多论点反对这种方法
- 需要付出巨大的努力来复制 SPDX 许可证列表并保持同步。
- 这会造成向后兼容性的重大破坏,迫使软件包作者在 PyPI 弃用旧分类器时立即更新到新分类器。
- 它仅涵盖单一许可证下的软件包;它没有解决供应商依赖项(例如 Setuptools)、提供许可证选择(例如 Packaging)或重新许可、从其他项目改编代码或包含其他许可证下的字体、图像、示例、二进制文件或其他资产的项目。
- 它要求作者和工具都理解并实现 PyPI 特定的分类器系统。
- 它没有提供一个明确的指示,表明软件包已采用新系统,并且应该相应地处理。
基本原理
进行了一项调查,以映射 Python 生态系统 和 各种其他打包系统、Linux 发行版、语言生态系统和应用程序 中现有的许可证元数据定义。
调查结果指导了本 PEP 的建议
因此,本 PEP 引入了两个新的核心元数据字段
- License-Expression,它提供了一种使用 SPDX 许可证表达式无歧义地表达软件包许可证的方式。
- License-File,它提供了一种标准化的方法,在分发软件包时包含许可证的完整文本,并允许使用 核心元数据 的其他工具查找 分发存档 的许可证文件。
此外,本规范建立在 Setuptools 和 Wheel 项目中现有实践的基础上。本 PEP 当前草案的最新版本已在 Hatch 打包工具中 实现,并且 许可证文件部分 的早期草案已在 Setuptools 中 实现。
术语
本文档中的关键字“必须”、“禁止”、“需要”、“应该”、“不应该”、“推荐”、“可能”和“可选”应按 RFC 2119 中所述进行解释。
许可证条款
许可证相关的术语主要来自 SPDX 项目,特别是 许可证标识符 和 许可证表达式。
- 许可证分类器
- PyPI Trove 分类器(如 所述 核心元数据 规范中)以
License ::
开头。 - 许可证表达式
- SPDX 表达式
- 包含一个或多个 SPDX 许可证标识符的有效 SPDX 许可证表达式语法 字符串,用于描述 项目 的许可证及其相互关系。例如:
GPL-3.0-or-later
、MIT AND (Apache-2.0 OR BSD-2-clause)
- 许可证标识符
- SPDX 标识符
- 有效的 SPDX 简写许可证标识符,如本 PEP 中 添加 License-Expression 字段 部分所述。这包括所有有效的 SPDX 标识符以及符合 SPDX 规范,第 10.1 条 的自定义
LicenseRef-[idstring]
字符串。例如:MIT
、GPL-3.0-only
、LicenseRef-My-Custom-License
- 根许可证目录
- 许可证目录
- 存储许可证文件的目录,位于 项目源代码树、发行版归档文件 或 已安装项目 中。此外,License-File 核心元数据字段 中记录的路径相对于该根目录。对于 项目源代码树 或 源代码发行版,定义为 项目根目录;对于 构建发行版 或 已安装项目,定义为包含 构建元数据 的目录(即
.dist-info/licenses
目录)的子目录licenses
。
规范
实施本 PEP 所需的更改包括
- 对 核心元数据 的添加,如 规范 中所定义。
- 对作者提供的 项目源元数据 的添加,如 规范 中所定义。
- 添加 到源代码发行版 (sdist)、构建发行版 (wheel) 和已安装项目规范中。
- 工具指南 处理和转换旧版许可证元数据到许可证表达式,以确保结果一致且正确。
请注意,关于错误和警告的指南适用于工具的默认行为;如果用户明确配置它们这样做,例如通过 CLI 标志或配置选项,它们可能会运行得更严格。
SPDX 许可证表达式语法
本 PEP 采用 SPDX 许可证表达式语法,如 SPDX 规范 中所记录,版本 2.2 或更高兼容版本。
许可证表达式可以使用以下 许可证标识符
- 在 SPDX 许可证列表 版本 3.17 或任何更高兼容版本中发布的任何 SPDX 列出的许可证简写标识符。请注意,SPDX 工作组从不删除任何许可证标识符;相反,他们可能会选择将某个标识符标记为“已弃用”。
- 自定义
LicenseRef-[idstring]
字符串,其中[idstring]
是一个包含字母、数字、.
和/或-
的唯一字符串,用于标识未包含在 SPDX 许可证列表中的许可证。自定义标识符必须遵循 SPDX 规范,给定规范版本的 第 10.1 条。
有效 SPDX 表达式的示例
MIT
BSD-3-Clause
MIT AND (Apache-2.0 OR BSD-2-Clause)
MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)
GPL-3.0-only WITH Classpath-Exception-2.0 OR BSD-3-Clause
LicenseRef-Special-License OR CC0-1.0 OR Unlicense
LicenseRef-Proprietary
无效 SPDX 表达式的示例
Use-it-after-midnight
Apache-2.0 OR 2-BSD-Clause
LicenseRef-License with spaces
LicenseRef-License_with_underscores
核心元数据
本节中的错误和警告指南适用于构建和发布工具;当遇到不符合本规范的格式错误的元数据时,面向最终用户的安装工具可能比此处提到的更宽松。
由于它添加了新字段,因此本 PEP 将核心元数据版本更新为 2.4。
添加 License-Expression
字段
可选的 License-Expression
核心元数据字段 指定为包含一个文本字符串,该字符串是一个有效的 SPDX 许可证表达式,如 上面所定义。
构建和发布工具**应该**检查 License-Expression
字段是否包含有效的 SPDX 表达式,包括特定许可证标识符的有效性(如 上面所定义)。当发现无效表达式时,工具**可以**停止执行并引发错误。如果工具选择验证 SPDX 表达式,它们也**应该**使用每个 SPDX 许可证标识符的参考大小写以及 AND
、OR
和 WITH
关键字的大写来存储 License-Expression
字段的大小写规范化版本。如果一个或多个许可证标识符在 SPDX 许可证列表 中被标记为已弃用,则工具**应该**报告警告,发布工具**可以**引发错误。
对于所有包含 License-Expression
字段的新上传的 发行版归档文件,Python 包索引 (PyPI) **必须**验证它们是否包含一个有效的、大小写规范化的许可证表达式以及有效的标识符(如 上面所定义),并且**必须**拒绝不符合要求的上传。符合 SPDX 规范的自定义许可证标识符被视为有效。PyPI **可以**拒绝使用已弃用许可证标识符的上传,只要它在上述 SPDX 许可证列表版本中被弃用。
添加 License-File
字段
License-File
是一个可选的 核心元数据字段。每个实例包含与许可证相关的文件的路径的字符串表示形式。该路径位于 项目源代码树 中,相对于 项目根目录。它是一个多用途字段,可以出现零次或多次,并且每个实例列出一个此类文件的路径。此字段下指定的文件可能包括许可证文本、作者/归属信息或需要与包一起分发的其他法律声明。
如 本 PEP 中所指定,其值也是该文件相对于 根许可证目录 的路径,无论是在 已安装项目 中还是在标准化的 发行版包 类型中。
如果 License-File
列在 源代码发行版 或 构建发行版 的核心元数据中
- 该文件**必须**包含在 发行版归档文件 中,路径相对于根许可证目录。
- 该文件**必须**与 项目 一起安装,路径相同。
- 指定的相对路径**必须**在项目源代码树、源代码发行版 (sdist)、构建发行版(Wheel)和已安装项目之间保持一致。
- 在根许可证目录内,打包工具**必须**复制源许可证文件相对于项目根目录所在的目录结构。
- 路径分隔符**必须**是正斜杠字符 (
/
),并且**不能**使用父目录指示符 (..
)。 - 许可证文件内容**必须**是 UTF-8 编码的文本。
构建工具**可以**,发布工具**应该**在构建发行版的元数据不包含 License-File
条目时发出提示性警告,发布工具**可以**,但构建工具**不能**引发错误。
对于所有在其核心元数据中包含一个或多个 License-File
字段并在其 Metadata-Version
中声明 2.4
或更高版本的新上传的 发行版归档文件,PyPI **应该**验证所有指定的文件是否都存在于该 发行版归档文件 中,并且**必须**拒绝未通过验证的上传。
弃用 License
字段
传统的非结构化文本 License
核心元数据字段 已被弃用,并由新的 License-Expression
字段取代。这两个字段是互斥的。生成核心元数据的工具**不得**同时创建这两个字段。读取核心元数据的工具,当同时遇到这两个字段时,**必须**读取 License-Expression
的值,并且**必须**忽略 License
字段的值。
如果只有 License
字段存在,工具**可以**发出警告,告知用户该字段已弃用,并建议使用 License-Expression
代替。
对于所有新上传的包含 License-Expression
字段的 发行版归档文件,Python 包索引 (PyPI) **必须**拒绝任何同时指定 License
和 License-Expression
字段的发行版归档文件。
License
字段可能会在未来某个 PEP 中从规范的新版本中移除。
弃用许可证分类器
在 Classifier
核心元数据字段 (在核心元数据规范中描述) 中使用 许可证分类器 已被弃用,并由更精确的 License-Expression
字段取代。
如果存在 License-Expression
字段,构建工具**可以**在 Classifier
字段中包含一个或多个许可证分类器时引发错误,并且**不得**自行添加此类分类器。
否则,如果该字段包含许可证分类器,工具**可以**发出警告,告知用户此类分类器已弃用,并建议使用 License-Expression
代替。为了与现有的发布和安装流程兼容,除非也提供了 License-Expression
,否则许可证分类器的存在**不应**引发错误。
**不得**将新的许可证分类器 添加到 PyPI;需要使用它们的用户**应**改用 License-Expression
字段。许可证分类器可能会在未来某个 PEP 中从规范的新版本中移除。
项目源元数据
本 PEP 指定了对项目源元数据在 pyproject.toml
文件中 [project]
表下的更改。
向 license
键添加字符串值
pyproject.toml
文件中 [project]
表中的 license
键被定义为包含一个顶级字符串值。它是一个有效的 SPDX 许可证表达式,如本 PEP 中所定义。其值映射到核心元数据中的 License-Expression
字段。
构建工具**应**验证并执行表达式的案例规范化,如 添加 License-Expression 字段 部分所述,并根据指定输出错误或警告。
示例
[project]
license = "MIT"
[project]
license = "MIT AND (Apache-2.0 OR BSD-2-clause)"
[project]
license = "MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)"
[project]
license = "LicenseRef-Proprietary"
添加 license-files
键
一个新的 license-files
键被添加到 [project]
表中,用于指定项目源代码树中相对于 pyproject.toml
的路径,指向包含许可证和其他法律声明的文件,这些文件将与包一起分发。它对应于核心元数据中的 License-File
字段。
其值是一个字符串数组,**必须**包含有效的 glob 模式,如下所述
- 字母数字字符、下划线 (
_
)、连字符 (-
) 和点 (.
) **必须**逐字匹配。 - 特殊 glob 字符:
*
、?
、**
和字符范围:[]
,其中只包含逐字匹配的字符**必须**被支持。在[...]
中,连字符表示不区分区域设置的范围(例如a-z
,基于 Unicode 代码点的顺序)。开头或结尾的连字符将逐字匹配。 - 路径分隔符**必须**是正斜杠字符 (
/
)。模式相对于包含pyproject.toml
的目录,因此**不得**使用前导斜杠字符。 - **不得**使用父目录指示符 (
..
)。
本规范未涵盖的任何字符或字符序列均无效。项目**不得**使用此类值。使用此字段的工具**应**使用错误拒绝无效值。
工具**必须**假设许可证文件内容是有效的 UTF-8 编码文本,并且**应**验证这一点,如果它不是,则引发错误。
字面路径(例如 LICENSE
)被视为有效的 glob,这意味着它们也可以被定义。
构建工具
- **必须**将每个值视为一个 glob 模式,并且**必须**在模式包含无效的 glob 语法时引发错误。
- **必须**在所有发行版归档文件中包含列出的模式匹配的所有文件。
- **必须**在核心元数据中的
License-File
字段下列出每个匹配的文件路径。 - **必须**在任何单个用户指定的模式至少不匹配一个文件时引发错误。
如果 license-files
键存在且设置为一个空数组的值,则工具**不得**包含任何许可证文件,并且**不得**引发错误。
有效的许可证文件声明示例
[project]
license-files = ["LICEN[CS]E*", "AUTHORS*"]
[project]
license-files = ["licenses/LICENSE.MIT", "licenses/LICENSE.CC0"]
[project]
license-files = ["LICENSE.txt", "licenses/*"]
[project]
license-files = []
无效的许可证文件声明示例
[project]
license-files = ["..\LICENSE.MIT"]
原因:**不得**使用 ..
。 \
是无效的路径分隔符,**必须**使用 /
。
[project]
license-files = ["LICEN{CSE*"]
原因:“LICEN{CSE*” 不是有效的 glob。
弃用 license
键表子键
[project]
表中 license
键的表值,包括 text
和 file
表子键,现在已弃用。如果存在新的 license-files
键,则如果 license
键已定义且其值不是单个顶级字符串,构建工具**必须**引发错误。
如果不存在新的 license-files
键并且 license
表中存在 text
子键,则工具**应**发出警告,告知用户它已弃用,并建议改为使用许可证表达式作为顶级字符串键。
同样,如果不存在新的 license-files
键并且 license
表中存在 file
子键,则工具**应**发出警告,告知用户它已弃用,并建议改为使用 license-files
键。
如果指定的许可证 file
存在于源代码树中,构建工具**应**使用它来填充核心元数据中的 License-File
字段,并且**必须**包含指定的文件,就好像它是在 license-file
字段中指定的一样。如果该文件在指定的路径中不存在,工具**必须**引发以前指定的提示性错误。
license
键的表值可能会在未来某个 PEP 中从规范的新版本中移除。
项目格式中的许可证文件
将对现有规范进行一些补充。
- 项目源代码树
- 根据 项目源元数据 部分,声明项目元数据规范 将更新为反映许可证文件路径**必须**相对于项目根目录;即包含
pyproject.toml
的目录(或等效地,其他旧版项目配置,例如setup.py
、setup.cfg
等)。 - 源代码分发 (sdist)
- sdist 规范 将更新为反映,如果
Metadata-Version
为2.4
或更高版本,则 sdist **必须**包含PKG-INFO
中 License-File 字段 指定的任何许可证文件,这些文件位于相对于 sdist 的相应路径(包含pyproject.toml
和PKG-INFO
核心元数据)。 - 构建分发 (wheel)
- 《轮子规范》将更新以反映,如果
Metadata-Version
为2.4
或更高版本,并且指定了一个或多个License-File
字段,则.dist-info
目录**必须**包含一个licenses
子目录,该子目录**必须**包含METADATA
文件中的License-File
字段列出的文件,其路径相对于licenses
目录。 - 已安装项目
- 《记录已安装项目规范》将更新以反映,如果
Metadata-Version
为2.4
或更高版本,并且指定了一个或多个License-File
字段,则.dist-info
目录**必须**包含一个licenses
子目录,该子目录**必须**包含METADATA
文件中的License-File
字段列出的文件,其路径相对于licenses
目录,并且安装工具**必须**从轮子里复制此目录中的任何文件。
转换旧版元数据
工具**不得**使用 license.text
[project]
键(或等效的特定于工具的格式)、许可证分类器或核心元数据 License
字段的值来填充 license
键的顶级字符串值或核心元数据 License-Expression
字段,而无需告知用户并要求用户明确、肯定地采取操作来选择和确认所需的许可证表达式值,然后再继续。
需要自动将许可证分类器转换为 SPDX 标识符的工具作者可以使用 PEP 作者编写的建议。
向后兼容性
在 pyproject.toml
[project]
表中添加新的 License-Expression
核心元数据字段和 license
键的顶级字符串值明确表示支持本 PEP 中的规范。这避免了新工具将许可证表达式误解为自由格式的许可证描述或反之亦然的风险。
旧版已弃用的核心元数据 License
字段、pyproject.toml
[project]
表中的 license
键表子键(text
和 file
)以及许可证分类器保留向后兼容性。删除将留给未来的 PEP 和核心元数据规范的新版本。
新的 License-File
核心元数据字段的规范以及在分发版中添加文件旨在与许多打包工具中该字段的现有用法在很大程度上向后兼容。 pyproject.toml
的 [project]
表中的新 license-files
键只有在用户和工具采用它后才会生效。
本 PEP 指定许可证文件应放在 .dist-info
目录的专用 licenses
子目录中。这是新的,并确保遵循本 PEP 的轮子的许可证位置将与通过以前的安装程序特定行为生成的轮子的许可证位置不同。新的元数据版本进一步支持这一点。
这也解决了当前许可证文件在不同位置具有相同名称时意外替换的问题,从而使轮子无法分发而未被注意到。它还避免了与同一目录中的其他元数据文件发生冲突。
这些新增内容将添加到源分发版 (sdist)、构建分发版 (wheel) 和已安装项目规范中。它们记录了其当前规范下允许的行为,并将它们限制在新元数据版本之后。
本 PEP 建议 PyPI 实现对新的 License-Expression
和 License-File
字段进行验证,这不会影响上传的新旧软件包,除非它们明确选择使用这些新字段并且未能正确遵循规范。因此,这不会对向后兼容性产生影响,并通过确保上传到 PyPI 的所有具有新字段的分发版都符合规范来保证向前兼容性。
安全隐患
本 PEP 没有预见的安全性影响: License-Expression
字段是普通字符串, License-File
字段是文件路径。两者都没有引入任何已知的新安全问题。
如何教授
大多数软件包使用单个许可证,这使得情况变得简单:单个许可证标识符是有效的许可证表达式。
打包工具的用户将通过工具在检测到无效许可证或使用已弃用的 License
字段或许可证分类器时发出的消息来了解其软件包的有效许可证表达式。
如果使用了无效的 License-Expression
,用户将无法将其软件包发布到 PyPI,并且错误消息将帮助他们了解需要使用 SPDX 标识符。可以使用错误的许可证元数据生成分发版,但不能将其发布到 PyPI 或任何其他强制执行 License-Expression
有效性的索引服务器上。对于使用现已弃用的 License
字段或许可证分类器的作者,打包工具可能会警告他们并告知他们替换项 License-Expression
。
工具还可以帮助转换并在许多常见情况下建议许可证表达式。
- 附录附录:将许可证分类器映射到 SPDX 标识符 为工具作者提供了有关如何建议从旧版分类器生成的许可证表达式的建议。
- 工具可能能够建议如何更新项目源元数据中现有的
License
值,并将其转换为许可证表达式,如本 PEP 中所指定。
参考实现
如果工具决定实施规范的这一部分,则需要支持解析和验证 License-Expression
字段中的许可证表达式。工具可以选择在他们自己的方面实现验证(例如,像hatch)或使用可用的 Python 库之一(例如,license-expression)。本 PEP 并不强制使用任何特定的库,并允许工具作者为他们的项目选择最佳的实现。
被拒绝的想法
提出了许多备选方案,并在仔细考虑后被拒绝。包括拒绝理由的完整列表可以在单独页面中找到。
附录
提供辅助文档列表
- 详细的许可示例,
- 用户场景,
- Python 和其他项目中的许可证文档,
- 将许可证分类器映射到 SPDX 标识符,
- 被拒绝的方案 详细信息。
致谢
- Alyssa Coghlan
- Kevin P. Fleming
- Pradyun Gedam
- Oleg Grenrus
- Dustin Ingram
- Chris Jerdonek
- Cyril Roelandt
- Luis Villa
- Seth M. Larson
- Ofek Lev
版权
本文件放置在公共领域或根据CC0-1.0-Universal 许可证,以更宽松的许可证为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0639.rst
上次修改时间:2024-08-29 18:30:14 GMT