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定义了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 中实现。
术语
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 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 许可证表达式,如 上述定义。
构建和发布工具 SHOULD 检查 License-Expression 字段是否包含有效的SPDX表达式,包括特定许可证标识符的有效性(如 上述定义)。发现无效表达式时,工具 MAY 停止执行并引发错误。如果工具选择验证SPDX表达式,它们还 SHOULD 存储 License-Expression 字段的大小写规范化版本,其中每个SPDX许可证标识符使用参考大小写,AND、OR 和 WITH 关键词使用大写。如果一个或多个许可证标识符在 SPDX许可证列表 中被标记为已弃用,工具 SHOULD 报告警告,发布工具 MAY 引发错误。
对于所有包含 License-Expression 字段的新上传的 分发档案,Python包索引 (PyPI) MUST 验证它们是否包含有效且大小写规范化的许可证表达式以及有效的标识符(如 上述定义),并且 MUST 拒绝不符合的上传。符合SPDX规范的自定义许可证标识符被认为是有效的。PyPI MAY 拒绝使用已弃用许可证标识符的上传,只要该标识符在上述SPDX许可证列表版本中已弃用。
添加 License-File 字段
License-File 是一个可选的 核心元数据字段。每个实例都包含许可证相关文件的路径的字符串表示形式。该路径位于 项目源树 内,相对于 项目根目录。这是一个可多次使用的字段,可以出现零次或多次,每个实例列出其中一个文件的路径。在此字段下指定的文件可能包括许可证文本、作者/归属信息或其他需要随包分发的法律声明。
如 本PEP所规定,其值也是该文件在 已安装项目 和标准化的 分发包 类型中相对于 根许可证目录 的路径。
如果 License-File 列在 源分发 或 构建分发 的核心元数据中
- 该文件 MUST 包含在 分发档案 中,其路径相对于根许可证目录。
- 该文件 MUST 随 项目 一同安装,路径相同。
- 指定的相对路径在项目源树、源分发 (sdists)、构建分发 (Wheels) 和已安装项目之间 MUST 保持一致。
- 在根许可证目录内,打包工具 MUST 复制源许可证文件相对于项目根目录的目录结构。
- 路径分隔符 MUST 是正斜杠字符 (
/),并且 MUST NOT 使用父目录指示符 (..)。 - 许可证文件内容 MUST 为UTF-8编码的文本。
构建工具 MAY 并且发布工具 SHOULD 在构建分发元数据不包含 License-File 条目时生成信息性警告,发布工具 MAY 但构建工具 MUST NOT 抛出错误。
对于所有新上传的 分发档案,如果其核心元数据中包含一个或多个 License-File 字段并声明 Metadata-Version 为 2.4 或更高版本,PyPI SHOULD 验证所有指定文件是否存在于该 分发档案 中,并且 MUST 拒绝未通过验证的上传。
弃用 License 字段
旧版非结构化文本 License 核心元数据字段 已被弃用,并由新的 License-Expression 字段取代。这两个字段互斥。生成核心元数据的工具 MUST NOT 同时创建这两个字段。读取核心元数据的工具,在同时处理这两个字段时,MUST 读取 License-Expression 的值,并且 MUST 忽略 License 字段的值。
如果只存在 License 字段,工具 MAY 发出警告,告知用户该字段已弃用,并推荐使用 License-Expression。
对于所有新上传的 分发档案,如果包含 License-Expression 字段,Python包索引 (PyPI) MUST 拒绝同时指定 License 和 License-Expression 字段的任何上传。
在未来的PEP中,License 字段可能会从新版规范中移除。
弃用许可证分类器
在 Classifier 核心元数据字段 中使用 许可证分类器(在核心元数据规范中描述)已被弃用,并由更精确的 License-Expression 字段取代。
如果 License-Expression 字段存在,构建工具 MAY 在 Classifier 字段中包含一个或多个许可证分类器时引发错误,并且 MUST NOT 自己添加此类分类器。
否则,如果此字段包含许可证分类器,工具 MAY 发出警告,告知用户此类分类器已弃用,并推荐使用 License-Expression。为了与现有的发布和安装过程兼容,除非同时提供了 License-Expression,否则出现许可证分类器 SHOULD NOT 引发错误。
新的许可证分类器 MUST NOT 添加到 PyPI;需要它们的用户 SHOULD 使用 License-Expression 字段。许可证分类器可能会在未来的PEP中从新版规范中移除。
项目源元数据
本PEP指定了对 pyproject.toml 文件中 [project] 表下的项目源元数据的更改。
向 license 键添加字符串值
[project] 表中的 license 键被定义为包含一个顶级字符串值。它是一个有效的SPDX许可证表达式,如 本PEP中所定义。其值映射到核心元数据中的 License-Expression 字段。
构建工具 SHOULD 按照 添加 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 键
在 [project] 表中添加了一个新的 license-files 键,用于指定项目源树中相对于 pyproject.toml 的文件路径,这些文件包含要随包分发的许可证和其他法律声明。它对应于核心元数据中的 License-File 字段。
其值为字符串数组,MUST 包含有效的全局模式,如下所述
- 字母数字字符、下划线 (
_)、连字符 (-) 和点 (.) MUST 按字面匹配。 - 特殊全局字符:
*、?、**和字符范围:[]仅包含字面匹配字符的 MUST 受支持。在[...]内,连字符表示不区分区域的范围(例如a-z,顺序基于Unicode代码点)。开头或结尾的连字符按字面匹配。 - 路径分隔符 MUST 是正斜杠字符 (
/)。模式是相对于包含pyproject.toml的目录,因此 MUST NOT 使用前导斜杠字符。 - 父目录指示符 (
..) MUST NOT 使用。
任何不受本规范涵盖的字符或字符序列都是无效的。项目 MUST NOT 使用此类值。使用此字段的工具 SHOULD 拒绝无效值并报错。
工具 MUST 假定许可证文件内容是有效的UTF-8编码文本,并且 SHOULD 验证这一点并在不是时引发错误。
字面路径(例如 LICENSE)被视为有效的全局模式,这意味着它们也可以被定义。
构建工具
- MUST 将每个值视为一个全局模式,并且如果模式包含无效的全局语法,MUST 抛出错误。
- MUST 将所有被列出的模式匹配的文件包含在所有分发档案中。
- MUST 在核心元数据的
License-File字段下,列出每个匹配的文件路径。 - 如果任何单个用户指定的模式未匹配到至少一个文件,MUST 抛出错误。
如果 license-files 键存在且设置为空数组,则工具 MUST NOT 包含任何许可证文件,并且 MUST NOT 抛出错误。
有效许可证文件声明示例
[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"]
原因:MUST NOT 使用 ..。\ 是无效的路径分隔符,MUST 使用 /。
[project]
license-files = ["LICEN{CSE*"]
原因:“LICEN{CSE*”不是一个有效的全局模式。
弃用 license 键表子键
[project] 表中 license 键的表值,包括 text 和 file 表子键,现已弃用。如果新的 license-files 键存在,则如果 license 键已定义且其值不是单个顶级字符串,则构建工具 MUST 抛出错误。
如果新的 license-files 键不存在,并且 license 表中存在 text 子键,工具 SHOULD 发出警告,告知用户它已弃用,并建议使用许可证表达式作为顶级字符串键。
同样,如果新的 license-files 键不存在,并且 license 表中存在 file 子键,工具 SHOULD 发出警告,告知用户它已弃用,并建议使用 license-files 键。
如果指定的许可证 file 存在于源树中,构建工具 SHOULD 用它来填充核心元数据中的 License-File 字段,并且 MUST 包含指定的文件,如同它在 license-file 字段中指定一样。如果文件在指定路径不存在,工具 MUST 抛出之前指定的信息性错误。
在未来的PEP中,license 键的表值可能会从新版规范中移除。
项目格式中的许可证文件
现有规范将增加一些内容。
- 项目源树
- 根据 项目源元数据 部分,声明项目元数据规范 将进行更新,以反映许可证文件路径 MUST 相对于项目根目录;即包含
pyproject.toml(或等效的其他旧版项目配置,例如setup.py、setup.cfg等)的目录。 - 源分发 (sdists)
- sdist规范 将更新,以反映如果
Metadata-Version为2.4或更高,sdist MUST 包含PKG-INFO中由 License-File 字段 指定的任何许可证文件,其路径相对于sdist(包含pyproject.toml和PKG-INFO核心元数据)的根目录。 - 构建分发 (wheels)
- Wheel规范 将更新,以反映如果
Metadata-Version为2.4或更高,并且指定了一个或多个License-File字段,则.dist-info目录 MUST 包含一个licenses子目录,该子目录 MUST 包含METADATA文件中License-File字段中列出的文件,其路径相对于licenses目录。 - 已安装项目
- 记录已安装项目规范 将更新,以反映如果
Metadata-Version为2.4或更高,并且指定了一个或多个License-File字段,则.dist-info目录 MUST 包含一个licenses子目录,该子目录 MUST 包含METADATA文件中License-File字段中列出的文件,其路径相对于licenses目录,并且此目录中的任何文件 MUST 由安装工具从轮子中复制。
转换旧版元数据
工具 MUST NOT 使用 license.text [project] 键(或等效的工具特定格式)的内容、许可证分类器或核心元数据 License 字段的值来填充 license 键的顶级字符串值或核心元数据 License-Expression 字段,除非在继续之前告知用户并要求明确、肯定的用户操作来选择并确认所需的许可证表达式值。
需要自动将许可证分类器转换为SPDX标识符的工具作者,可以使用PEP作者准备的 建议。
向后兼容性
在核心元数据中添加新的 License-Expression 字段,并在 pyproject.toml 的 [project] 表中为 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
最后修改: 2025-01-24 23:56:07 GMT