贡献指南

PyPy 是一个非常庞大的项目,以难以深入研究而闻名。其中一些名声是合理的,而另一些则是纯粹的偶然。对于所有愿意贡献的人来说,有三个重要的教训需要学习

  • PyPy 有层次结构。架构中有许多部分彼此之间非常独立。下面会详细介绍,但通常表现为事情处于与你预期不同的层次。例如,如果你正在寻找 JIT 实现,你不会在 Python 编程语言的实现中找到它。
  • 由于上述原因,我们非常重视测试驱动开发。这不仅是我们所相信的,而且 PyPy 的架构非常适合 TDD,而没有 TDD 则效果不佳。开发通常意味着在一个无关的角落逐步前进,一次一个单元测试;然后翻转一个巨大的开关,将所有内容整合在一起。(它通常开箱即用。如果不行,那就是我们没有写足够的单元测试。)值得重复的是 - PyPy 的方法在使用 TDD 时非常棒,而在不使用 TDD 时则效果不佳。
  • PyPy 使用一组完全不同的工具 - 其中大多数包含在 PyPy 存储库中。没有 Makefile 也没有 autoconf。下面会详细介绍。

首先要记住的是,PyPy 项目与大多数其他项目截然不同。它也与传统的编译器项目不同,因此关于编译器的学术课程通常不适用或会导致错误的方向。但是,如果你想了解在现实世界中设计和构建运行时的工作原理,那么这是一个很棒的项目!

参与

PyPy 采用相对标准的开源开发流程。作为第一步,我们鼓励你加入我们的 pypy-dev 邮件列表 和 IRC 频道,详细信息可以在我们的 联系 部分找到。那里的人非常友好,可以为你指明正确的方向。

我们通常会非常慷慨地授予提交权限,因此如果你想用 PyPy 做点什么,你可以通过登录 https://foss.heptapod.net 并点击 PyPy 组页面 上的“请求访问”链接来成为“开发者”。我们还会举办编码冲刺,这些冲刺会单独宣布,通常会在 博客 上宣布。

与任何开源项目一样,问题应该在 问题跟踪器 上提交,并且欢迎 拉取请求 来修复问题。

进一步阅读:联系

你的第一个贡献

第一个也是最重要的规则是如何 **不** 贡献 PyPy,那就是“只是黑客一个功能”。这行不通,你会发现你的 PR 通常需要很多重新工作。有几个原因

  • 构建时间很长
  • PyPy 的层级分离非常厚重
  • 通常需要 cPython 运行时的上下文

相反,请在开发者邮件列表或 IRC 频道联系我们,我们很乐意提供帮助!:)

一些第一个贡献的想法是

  • 文档 - 这将使您了解 pypy 的架构
  • 测试失败 - 在 nightly builds 中找到一个失败的测试,并修复它
  • 缺少的语言特性 - 这些列在我们 issue tracker

源代码控制

PyPy 的主要 git 仓库托管在这里:https://github.com/pypy,而旧的仓库托管在这里:https://foss.heptapod.net/pypy.

Pypy 的旧仓库托管在 Heptapod 上。Heptapod 是 GitLab 社区版的友好分支,支持 Mercurial。 https://foss.heptapod.net 是一个面向自由和开源软件的公共实例(更多信息 here)。

感谢 OctobusClever Cloud 提供这项服务!

Octobus + Clever Cloud

克隆

编辑

  • 编辑内容。使用 git diff 查看您所做的更改。使用 git add 使 git 了解您添加的新文件,例如新的测试文件。使用 git status 查看是否有此类文件。编写并运行测试!(请参阅本页的其余部分。)
  • 使用 git commit 定期提交。一行提交消息就可以了。我们喜欢大量的提交;只要您取得了一些进展,就进行一次提交,即使它只是一些尚未通过的新测试,或者修复了一些东西,即使并非所有测试都通过。一步一步地,您正在构建更改的历史记录,这是版本控制系统的意义所在。(有一些命令,例如 git log,您应该稍后阅读,以了解如何浏览此历史记录。)
  • 提交将保留在您的机器上,直到您执行 git push 将它们“推送到”您的分支。命令 git pushgit pull 在提交之间进行复制,目标是所有相关的仓库最终都拥有完全相同的提交集。
  • 您应该经常推送;没有真正的理由不这样做。请记住,即使它们被推送,在上面的设置中,提交只存在于您命名的分支中。是的,它们是公开可见的,但不要担心有人在 PyPy 的众多分支中走来走去,说“哈哈,看看那个人的糟糕编码风格”。试着进入这样的心态,您的工作不是秘密,这样很好。我们可能不会按原样接受它,而是要求您改进一些东西,但除非您不编写测试,否则我们不会评判您。

拉取请求

  • 最后一步是打开一个拉取请求,这样我们就知道您想将该分支合并回原始的 pypy/pypy 仓库。如果您的分支有有趣的中间状态,也可以多次执行此操作,但如果您到达那里,那么我们很可能会进入下一阶段,即……
  • 如果你更深入日常开发,你会发现我们通常会将小的改动以一个或几个提交的形式直接推送到 defaultpy3.9 分支。此外,即使我们在其他分支上,我们也经常协作,这些分支并不真正“属于”任何人。此时,你需要使用 git merge 并学习如何解决冲突,这些冲突有时会发生在两个人尝试在同一个分支上并行推送不同的提交时。但这很可能是一个以后的问题 :-)

架构

PyPy 有层级结构,就像食人魔或洋葱一样。这些层级帮助我们将各个部分分开,以便独立进行工作,并使复杂性易于管理。这同样是如此复杂项目的一项基本要求。例如,为 JIT 编写新的优化通常**不会**涉及到 Python 解释器本身,或者 JIT 汇编器后端或垃圾收集器。相反,它需要在 rpython/jit/metainterp/optimizeopt/test/test_* 中编写小的测试,并修复那里的文件。之后,你只需编译 PyPy,一切应该都能正常工作。

进一步阅读:架构

从哪里开始?

PyPy 由相对独立的部分组成。你应该从最吸引你的部分开始(所有路径相对于 PyPy 顶层目录)。你可以查看我们的 目录参考,或者从以下几个点开始

构建

为了构建 PyPy,我们建议首先安装一个预构建的 PyPy(参见 下载和安装 PyPy)。可以使用 CPython 构建 PyPy,但运行时间会长得多——根据你的架构,运行时间会是两到三倍。

进一步阅读:构建

编码指南

除了常见的 pep8 和格式化标准之外,在浏览源代码之前,还需要了解一些重要的命名约定和编码风格。

进一步阅读:编码指南

测试

测试驱动开发

相反,我们实践了很多测试驱动开发。这部分是因为编译器对质量要求很高,部分是因为没有其他方法可以解决如此复杂的项目,这会让你保持理智。可能有些人足够聪明,不需要它,我们不是其中之一。你可以考虑熟悉一下 pytest,因为这是我们用于测试的工具。我们在树的顶部发布了我们自己的调整版本的 pytest,所以 python -m pytest 会选择我们的版本,这意味着我们的测试需要使用该版本的 pytest 运行。

我们还在 extra_tests 目录中进行翻译后测试,这些测试在虚拟环境中从一个单独的目录运行,因此它们使用更新版本的 pytest。尽可能地,这些测试应该与 CPython 一起通过。

运行 PyPy 的单元测试

PyPy 开发一直以来都是彻底的测试驱动。测试有两种模式:那些在翻译之前在 RPython 之上运行的(未翻译测试)和那些在翻译后的 pypy 之上运行的(应用程序测试)。由于 RPython 是 Python2 的一种方言,未翻译的测试使用 python2 主机运行。

PyPy 源代码树附带一个内联版本的 py.test,你可以通过键入以下命令来调用它

python2 pytest.py -h

你需要 构建时依赖项 才能成功运行测试,因为其中许多测试会编译 PyPy 的一小部分,然后在那个最小解释器中运行测试。 cpyext 测试还需要 pycparser,许多测试使用 hypothesis 构建案例。

现在开始运行一些测试。PyPy 有许多不同的测试目录,你可以使用 shell 自动补全来指向目录或文件

python2 pytest.py pypy/interpreter/test/test_pyframe.py

# or for running tests of a whole subdirectory
python2 pytest.py pypy/interpreter/

注意,不要尝试通过指向根目录甚至顶层子目录 pypy 来运行“所有”pypy 测试。这需要几个小时,并且会消耗大量的 RAM,不建议这样做。

要运行 CPython 回归测试,你应该从一个翻译后的 PyPy 开始,并像使用 CPython 一样运行测试(见下文)。但是,你也可以尝试在翻译之前运行测试,但要注意,这是通过一个在所有情况下都无法正常工作的黑客完成的,而且通常非常慢:py.test lib-python/2.7/test/test_datetime.py。通常,一个更好的方法是提取一个最多几行的最小失败测试,并将其放入我们自己的测试之一中,位于 pypy/*/test/

应用程序级测试

虽然通常调用 python2 pytest.py 在运行在 CPython 之上的未翻译的 PyPy 上运行应用程序级测试,但我们有一个测试扩展来直接在主机 python 上运行测试。这对于 cpyext 等模块来说非常方便,可以比较和对比 CPython 和 PyPy 之间的测试结果。

应用程序级测试(文件名以 apptest_ 而不是 test_ 开头的测试)在将 -D–direct-apptest 传递给 pytest 时直接在主机解释器上运行

pypy3 -m pytest -D pypy/interpreter/test/apptest_pyframe.py

混合级别测试(通常以 test_ 开头的测试)可以通过使用 -A–runappdirect 选项调用 pytest

python2 pytest.py -A pypy/module/cpyext/test

其中 python2 可以是 python2pypy2。在 py3 分支上,收集阶段必须使用 python2 运行,因此未翻译的测试将使用

python2 pytest.py -A pypy/module/cpyext/test --python=path/to/pypy3

翻译后测试

如果您运行翻译,您最终将在运行翻译的目录中得到一个名为 pypy-c(或 Python3 分支的 pypy3-c)的二进制文件。

要从标准 CPython 回归测试套件运行测试,请使用常规的 Python 方式,即(使用确切的二进制文件名称)

./pypy3-c -m test.test_datetime
# or
./pypy3-c lib-python/3/test/test_audit.py

Buildbot

PyPy 在 https://buildbot.pypy.org 上运行一个基于 buildbot 的 CI 系统。这是由 https://foss.heptapod.net/pypy/buildbot 上的代码驱动的。x86_64、i686 和 aarch64 上的 Linux 运行器使用 Docker 容器,它管理依赖项。有关更多信息,请参阅 Dockerfile。Windows 运行器使用 externals 存储库的 win64_14x 分支中的依赖项。macOS 运行器(x86_64、arm64)使用 M1 机器上的 venv。

工具和实用程序

如果您对 PyPy Python 解释器的内部工作原理感兴趣,未翻译的 Python 解释器中有一些功能可以让您内省其内部结构。

解释器级控制台

要开始使用 PyPy 解释 Python,请安装 distutils 支持的 C 编译器,并使用 Python 2.7 或更高版本运行 PyPy

cd pypy
python bin/pyinteractive.py

几秒钟后(请记住:这是在 CPython 之上运行的),您应该位于 PyPy 提示符处,它与 Python 提示符相同,但多了一个“>”。

如果您在控制台上按 <Ctrl-C>,您将进入解释器级控制台,这是一个普通的 CPython 控制台。然后,您可以使用前缀 w_ 访问 PyPy 的内部对象(例如 对象空间)以及您在 PyPy 提示符处创建的任何变量。

>>>> a = 123
>>>> <Ctrl-C>
*** Entering interpreter-level console ***
>>> w_a
W_IntObject(123)

该机制在两个方向上都有效。如果您在解释器级别使用 w_ 前缀定义一个变量,您将在应用程序级别看到它。

>>> w_l = space.newlist([space.wrap(1), space.wrap("abc")])
>>> <Ctrl-D>
*** Leaving interpreter-level console ***

KeyboardInterrupt
>>>> l
[1, 'abc']

请注意,解释器级控制台的提示符只是“>>>”,因为它在 CPython 级别运行。如果您想返回 PyPy,请按 <Ctrl-D>(在 Linux 下)或 <Ctrl-Z>、<Enter>(在 Windows 下)。

还要注意,并非所有模块在此模式下默认可用(例如:_continuationgreenlet 需要),您可能需要使用其中一个 --withmod-... 命令行选项。

您可能对阅读有关 解释器级和应用程序级 之间区别的更多信息感兴趣。

pyinteractive.py 选项

要列出 PyPy 解释器命令行选项,请键入

cd pypy
python bin/pyinteractive.py --help

pyinteractive.py 支持 CPython 支持的大多数选项(除了大量可用于自定义 pyinteractive.py 的选项)。作为从命令行使用 PyPy 的示例,您可以键入

python pyinteractive.py --withmod-time -c "from test import pystone; pystone.main(10)"

或者,与常规 Python 一样,您只需在命令行上提供脚本名称即可

python pyinteractive.py --withmod-time ../../lib-python/2.7/test/pystone.py 10

选项 --withmod-xxx 启用内置模块 xxx。默认情况下,几乎没有一个模块是启用的,因为初始化它们需要时间。如果您无论如何都想要启用所有内置模块,您可以使用 --allworkingmodules

有关所有命令行选项的功能的详细信息,请参阅我们的 配置部分

跟踪字节码和对对象的运算

您可以使用简单的跟踪模式来监控字节码的解释。要启用它,请在交互式 PyPy 控制台中设置 __pytrace__ = 1

>>>> __pytrace__ = 1
Tracing enabled
>>>> x = 5
        <module>:           LOAD_CONST    0 (5)
        <module>:           STORE_NAME    0 (x)
        <module>:           LOAD_CONST    1 (None)
        <module>:           RETURN_VALUE    0
>>>> x
        <module>:           LOAD_NAME    0 (x)
        <module>:           PRINT_EXPR    0
5
        <module>:           LOAD_CONST    0 (None)
        <module>:           RETURN_VALUE    0
>>>>

演示

example-interpreter 存储库包含使用 RPython 翻译工具链编写的示例解释器。