Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 增强提案

PEP 440 – 版本识别和依赖关系规范

作者:
Alyssa Coghlan <ncoghlan at gmail.com>,Donald Stufft <donald at stufft.io>
BDFL 代表:
Alyssa Coghlan <ncoghlan at gmail.com>
讨论列表:
Distutils-SIG 列表
状态:
最终版
类型:
标准跟踪
主题:
打包
创建日期:
2013 年 3 月 18 日
修订历史:
2013 年 3 月 30 日,2013 年 5 月 27 日,2013 年 6 月 20 日,2013 年 12 月 21 日,2014 年 1 月 28 日,2014 年 8 月 8 日,2014 年 8 月 22 日
替换:
386
决议:
Distutils-SIG 消息

目录

注意

此 PEP 是历史文档。最新的规范版本,版本说明符,维护在 PyPA 规范页面 上。

×

请参阅 PyPA 规范更新流程,了解如何提出更改建议。

摘要

此 PEP 描述了一种用于识别 Python 软件发行版版本以及声明对特定版本的依赖关系的方案。

本文档解决了先前尝试标准化版本控制方法的一些限制,如 PEP 345PEP 386 中所述。

定义

本文档中出现的关键词“必须”、“禁止”、“需要”、“应”、“不应”、“应该”、“不应该”、“推荐”、“可以”和“可选”的解释方式如 RFC 2119 中所述。

“项目”是指可供集成的软件组件。项目包括 Python 库、框架、脚本、插件、应用程序、数据集合或其他资源,以及各种组合。

“版本”是项目的唯一标识的快照。

“发行版”是指用于发布和分发版本的打包文件。

“构建工具”是指旨在在开发系统上运行的自动化工具,用于生成源代码和二进制发行版归档文件。构建工具也可以由集成工具调用,以便构建作为 sdists 分发的软件,而不是预构建的二进制归档文件。

“索引服务器”是指发布版本和依赖关系元数据并对允许的元数据施加约束的活动分发注册表。

“发布工具”是指旨在在开发系统上运行并上传源代码和二进制发行版归档文件到索引服务器的自动化工具。

“安装工具”是指专门旨在在部署目标上运行的集成工具,从索引服务器或其他指定位置使用源代码和二进制发行版归档文件,并将它们部署到目标系统。

“自动化工具”是一个集体术语,涵盖构建工具、索引服务器、发布工具、集成工具以及任何其他生成或使用分发版本和依赖关系元数据的软件。

版本方案

发行版由一个公共版本标识符标识,该标识符支持所有定义的版本比较操作。

版本方案用于描述特定发行版归档文件提供的发行版版本,以及对构建或运行软件所需的依赖关系版本施加约束。

公共版本标识符

规范的公共版本标识符必须符合以下方案。

[N!]N(.N)*[{a|b|rc}N][.postN][.devN]

公共版本标识符不得包含前导或尾随空格。

公共版本标识符在给定的发行版中必须唯一。

安装工具应该忽略任何不符合此方案的公共版本,但必须包含下面指定的规范化。安装工具可以在检测到不符合或不明确的版本时向用户发出警告。

另请参阅 Appendix B : Parsing version strings with regular expressions,其中提供了一个正则表达式来检查与规范格式的严格一致性,以及一个更宽松的正则表达式,接受可能需要后续规范化的输入。

公共版本标识符分为最多五个部分。

  • 纪元部分:N!
  • 发布部分:N(.N)*
  • 预发布部分:{a|b|rc}N
  • 发布后部分:.postN
  • 开发版本部分:.devN

任何给定的版本将是“最终版本”、“预发布版本”、“发布后版本”或“开发版本”,如以下部分所定义。

所有数字组件必须是非负整数,表示为 ASCII 数字序列。

所有数字组件必须根据其数值进行解释和排序,而不是作为文本字符串。

所有数字组件可以为零。除了下面描述的发布部分外,零的数字组件除了始终是版本排序中可能的最低值外,没有任何特殊意义。

注意

为了更好地适应现有公共和私有 Python 项目中广泛的版本控制实践,此方案允许一些难以阅读的版本标识符。

因此,PEP 在技术上允许的一些版本控制实践对于新项目来说是强烈不建议的。在这种情况下,相关细节将在以下部分中注明。

本地版本标识符

本地版本标识符必须符合以下方案。

<public version identifier>[+<local version label>]

它们由一个正常的公共版本标识符(如上一节中定义)以及一个任意“本地版本标签”组成,并用加号与公共版本标识符分隔。本地版本标签没有分配任何特定的语义,但会施加一些语法限制。

本地版本标识符用于表示上游项目的完全 API(以及适用的 ABI)兼容的修补版本。例如,这些版本可能由应用程序开发人员和系统集成商创建,方法是在升级到新的上游版本会对应用程序或其他集成系统(如 Linux 发行版)造成过大干扰时应用特定的回退错误修复。

包含本地版本标签可以区分上游版本与下游集成商可能修改的重建版本。本地版本标识符的使用不影响版本的种类,但在应用于源代码分发时,确实表明它可能不包含与相应的上游版本完全相同的代码。

为了确保本地版本标识符可以轻松地作为文件名和 URL 的一部分合并,并避免十六进制哈希表示中的格式不一致,本地版本标签必须限于以下允许的字符集。

  • ASCII 字母([a-zA-Z]
  • ASCII 数字([0-9]
  • 句点(.

本地版本标签必须以 ASCII 字母或数字开头和结尾。

本地版本的比较和排序分别考虑本地版本的每个部分(以 . 分隔)。如果某个部分完全由 ASCII 数字组成,则该部分应被视为整数以进行比较;如果某个部分包含任何 ASCII 字母,则该部分应以不区分大小写的词法方式进行比较。在比较数字部分和词法部分时,数字部分始终比词法部分大。此外,具有更多部分的本地版本始终与具有较少部分的本地版本进行比较时更大,只要较短的本地版本的部分与较长本地版本的开头部分完全匹配。

“上游项目”是指定义自己的公共版本的项目。“下游项目”是指跟踪和重新分发上游项目的项目,它可能会从上游项目的更高版本中回退安全性和错误修复。

在将上游项目发布到公共索引服务器时,不应使用本地版本标识符,但可以使用它来识别直接从项目源代码创建的私有构建。下游项目在发布与公共版本标识符标识的上游项目版本 API 兼容但包含其他更改(如错误修复)的版本时,应使用本地版本标识符。由于 Python 包索引仅用于索引和托管上游项目,因此它不允许使用本地版本标识符。

使用本地版本标识符的源代码分发应提供 python.integrator 扩展元数据(如 PEP 459 中所定义)。

最终版本

仅由发布部分和可选的纪元标识符组成的版本标识符称为“最终版本”。

发布部分由一个或多个非负整数值组成,用点分隔。

N(.N)*

项目内的最终版本号**必须**以一致递增的方式编号,否则自动化工具将无法正确升级它们。

版本段的比较和排序依次考虑版本段每个组件的数值。当比较组件数量不同的版本段时,较短的版本段将根据需要用额外的零进行填充。

虽然在此方案下允许在第一个组件之后添加任意数量的额外组件,但最常见的变体是使用两个组件(“主版本.次版本”)或三个组件(“主版本.次版本.微版本”)。

例如

0.9
0.9.1
0.9.2
...
0.9.10
0.9.11
1.0
1.0.1
1.1
2.0
2.0.1
...

版本系列是指任何以共同前缀开头的最终版本号集合。例如,3.3.13.3.53.3.9.45 都属于 3.3 版本系列。

注意

X.YX.Y.0 不被视为不同的版本号,因为版本段比较规则隐式地在将其与包含三个组件的任何版本段进行比较时,将两个组件形式扩展为 X.Y.0

也允许使用基于日期的版本段。一个使用发布的年份和月份的基于日期的版本方案示例

2012.4
2012.7
2012.10
2013.1
2013.6
...

预发布版本

一些项目使用“alpha、beta、候选版本”预发布周期,以便在最终发布之前支持其用户进行测试。

如果用作项目开发周期的一部分,则这些预发布版本通过在版本标识符中包含预发布段来指示。

X.YaN   # Alpha release
X.YbN   # Beta release
X.YrcN  # Release Candidate
X.Y     # Final release

仅由版本段和预发布段组成的版本标识符称为“预发布版本”。

预发布段由预发布阶段的字母标识符以及一个非负整数值组成。给定版本的预发布版本首先按阶段(alpha、beta、候选版本)排序,然后按该阶段内的数字组件排序。

安装工具**可以**接受相同版本段的 crc 版本,以处理某些现有的遗留发行版。

安装工具**应该**将 c 版本解释为等效于 rc 版本(即,c1 表示与 rc1 相同的版本)。

构建工具、发布工具和索引服务器**应该**不允许为相同版本段创建 rcc 版本。

发布后版本

一些项目使用发布后版本来解决最终版本中不影响分发软件的次要错误(例如,更正发行说明中的错误)。

如果用作项目开发周期的一部分,则这些发布后版本通过在版本标识符中包含发布后段来指示。

X.Y.postN    # Post-release

包含发布后段但不包含开发版本段的版本标识符称为“发布后版本”。

发布后段由字符串 .post 后跟一个非负整数值组成。发布后版本按其数字组件排序,紧随相应的版本之后,并在任何后续版本之前。

注意

强烈建议不要使用发布后版本发布包含实际错误修复的维护版本。通常,最好使用更长的版本号并为每个维护版本递增最后一个组件。

预发布版本也允许发布后版本。

X.YaN.postM   # Post-release of an alpha release
X.YbN.postM   # Post-release of a beta release
X.YrcN.postM  # Post-release of a release candidate

注意

强烈建议不要创建预发布版本的发布后版本,因为它使人类读者难以解析版本标识符。通常,只需递增数字组件创建一个新的预发布版本会更清晰。

开发版本

一些项目会定期发布开发版本,并且系统打包程序(特别是对于 Linux 发行版)可能希望直接从源代码控制创建早期版本,这些版本不会与以后的项目版本冲突。

如果用作项目开发周期的一部分,则这些开发版本通过在版本标识符中包含开发版本段来指示。

X.Y.devN    # Developmental release

包含开发版本段的版本标识符称为“开发版本”。

开发版本段由字符串 .dev 后跟一个非负整数值组成。开发版本按其数字组件排序,紧接在相应的版本之前(以及任何具有相同版本段的预发布版本之前),并在任何之前的版本(包括任何发布后版本)之后。

预发布版本和发布后版本也允许开发版本。

X.YaN.devM       # Developmental release of an alpha release
X.YbN.devM       # Developmental release of a beta release
X.YrcN.devM      # Developmental release of a release candidate
X.Y.postN.devM   # Developmental release of a post-release

注意

虽然它们可能对持续集成很有用,但强烈建议不要将预发布版本的开发版本发布到通用公共索引服务器,因为它使人类读者难以解析版本标识符。如果需要发布此类版本,则只需递增数字组件创建一个新的预发布版本会更清晰。

发布后版本的开发版本也强烈建议不要使用,但对于将发布后版本符号用于可能包含代码更改的完整维护版本的项目来说,它们可能是合适的。

版本纪元

如果包含在版本标识符中,则纪元出现在所有其他组件之前,并用感叹号与版本段分隔。

E!X.Y  # Version identifier with epoch

如果没有给出显式纪元,则隐式纪元为 0

大多数版本标识符不会包含纪元,因为只有当项目以导致正常版本排序规则给出错误答案的方式更改其版本编号处理方式时,才需要显式纪元。例如,如果项目正在使用基于日期的版本,如 2014.04,并且想要切换到语义版本,如 1.0,那么使用正常排序方案时,新版本将被识别为比基于日期的版本**更旧**。

1.0
1.1
2.0
2013.10
2014.04

但是,通过指定显式纪元,可以适当地更改排序顺序,因为来自后期纪元的版本都排在来自早期纪元的版本之后。

2013.10
2014.04
1!1.0
1!1.1
1!2.0

规范化

为了更好地与现有版本保持兼容性,在解析版本时**必须**考虑许多“替代”语法。在解析版本时**必须**考虑这些语法,但是应该将它们“规范化”为上面定义的标准语法。

大小写敏感性

所有 ASCII 字母都应该在版本内不区分大小写地解释,并且正常形式为小写。这允许诸如 1.1RC1 之类的版本,它将被规范化为 1.1rc1

整数规范化

所有整数都通过内置的 int() 进行解释并规范化为输出的字符串形式。这意味着整数版本 00 将规范化为 0,而 09000 将规范化为 9000。这对于本地版本字母数字段内的整数(如 1.0+foo0100,它已处于规范形式)不适用。

预发布分隔符

预发布版本应该允许在版本段和预发布段之间使用 .-_ 分隔符。此分隔符的正常形式是不带分隔符。这允许诸如 1.1.a11.1-a1 之类的版本,它们将被规范化为 1.1a1。它还应该允许在预发布标识符和数字之间使用分隔符。这允许诸如 1.0a.1 之类的版本,它将被规范化为 1.0a1

预发布拼写

预发布版本允许使用 alphabetacprepreview 的额外拼写,分别对应于 abrcrcrc。这允许诸如 1.1alpha11.1beta21.1c3 之类的版本,它们将规范化为 1.1a11.1b21.1rc3。在每种情况下,都应将其他拼写视为与其正常形式等效。

隐式预发布编号

预发布版本允许省略数字,在这种情况下,它被隐式地假定为 0。此省略的正常形式是显式包含 0。这允许诸如 1.2a 之类的版本,它被规范化为 1.2a0

发布后分隔符

发布后版本也允许 .-_ 分隔符,以及完全省略分隔符。此分隔符的正常形式是使用 . 分隔符。这允许诸如 1.2-post21.2post2 之类的版本,它们将规范化为 1.2.post2。与预发布分隔符一样,它还允许在发布后标识符和数字之间使用可选分隔符。这允许诸如 1.2.post-2 之类的版本,它将规范化为 1.2.post2

发布后拼写

发布后版本允许使用 revr 的额外拼写。这允许诸如 1.0-r4 之类的版本,它将规范化为 1.0.post4。与预发布版本一样,其他拼写应视为与其正常形式等效。

隐式发布后编号

发布后版本允许省略数字,在这种情况下,它被隐式地假定为 0。此省略的正常形式是显式包含 0。这允许诸如 1.2.post 之类的版本,它被规范化为 1.2.post0

隐式发布后版本

发布后版本允许完全省略 post 标识符。使用此形式时,分隔符**必须**为 -,不允许使用其他形式。这允许将诸如 1.0-1 这样的版本规范化为 1.0.post1。此特定规范化**不得**与隐式发布后版本号规则结合使用。换句话说,1.0- **不是**有效版本,并且它**不会**规范化为 1.0.post0

开发版本分隔符

开发版本允许使用 .-_ 作为分隔符,也可以完全省略分隔符。其标准形式是使用 . 分隔符。这允许将诸如 1.2-dev21.2dev2 这样的版本规范化为 1.2.dev2

隐式开发版本编号

开发版本允许省略数字,在这种情况下,隐式假定为 0。其标准形式是显式包含 0。这允许将诸如 1.2.dev 这样的版本规范化为 1.2.dev0

本地版本段

对于本地版本,除了使用 . 作为段的分隔符之外,使用 -_ 也是可以接受的。标准形式是使用 . 字符。这允许将诸如 1.0+ubuntu-1 这样的版本规范化为 1.0+ubuntu.1

前导 v 字符

为了支持常见的版本表示法 v1.0,版本前面可以有一个文字 v 字符。此字符**必须**为所有目的忽略,并且应从版本的所有规范化形式中省略。带有和不带 v 的相同版本被认为是等效的。

前导和尾随空格

开头和结尾的空格必须静默忽略并从版本的规范化形式中删除。这包括 " "\t\n\r\f\v。这允许合理地处理意外的空格,例如 1.0\n 这样的版本,它规范化为 1.0

符合版本方案的示例

标准版本方案旨在涵盖公共和私有 Python 项目中广泛的识别实践。在实践中,单个项目试图使用方案提供的全部灵活性将导致人类用户难以确定版本的相对顺序,即使上述规则确保所有符合标准的工具都会一致地对其进行排序。

以下示例说明了项目可能选择识别其发布的不同方法的一小部分,同时仍然确保人类用户和自动化工具都可以轻松确定“最新发布”和“最新稳定发布”。

简单的“主版本.次版本”版本控制

0.1
0.2
0.3
1.0
1.1
...

简单的“主版本.次版本.微版本”版本控制

1.1.0
1.1.1
1.1.2
1.2.0
...

带有 alpha、beta 和候选发布前版本的“主版本.次版本”版本控制

0.9
1.0a1
1.0a2
1.0b1
1.0rc1
1.0
1.1a1
...

带有开发版本、候选发布和发布后版本(用于次要修正)的“主版本.次版本”版本控制

0.9
1.0.dev1
1.0.dev2
1.0.dev3
1.0.dev4
1.0c1
1.0c2
1.0
1.0.post1
1.1.dev1
...

基于日期的发布,在每一年中使用递增的序列号,跳过零

2012.1
2012.2
2012.3
...
2012.15
2013.1
2013.2
...

允许的后缀和相对顺序摘要

注意

本节主要面向自动处理分发元数据的工具的作者,而不是决定版本控制方案的 Python 分发开发者。

版本标识符的纪元段**必须**根据给定纪元的数值进行排序。如果不存在纪元段,则隐式数值为 0

版本标识符的发布段**必须**按照 Python 的元组排序的相同顺序进行排序,当规范化的发布段按如下方式解析时

tuple(map(int, release_segment.split(".")))

参与比较的所有发布段**必须**通过根据需要用零填充较短的段来转换为一致的长度。

在数值发布(1.02.7.3)中,允许使用以下后缀,并且**必须**按所示顺序排序

.devN, aN, bN, rcN, <no suffix>, .postN

请注意,c 被认为在语义上等效于 rc,并且必须按其为 rc 的方式排序。工具**可以**拒绝在同一发布段中同时具有相同 Ncrc 的情况,因为它是模棱两可的,并且仍然符合 PEP。

在 alpha(1.0a1)、beta(1.0b1)或候选发布(1.0rc11.0c1)中,允许使用以下后缀,并且**必须**按所示顺序排序

.devN, <no suffix>, .postN

在发布后版本(1.0.post1)中,允许使用以下后缀,并且**必须**按所示顺序排序

.devN, <no suffix>

请注意,devNpostN **必须**始终以点开头,即使在紧跟数值版本使用时也是如此(例如 1.0.dev4561.0.post1)。

在具有共享前缀的发布前、发布后或开发版本段中,排序**必须**根据数值组件的值进行。

以下示例涵盖了许多可能的组合

1.dev0
1.0.dev456
1.0a1
1.0a2.dev456
1.0a12.dev456
1.0a12
1.0b1.dev456
1.0b2
1.0b2.post345.dev456
1.0b2.post345
1.0rc1.dev456
1.0rc1
1.0
1.0+abc.5
1.0+abc.7
1.0+5
1.0.post456.dev34
1.0.post456
1.0.15
1.1.dev1

跨不同元数据版本的版本排序

元数据 v1.0(PEP 241)和元数据 v1.1(PEP 314)未指定标准版本识别或排序方案。但是,元数据 v1.2(PEP 345)确实指定了一个方案,该方案在 PEP 386 中定义。

由于简单安装程序 API 的性质,安装程序无法知道特定分发使用的是哪个元数据版本。此外,安装程序需要能够创建一个合理的优先级列表,其中包含项目的所有版本或尽可能多的版本,以确定它应该安装哪些版本。这些要求需要跨一个解析机制进行标准化,以便用于项目的所有版本。

因此,此 PEP **必须**用于所有版本的元数据,并且即使对于元数据 v1.2,也取代了 PEP 386。工具**应该**忽略任何无法通过此 PEP 中的规则解析的版本,但如果不存在符合此 PEP 的版本,则**可以**回退到实现定义的版本解析和排序方案。

分发用户可能希望显式地从他们控制的任何私有包索引中删除不符合标准的版本。

与其他版本方案的兼容性

某些项目可能会选择使用需要转换才能符合此 PEP 中定义的公共版本方案的版本方案。在这种情况下,项目特定版本可以存储在元数据中,而转换后的公共版本则发布在版本字段中。

这允许自动分发工具一致地提供已发布版本的正确排序,同时仍允许开发人员在其项目中使用他们喜欢的内部版本控制方案。

语义版本控制

语义版本控制 是一种流行的版本识别方案,它比此 PEP 对发布号的不同元素的重要性更具规定性。即使项目选择不遵守语义版本控制的细节,也值得理解该方案,因为它涵盖了依赖于其他分发时以及发布其他依赖的分发时可能出现的许多问题。

语义版本控制的“主版本.次版本.修订版本”(在此 PEP 中称为“主版本.次版本.微版本”)方面(2.0.0 规范中的第 1-8 条)与在此 PEP 中定义的版本方案完全兼容,并且鼓励遵守这些方面。

包含连字符(发布前版本 - 第 10 条)或加号(构建 - 第 11 条)的语义版本**与**此 PEP**不兼容**,并且在公共版本字段中不允许使用。

一种将此类基于语义版本控制的源标签转换为兼容的公共版本的一种可能机制是使用 .devN 后缀来指定适当的版本顺序。

特定构建信息也可以包含在本地版本标签中。

基于 DVCS 的版本标签

许多构建工具与 Git 和 Mercurial 等分布式版本控制系统集成,以便将识别哈希添加到版本标识符中。由于哈希无法可靠地排序,因此此类版本在公共版本字段中不允许使用。

与语义版本控制一样,公共 .devN 后缀可用于唯一标识此类发布以供发布,而原始的基于 DVCS 的标签可以存储在项目元数据中。

识别哈希信息也可以包含在本地版本标签中。

Olson 数据库版本控制

pytz 项目从相应的 Olson 时区数据库版本控制方案继承其版本控制方案:年份后跟一个表示该年内数据库版本的字母。

这可以转换为兼容的公共版本标识符,如 <year>.<serial>,其中序列号从零或一(对于“<year>a”发布)开始,并且在当年每次后续数据库更新时递增。

与其他转换后的版本标识符一样,相应的 Olson 数据库版本可以记录在项目元数据中。

版本说明符

版本说明符由一系列版本子句组成,子句之间用逗号分隔。例如

~= 0.9, >= 1.0, != 1.3.4.*, < 2.0

比较运算符确定版本子句的类型

逗号(“,”)等效于逻辑 **与** 运算符:候选版本必须匹配所有给定的版本子句才能匹配整个规范。

条件运算符和后续版本标识符之间的空格是可选的,逗号周围的空格也是可选的。

当多个候选版本匹配版本规范时,首选版本 **应该** 是根据标准 版本方案 定义的一致排序确定的最新版本。是否将预发布版本视为候选版本 **应该** 如 预发布版本的处理 中所述进行处理。

除非在下面特别说明,否则版本规范中 **不得** 允许本地版本标识符,并且在检查候选版本是否匹配给定版本规范时,**必须** 完全忽略本地版本标签。

兼容版本

兼容版本子句由兼容版本运算符 ~= 和版本标识符组成。它匹配任何预期与指定版本兼容的候选版本。

指定的版本标识符必须采用 版本方案 中描述的标准格式。此版本规范中 **不允许** 使用本地版本标识符。

对于给定的版本标识符 V.N,兼容版本子句大约等效于以下比较子句对

>= V.N, == V.*

此运算符 **不得** 与单个片段版本号(例如 ~=1)一起使用。

例如,以下版本子句组是等效的

~= 2.2
>= 2.2, == 2.*

~= 1.4.5
>= 1.4.5, == 1.4.*

如果在兼容版本子句中将预发布、发布后或开发版本命名为 V.N.suffix,则在确定所需的前缀匹配时会忽略后缀。

~= 2.2.post3
>= 2.2.post3, == 2.*

~= 1.4.5a4
>= 1.4.5a4, == 1.4.*

版本片段比较的填充规则意味着可以通过将附加的零追加到版本规范来控制兼容版本子句中假定的前向兼容性程度。

~= 2.2.0
>= 2.2.0, == 2.2.*

~= 1.4.5.0
>= 1.4.5.0, == 1.4.5.*

版本匹配

版本匹配子句包括版本匹配运算符 == 和版本标识符。

指定的版本标识符必须采用 版本方案 中描述的标准格式,但如下所述,在公共版本标识符上允许使用尾随 .*

默认情况下,版本匹配运算符基于严格的相等比较:指定的版本必须与请求的版本完全相同。执行的 **唯一** 替换是版本片段的零填充,以确保版本片段以相同的长度进行比较。

严格版本匹配是否合适取决于版本规范的具体用例。当不适当地使用严格版本匹配时,自动化工具 **应该** 至少发出警告,并且 **可以** 完全拒绝它们。

可以通过将尾随 .* 追加到版本匹配子句中的版本标识符来请求前缀匹配而不是严格比较。这意味着在确定版本标识符是否匹配该子句时,将忽略其他尾随片段。如果指定的版本仅包含版本片段,则也会忽略版本片段中的尾随组件(或其不存在)。

例如,给定版本 1.1.post1,以下子句将匹配或不匹配,如下所示

== 1.1        # Not equal, so 1.1.post1 does not match clause
== 1.1.post1  # Equal, so 1.1.post1 matches clause
== 1.1.*      # Same prefix, so 1.1.post1 matches clause

出于前缀匹配的目的,预发布片段被认为具有隐含的前导 .,因此,给定版本 1.1a1,以下子句将匹配或不匹配,如下所示

== 1.1        # Not equal, so 1.1a1 does not match clause
== 1.1a1      # Equal, so 1.1a1 matches clause
== 1.1.*      # Same prefix, so 1.1a1 matches clause if pre-releases are requested

精确匹配也被视为前缀匹配(此解释是由版本标识符版本片段的常用零填充规则隐含的)。给定版本 1.1,以下子句将匹配或不匹配,如下所示

== 1.1        # Equal, so 1.1 matches clause
== 1.1.0      # Zero padding expands 1.1 to 1.1.0, so it matches clause
== 1.1.dev1   # Not equal (dev-release), so 1.1 does not match clause
== 1.1a1      # Not equal (pre-release), so 1.1 does not match clause
== 1.1.post1  # Not equal (post-release), so 1.1 does not match clause
== 1.1.*      # Same prefix, so 1.1 matches clause

拥有包含开发或本地版本(例如 1.0.dev1.*1.0+foo1.*)的前缀匹配是无效的。如果存在,开发版本片段始终是公共版本中的最后一个片段,并且本地版本被忽略以进行比较,因此在前缀匹配中使用任何一个都没有意义。

在为发布的分发定义依赖项时,强烈建议 **不要** 使用 ==(至少没有通配符后缀),因为它极大地增加了安全修复程序的部署复杂性。严格版本比较运算符主要用于在使用共享分发索引时定义可重复的 *应用程序部署* 依赖项时。

如果指定的版本标识符是公共版本标识符(没有本地版本标签),则在匹配版本时 **必须** 忽略任何候选版本的本地版本标签。

如果指定的版本标识符是本地版本标识符,则在匹配版本时 **必须** 考虑候选版本的本地版本标签,其中公共版本标识符如上所述匹配,并且使用严格的字符串相等比较检查本地版本标签是否等效。

版本排除

版本排除子句包括版本排除运算符 != 和版本标识符。

允许的版本标识符和比较语义与 版本匹配 运算符的相同,只是任何匹配的含义都反转了。

例如,给定版本 1.1.post1,以下子句将匹配或不匹配,如下所示

!= 1.1        # Not equal, so 1.1.post1 matches clause
!= 1.1.post1  # Equal, so 1.1.post1 does not match clause
!= 1.1.*      # Same prefix, so 1.1.post1 does not match clause

包含有序比较

包含有序比较子句包括一个比较运算符和一个版本标识符,并且将匹配任何版本,其中比较是正确的,基于候选版本和给定标准 版本方案 定义的一致排序的指定版本的相对位置。

包含有序比较运算符为 <=>=

与版本匹配一样,版本片段根据需要进行零填充,以确保版本片段以相同的长度进行比较。

此版本规范中 **不允许** 使用本地版本标识符。

排除有序比较

排他有序比较 >< 类似于包含有序比较,因为它们依赖于候选版本和给定标准 版本方案 定义的一致排序的指定版本的相对位置。但是,它们专门排除指定版本的预发布、发布后和本地版本。

排他有序比较 >V **不得** 允许给定版本的发布后版本,除非 V 本身是发布后版本。您可以通过使用 >V.postN 来强制发布版本晚于特定的发布后版本,包括其他发布后版本。例如,>1.7 将允许 1.7.1 但不允许 1.7.0.post1,并且 >1.7.post2 将允许 1.7.11.7.0.post3 但不允许 1.7.0

排他有序比较 >V **不得** 匹配指定版本的本地版本。

排他有序比较 <V **不得** 允许指定版本的预发布版本,除非指定版本本身是预发布版本。允许早于但不等于特定预发布版本的预发布版本可以通过使用 <V.rc1 或类似方法来实现。

与版本匹配一样,版本片段根据需要进行零填充,以确保版本片段以相同的长度进行比较。

此版本规范中 **不允许** 使用本地版本标识符。

任意相等

任意相等比较是简单的字符串相等运算,不考虑任何语义信息,例如零填充或本地版本。此运算符也不支持 == 运算符所支持的前缀匹配。

任意相等的主要用例是允许指定此 PEP 无法以其他方式表示的版本。此运算符是特殊的,并且充当逃生舱口,允许使用实现此 PEP 的工具的人员仍然安装与此 PEP 不兼容的旧版本。

例如,===foobar 将匹配 foobar 版本。

此运算符也可用于显式要求项目的未修补版本,例如 ===1.0,它不会匹配版本 1.0+downstream1

强烈建议不要使用此运算符,并且工具 **可以** 在使用时显示警告。

处理预发布版本

任何类型的预发布版本,包括开发版本,都隐式地从所有版本规范中排除,*除非* 它们已存在于系统中,由用户显式请求,或者满足版本规范的唯一可用版本是预发布版本。

默认情况下,依赖项解析工具 **应该**

  • 接受所有版本规范的已安装预发布版本
  • 接受版本规范的远程可用预发布版本,其中没有满足版本规范的最终版本或发布后版本
  • 排除所有其他预发布版本以供考虑

如果需要预发布版本来满足版本规范,依赖项解析工具 **可以** 发出警告。

依赖项解析工具 **应该** 也允许用户请求以下替代行为

  • 接受所有版本规范的预发布版本
  • 排除所有版本规范的预发布版本(如果预发布版本已本地安装,或者如果预发布版本是满足特定规范的唯一方法,则报告错误或警告)

依赖项解析工具 **可以** 也允许在每个分发基础上控制上述行为。

发布后版本和最终版本在版本规范中不会受到特殊处理 - 它们始终包含在内,除非显式排除。

示例

  • ~=3.1:版本 3.1 或更高版本,但不包括版本 4.0 或更高版本。
  • ~=3.1.2:版本 3.1.2 或更高版本,但不包括版本 3.2.0 或更高版本。
  • ~=3.1a1:版本 3.1a1 或更高版本,但不包括版本 4.0 或更高版本。
  • == 3.1:具体版本 3.1(或 3.1.0),排除所有预发布、发布后、开发版本和任何 3.1.x 维护版本。
  • == 3.1.*:以 3.1 开头的任何版本。等效于 ~=3.1.0 兼容版本子句。
  • ~=3.1.0, != 3.1.3:版本 3.1.0 或更高版本,但不包括版本 3.1.3 和版本 3.2.0 或更高版本。

直接引用

某些自动化工具可能允许使用直接引用作为普通版本说明符的替代方案。直接引用由说明符@和显式 URL 组成。

直接引用是否合适取决于版本说明符的具体用例。当不恰当地使用直接引用时,自动化工具至少应发出警告,并且可以完全拒绝它们。

公共索引服务器不应允许在上传的发行版中使用直接引用。直接引用旨在作为软件集成商而非发布者的工具。

根据用例,直接 URL 引用的某些合适目标可能是 sdist 或 wheel 二进制归档文件。支持的确切 URL 和目标将取决于工具。

例如,可以对本地源归档文件进行直接引用

pip @ file:///localbuilds/pip-1.3.1.zip

或者,也可以引用预构建的归档文件

pip @ file:///localbuilds/pip-1.3.1-py33-none-any.whl

所有不引用本地文件 URL 的直接引用都应指定安全的传输机制(例如https)并包含 URL 中的预期哈希值以进行验证。如果指定了没有任何哈希信息的直接引用,或者哈希信息是工具无法理解的,或者所选哈希算法是工具认为不可信的,则自动化工具至少应发出警告,并且可以拒绝依赖该 URL。如果此类直接引用还使用不安全的传输,则自动化工具不应依赖该 URL。

建议仅对源归档文件哈希使用标准库最新版本的hashlib模块无条件提供的哈希。在撰写本文时,该列表包含'md5''sha1''sha224''sha256''sha384''sha512'

对于源归档文件和 wheel 引用,可以通过在 URL 片段中包含<hash-algorithm>=<expected-hash>条目来指定预期哈希值。

对于版本控制引用,应使用VCS+protocol方案来识别版本控制系统和安全传输,并且应使用具有基于哈希的提交标识符的版本控制系统。对于不提供基于哈希的提交标识符的版本控制系统,自动化工具可以省略有关缺少哈希的警告。

为了处理不支持直接在 URL 中包含提交或标签引用的版本控制系统,可以使用@<commit-hash>@<tag>#<commit-hash>表示法将该信息附加到 URL 的末尾。

注意

这与 pip 支持的现有 VCS 引用表示法并不完全相同。首先,发行版名称被移动到前面,而不是嵌入为 URL 的一部分。其次,即使基于标签检索,也会包含提交哈希,以满足上述要求,即每个链接都应包含一个哈希以使伪造更加困难(创建具有特定标签的恶意存储库很容易,创建具有特定哈希的存储库则比较困难)。

远程 URL 示例

pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686
pip @ git+https://github.com/pypa/pip.git@7921be1537eac1e97bc40179a57f0349c2aee67d
pip @ git+https://github.com/pypa/pip.git@1.3.1#7921be1537eac1e97bc40179a57f0349c2aee67d

文件 URL

文件 URL 采用file://<host>/<path>的形式。如果省略了<host>,则假定为localhost,即使省略了<host>,第三个斜杠也必须存在。<path>定义要访问的文件系统上的文件路径。

在各种 *nix 操作系统上,<host>的唯一允许值为省略它、localhost或当前机器认为与其自身主机匹配的其他 FQDN。换句话说,在 *nix 上,file://方案只能用于访问本地机器上的路径。

在 Windows 上,文件格式应在适用的情况下包含驱动器号作为<path>的一部分(例如file:///c:/path/to/a/file)。与 *nix 不同,在 Windows 上,<host>参数可用于指定驻留在网络共享上的文件。换句话说,为了将\\machine\volume\file转换为file:// URL,它将最终变为file://machine/volume/file。有关 Windows 上file:// URL 的更多信息,请参阅 MSDN [4]

更新版本规范

版本控制规范可以在不需新的 PEP 或元数据版本更改的情况下进行澄清更新。

任何影响版本识别和比较语法和语义的技术更改都需要在新的 PEP 中定义更新的版本控制方案。

与 pkg_resources.parse_version 的差异总结

  • 注意:此比较针对的是 PEP 编写时存在的pkg_resourses.parse_version。PEP 被接受后,setuptools 6.0 及更高版本采用了本 PEP 中描述的行为。
  • 本地版本排序不同,本 PEP 要求它们排序为大于没有本地版本的相同版本,而pkg_resources.parse_version将其视为预发布标记。
  • 本 PEP 故意限制构成有效版本的语法,而pkg_resources.parse_version尝试从任何任意字符串中提供一些含义。
  • pkg_resources.parse_version允许任意深度嵌套的版本指示符,例如1.0.dev1.post1.dev5。但是,本 PEP 仅允许每种类型使用一次,并且它们必须按特定顺序存在。

与 PEP 386 的差异总结

  • 将版本说明符的描述移至版本控制 PEP 中
  • 添加了“直接引用”概念作为对资源的直接引用的标准表示法(而不是每个工具都需要发明自己的表示法)
  • 添加了“本地版本标识符”和“本地版本标签”概念,以允许系统集成商以其上游工具支持的方式指示已修补的构建,以及允许将构建标签合并到二进制发行版的版本控制中。
  • 添加了“兼容版本”条款
  • 添加了用于基于前缀的版本匹配和排除的后缀通配符语法
  • 更改了.devN后缀的顶级排序位置
  • 允许单值版本号
  • 显式排除前导或尾随空格
  • 显式支持基于日期的版本
  • 显式规范化规则,以提高与 PyPI 上现有版本元数据的兼容性,前提是它不会引入歧义
  • 隐式排除预发布版本,除非它们已经存在或需要满足依赖项
  • 将后期发布版本与未限定的发布版本相同对待
  • 讨论元数据版本之间的排序和依赖项
  • 从首选c切换到rc

以下各节给出了主要更改的理由。

更改版本方案

本 PEP 中的版本方案相对于PEP 386中的一个关键变化是,将顶级开发版本(如X.Y.devN)排序在 alpha 版本(如X.Ya1)之前。这是一个更符合逻辑的排序顺序,因为已经同时使用开发版本和 alpha/beta/候选版本的项目不希望它们的开发版本排序在其候选版本和最终版本之间。没有理由在该位置使用dev版本,而不是仅仅创建额外的候选版本。

更新后的排序顺序还意味着dev版本的排序现在在元数据标准和pkg_resources的预先存在行为(以及当前安装工具的行为)之间保持一致。

进行此更改应该可以使受影响的现有项目更容易迁移到最新版本的元数据标准。

版本方案的另一个变化是允许单数字版本,类似于 Mozilla Firefox、Google Chrome 和 Fedora Linux 发行版等非 Python 项目使用的版本。这实际上预计会对版本说明符更有用,但允许版本说明符和版本号都使用它比拆分这两个定义更容易。

在 PyPI 上发现了一些版本标识符仅在尾随\n字符方面不同的项目后,显式排除了前导和尾随空格。

还添加了各种其他规范化规则,如下面关于版本规范化的单独部分所述。

附录 A显示了对 PyPI 发行版版本信息的分析的详细结果,这些信息收集于 2014 年 8 月 8 日。此分析比较了本 PEP 中定义的显式排序版本方案与 setuptools 行为定义的事实标准。这些指标很有用,因为本 PEP 的目的是尽可能遵循现有的 setuptools 行为,同时仍然对不可排序的版本抛出异常(而不是像 setuptools 那样尝试猜测合适的顺序)。

版本方案的更明确描述

PEP 386一样,主要重点在于将现有实践编纂成更容易自动化的形式,而不是要求现有项目对其工作流程进行非平凡的更改。但是,标准方案允许的灵活性远大于绝大多数简单 Python 包所需的灵活性(这些包通常甚至不需要维护版本——许多用户乐于需要升级到新的功能版本才能获得错误修复)。

为了使新手开发人员受益,以及为了让希望更好地了解各种用例的经验丰富的开发人员受益,规范现在对定义的版本方案的各个组成部分进行了更详细的说明,包括每个组成部分在实践中如何使用的示例。

PEP 还明确指导开发人员朝着语义版本控制的方向发展(无需强制要求),并阻止使用完整版本方案的几个方面,这些方面主要为了涵盖现有项目实践和为 Linux 发行版重新打包软件的深奥的极端情况而包含在内。

描述版本方案 alongside 版本说明符

首先拥有标准化版本方案的主要原因是使可靠的自动化依赖项分析更容易。在版本标识符的定义旁边描述版本标识符的主要用例更有意义。

更改版本说明符的解释

之前对版本说明符的解释使得意外下载依赖项的预发布版本变得非常容易。这反过来又使得开发人员难以将软件的预发布版本发布到 Python 包索引中,因为即使将包标记为隐藏也无法阻止自动化工具下载它,并且还使得用户更难以通过主要的 PyPI Web 界面手动获取测试版本。

之前的解释还无充分理由地从某些版本说明符中排除了发布后的版本。

更新后的解释旨在使意外接受预发布版本作为满足依赖项变得困难,同时仍然允许在这是满足依赖项的唯一方法时自动检索预发布版本。

“假设某些向前兼容性”版本约束源自 Ruby 社区的“悲观版本约束”运算符 [2],以允许项目对向前兼容性承诺采取谨慎的态度,同时仍然轻松设置其依赖项的最低所需版本。兼容版本子句的拼写(~=)受到 Ruby(~>)和 PHP(~)等效项的启发。

还计划进一步改进对同一库的多个版本的并行安装的处理,但这将取决于安装数据库定义的更新以及改进的动态路径操作工具。

添加了请求基于前缀的版本匹配的尾随通配符语法,以便能够合理地定义兼容版本子句。

支持基于日期的版本标识符

排除基于日期的版本导致将 pytz 迁移到新的元数据标准时出现重大问题。它还引起了 OpenStack 开发人员的担忧,因为他们使用基于日期的版本控制方案,并且希望能够迁移到新的元数据标准而无需更改它。

添加版本纪元

添加版本纪元的原因与它们成为其他版本控制方案(例如 Fedora 和 Debian Linux 发行版的版本控制方案)的一部分的原因相同:允许项目优雅地更改其对版本号发布的方法,而不会使新版本看起来比以前的版本具有较低的版本号,并且无需更改项目名称。

特别是,支持版本纪元允许以前使用基于日期的版本控制的项目通过指定新的版本纪元切换到语义版本控制。

选择 ! 字符来分隔纪元版本而不是 : 字符(在其他系统中通常使用),因为 : 不是 Windows 目录名称中的有效字符。

添加直接引用

添加直接引用作为“转义子句”以处理不整齐的现实世界情况,这些情况与标准分发模型不完全匹配。这包括对内部使用的未发布软件的依赖,以及处理在将第三方库包装为 C 扩展时可能出现的更复杂的兼容性问题(这尤其关系到科学界)。

索引服务器被有意地赋予了很大的自由来拒绝直接引用,因为它们主要被用作集成商而不是发布者的工具。特别是 PyPI 目前正在经历消除对外部引用的依赖的过程,因为不可靠的外部服务会减慢安装操作的速度,并降低 PyPI 本身明显的可靠性。

添加任意相等

添加任意相等性作为“转义子句”以处理某人需要安装使用不兼容版本的项目的情况。尽管此 PEP 能够实现与 PyPI 上已有的版本约 97% 的兼容性,但仍然有约 3% 的版本无法解析。此运算符提供了一种简单有效的方法来继续依赖它们,而不必“猜测”它们的语义含义(如果支持严格的基于字符串的相等性以外的任何内容,则需要这样做)。

添加本地版本标识符

事实上,下游集成商经常需要将上游错误修复移植到旧版本。这是为 Linux 发行版供应商带来收入的服务之一,应用程序开发人员也可能将他们需要的补丁应用到捆绑的依赖项。

从历史上看,这种做法对跨平台特定于语言的分发工具是不可见的 - 上游元数据中报告的“版本”与未修改代码的版本相同。当尝试使用集成商提供的代码和未修改的上游代码的混合体时,或者只是尝试确定安装的软件的哪个确切版本时,这种不准确性会导致问题。

将本地版本标识符和“本地版本标签”引入版本控制方案,以及相应的 python.integrator 元数据扩展允许此类活动被准确地表示,这应该会提高上游工具与各种集成平台之间的互操作性。

选择的精确方案很大程度上模拟了 pkg_resources.parse_versionpkg_resources.parse_requirements 的现有行为,主要区别在于,在 pkg_resources 目前始终在比较版本以进行精确匹配时考虑后缀的情况下,PEP 要求在版本说明符子句中不存在本地版本标签时忽略候选版本的本地版本标签。此外,PEP 不会尝试对本地版本标签强加任何结构(除了限制允许字符集并定义其排序)。

此更改旨在确保集成商提供的版本(如 pip 1.5+1pip 1.5+1.git.abc123de)仍然满足版本说明符(如 pip>=1.5)。

主要出于本地版本标识符的可读性而选择加号。选择它而不是连字符是为了防止 pkg_resources.parse_version 将其解析为预发布版本,这对于启用到新的、更结构化的版本控制方案的成功迁移非常重要。选择加号而不是波浪号是因为波浪号在 Debian 的版本排序算法中的重要性。

提供显式版本规范化规则

从历史上看,Python 中解析版本的实际标准是 setuptools 项目中的 pkg_resources.parse_version 命令。它不会尝试拒绝任何版本,而是尝试从给定的任何内容中获得有意义的东西,并取得不同程度的成功。它有一些简单的规则,但在其他方面,它或多或少地依赖于字符串比较。

此 PEP 中提供的规范化规则主要存在于以下两种情况:增加与 pkg_resources.parse_version 的兼容性,尤其是在记录的使用案例(如 revrpre 等)中,或者对 PyPI 上已存在的版本做一些更合理的事情。

所有可能的规范化规则都权衡了它们是否可能会导致任何歧义(例如,虽然有人可能会设计一个方案,其中 v1.01.0 被视为不同的版本,但实际上有人这样做的可能性,更不用说在任何显着的规模上,都相当低)。它们还权衡了 pkg_resources.parse_version 如何处理特定的版本字符串,尤其是在如何排序方面。最后,每个规则都权衡了它允许的额外版本类型、这些版本的“丑陋”程度、它们有多难解析(在心理上和机械上)以及它会带来多少额外的兼容性。

可能的规范化范围被限制在可以轻松地作为版本解析的一部分实现,而不是对版本应用预解析转换。这样做是为了限制每次转换的副作用,因为简单的搜索和替换样式转换会增加歧义或“垃圾”版本的可能性。

有关考虑过的各种类型规范化的扩展讨论,请参阅 pip 中 PEP 440 的概念证明 [5]

允许在规范化中使用下划线

PyPI 上没有很多项目在版本字符串中使用 _。但是,此 PEP 允许在 - 可接受的任何位置使用它。这样做的原因是 Wheel 规范化方案指定 - 规范化为 _ 以便于解析文件名。

PEP 440 更改摘要

根据在 setuptools 8.0 和 pip 6.0 中发布初始参考实现后收到的反馈,对 PEP 进行了以下更改

  • 排他的有序比较已更新为不再暗示 !=V.*,这被认为是令人惊讶的行为,难以准确描述。相反,排他的有序比较将简单地不允许匹配指定版本的预发布版本、发布后版本和本地版本(除非指定版本本身是预发布版本、发布后版本或本地版本)。有关扩展讨论,请参阅 distutils-sig 上的主题 [6] [7]
  • 发布候选版本的规范化形式已从“c”更新为“rc”。此更改基于在 setuptools 8.0 开始将规范化应用于在准备用于在 PyPI 上发布的包时生成的发布元数据时收到的用户反馈 [8]
  • PEP 文本和 is_canonical 正则表达式已更新为明确说明数字组件需要专门表示为 ASCII 数字序列,而不是任意 Unicode [Nd] 代码点。这之前由附录 B 中的版本解析正则表达式隐含,但未明确说明 [10]

参考文献

标准化版本方案的初始尝试以及需要此类标准的理由可以在 PEP 386 中找到。

附录 A

元数据 v2.0 指南与 setuptools

$ invoke check.pep440
Total Version Compatibility:              245806/250521 (98.12%)
Total Sorting Compatibility (Unfiltered): 45441/47114 (96.45%)
Total Sorting Compatibility (Filtered):   47057/47114 (99.88%)
Projects with No Compatible Versions:     498/47114 (1.06%)
Projects with Differing Latest Version:   688/47114 (1.46%)

附录 B:使用正则表达式解析版本字符串

如前面Public version identifiers部分所述,发布的版本标识符 SHOULD 使用规范格式。本节提供了一些正则表达式,可用于测试版本是否已采用该格式,如果未采用,则提取各个组件以供后续规范化。

要测试版本标识符是否采用规范格式,可以使用以下函数

import re
def is_canonical(version):
    return re.match(r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$', version) is not None

要提取版本标识符的组件,请使用以下正则表达式(由 packaging 项目定义)

VERSION_PATTERN = r"""
    v?
    (?:
        (?:(?P<epoch>[0-9]+)!)?                           # epoch
        (?P<release>[0-9]+(?:\.[0-9]+)*)                  # release segment
        (?P<pre>                                          # pre-release
            [-_\.]?
            (?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
            [-_\.]?
            (?P<pre_n>[0-9]+)?
        )?
        (?P<post>                                         # post release
            (?:-(?P<post_n1>[0-9]+))
            |
            (?:
                [-_\.]?
                (?P<post_l>post|rev|r)
                [-_\.]?
                (?P<post_n2>[0-9]+)?
            )
        )?
        (?P<dev>                                          # dev release
            [-_\.]?
            (?P<dev_l>dev)
            [-_\.]?
            (?P<dev_n>[0-9]+)?
        )?
    )
    (?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
"""

_regex = re.compile(
    r"^\s*" + VERSION_PATTERN + r"\s*$",
    re.VERBOSE | re.IGNORECASE,
)

来源:https://github.com/python/peps/blob/main/peps/pep-0440.rst

上次修改时间:2023-11-09 05:30:12 GMT