2. 从源代码构建 CPython
2. 从源代码构建 CPython
从源代码构建 CPython 为您提供了一个可以检查、修改、调试和测试的本地解释器。这是认真阅读内部结构之前的第一个实际步骤。
源代码构建可以让您执行打包的 Python 安装通常隐藏的操作:```text change interpreter code add debug prints inspect object layout run CPython tests use debug-only assertions trace reference counts debug crashes in C compare bytecode across builds
CPython 存在于 Git 存储库中。正常的本地结帐如下所示:```bash
git clone https://github.com/python/cpython.git
cd cpython
```该存储库包含解释器、标准库、测试、文档、构建文件和平台支持代码。
简化视图:```text
cpython/
Include/ public and internal C headers
Objects/ core object implementations
Python/ compiler, runtime, interpreter loop
Parser/ tokenizer and parser support
Modules/ built-in and extension modules
Lib/ Python standard library
Lib/test/ regression test suite
Programs/ executable entry points
Tools/ developer tools
Doc/ documentation source
```内部工作最重要的目录是`Objects`, `Python`, `Include`, `Modules`, 和`Lib/test`。
## 2.2 选择构建模式
有两种常见的构建:
|构建 |目的|
| ------------- | ------------------------------------------------ |
|发布版本 |类似于普通安装的Python |
|调试构建 |更好地进行内部工作 |
对于内部结构研究,首先使用调试版本。它支持额外的断言、调试助手、参考跟踪支持和更安全的故障模式。
调试构建速度较慢,但更容易检查。
## 2.3 在 Linux 或 macOS 上构建
在类 Unix 系统上,CPython 使用常用的配置和制作流程。```bash
./configure --with-pydebug
make -j
```这会在源树中生成一个可执行文件,通常命名为:```bash
./python
```运行它:```bash
./python -V
./python -c "print('hello from local CPython')"
```这`--with-pydebug`选项创建一个调试版本。这会更改 ABI 标记并启用调试行为。
常见的开发配置命令是:```bash
./configure --with-pydebug --with-trace-refs
make -j
```使用`--with-trace-refs`仅当您需要更深入的参考跟踪时。它改变了对象布局并可能进一步减慢解释器的速度。
## 2.4 构建依赖关系
在没有每个可选依赖项的情况下,最小构建可能会成功,但许多标准库模块需要系统库。
常见的依赖关系包括:
|特色 |典型的依赖性|
| ---------------------- | -------------------------- |
| SSL 和 HTTPS | OpenSSL |
|压缩| zlib、bzip2、xz |
| SQLite | SQLite 开发头文件 |
| Readline shell 支持 | readline 或 libedit |
|诅咒| ncurses | ncurses |
| Tkinter | Tcl/Tk |
| UUID 支持 | libuuid |
| FFI 支持 |库菲|
如果缺少依赖项,CPython 仍然可以构建,但某些扩展模块将被跳过。
您可以检查构建输出中有关缺少模块的消息。
## 2.5 树外构建
您可以在源目录之外构建 CPython。这使生成的文件保持独立。```bash
mkdir ../cpython-build-debug
cd ../cpython-build-debug
../cpython/configure --with-pydebug
make -j
```生成的可执行文件位于构建目录中:```bash
./python
```当您希望从一个源签出多个配置时,树外构建非常有用:```text
cpython/
cpython-build-debug/
cpython-build-release/
cpython-build-asan/
```## 2.6 在 Windows 上构建
在 Windows 上,CPython 使用 Visual Studio 构建文件。
从开发人员命令提示符:```bat
PCbuild\build.bat -d
```这`-d`flag 构建一个调试解释器。
可执行文件通常位于:```text
PCbuild\amd64\python_d.exe
```发布版本使用:```bat
PCbuild\build.bat
```Windows 构建有自己的项目文件、平台代码和扩展构建规则。布局与 Unix 构建不同,但解释器源代码是相同的核心代码。
## 2.7 验证构建
构建后,检查可执行文件:```bash
./python -V
./python -m sysconfig
```检查解释器认为它安装在哪里:```bash
./python - <<'PY'
import sys
print(sys.executable)
print(sys.prefix)
print(sys.path)
PY
```对于源代码树开发,`sys.path`应该包括当地的`Lib`目录。
## 2.8 运行测试套件
CPython 的测试套件运行于:```bash
./python -m test
```为了更快地进行首次检查:```bash
./python -m test test_sys test_gc test_dict test_compile
```并行运行测试:```bash
./python -m test -j8
```重新运行失败的测试:```bash
./python -m test --fail-env-changed
```运行一个测试文件:```bash
./python -m test test_dict
```运行一个测试用例`unittest`句法:```bash
./python -m unittest Lib.test.test_dict.DictTest.test_constructor
```测试套件是内部工作流程的一部分。当您更改 CPython 时,测试是防止破坏语言行为的第一道防线。
## 2.9 调试构建行为
调试版本改变了 CPython 内部的行为方式。
它可以进行额外的检查,例如:```text
assertions in C code
debug memory allocator checks
extra object consistency checks
reference leak tools
debug ABI marker
stricter failure behavior
```调试版本通常会更早地暴露错误。在发布版本中看似无害的内存误用可能会在调试版本中快速中止。
这很有用。内部工作应该大声失败。
## 2.10 检查构建配置
CPython 通过以下方式公开构建配置`sysconfig`。```python
import sysconfig
print(sysconfig.get_config_var("Py_DEBUG"))
print(sysconfig.get_config_var("WITH_PYMALLOC"))
print(sysconfig.get_config_var("Py_GIL_DISABLED"))
print(sysconfig.get_config_var("CONFIG_ARGS"))
```直接运行:```bash
./python - <<'PY'
import sysconfig
for name in ["Py_DEBUG", "WITH_PYMALLOC", "Py_GIL_DISABLED", "CONFIG_ARGS"]:
print(name, "=", sysconfig.get_config_var(name))
PY
```这会告诉您哪些编译时功能处于活动状态。
## 2.11 有用的构建目标
常见`make`目标:
|目标|目的|
| ------------------ | -------------------------------------- |
|`make`|构建解释器 |
|`make -j`|并行构建 |
|`make clean`|删除许多生成的文件 |
|`make distclean`|也删除配置输出 |
|`make test`|运行测试 |
|`make regen-all`|重新生成生成的文件 |
|`make profile-opt`|使用配置文件引导优化进行构建 |
对于普通的内部工作,请使用:```bash
make -j
./python -m test test_name
```对于生成的文件,仅当您更改语法、临床定义或操作码元数据等输入时才使用重新生成目标。
## 2.12 生成文件
生成一些 CPython 文件。不要盲目编辑它们。
生成的工件可能来自:```text
Grammar definitions
Argument Clinic input
opcode metadata
frozen modules
configuration scripts
documentation tools
```Argument Clinic 在 C 扩展和内置方法定义中尤其常见。它从结构化注释生成解析和包装代码。
C 文件可能包含如下块:```c
/*[clinic input]
module.function
arg: object
Description here.
[clinic start generated code]*/
```生成的部分应该通过正确的工具重新生成,而不是手动编辑。
## 2.13 更改后重建
正常的编辑循环:```bash
vim Objects/listobject.c
make -j
./python -m test test_list
```对于 C 代码的小更改,增量重建通常很快。
对于 Python 标准库更改:```bash
vim Lib/pathlib/__init__.py
./python -m test test_pathlib
```纯 Python 更改不需要 C 重建。
对于解析器、操作码或生成代码的更改,您可能需要在构建之前重新生成。
## 2.14 添加调试打印
确认您正在运行自己的解释器的一个简单方法是添加临时调试打印。
例如,在 C 函数中:```c
fprintf(stderr, "debug: list append called\n");
```然后重建:```bash
make -j
```运行一个小程序:```bash
./python - <<'PY'
x = []
x.append(1)
PY
```临时打印虽然粗糙但有效。在提交之前删除它们。
## 2.15 使用`gdb`或者`lldb`调试构建与本机调试器配合良好。
和`gdb`:
```bash
gdb --args ./python script.py
```里面`gdb`:
```gdb
break PyEval_EvalFrameDefault
run
bt
```和`lldb`:
```bash
lldb -- ./python script.py
```里面`lldb`:
```lldb
breakpoint set --name PyEval_EvalFrameDefault
run
bt
```有用的断点:```text
Py_Initialize
PyEval_EvalFrameDefault
_PyEval_EvalFrameDefault
PyObject_Malloc
PyObject_Free
PyErr_SetString
_Py_Dealloc
```确切的符号名称可能会因版本和构建配置而改变。
## 2.16 使用Python级别的检查
并非每个内部问题都需要 C 调试器。
有用的模块:
|模块|使用|
| ------------- | -------------------------------------- |
|`dis`| Inspect bytecode |
|`sys`|运行时状态和解释器设置|
|`gc`|垃圾收集者检查|
|`inspect`|框架、函数、来源、签名 |
|`types`|运行时类型对象 |
|`sysconfig`|构建配置|
|`tracemalloc`| Python 分配跟踪 |
例子:```python
import dis
import gc
import sys
def f(x):
return x + 1
dis.dis(f)
print(f.__code__)
print(sys.getrefcount(f))
print(gc.is_tracked(f))
```在进入 C 之前,这种风格很有用。
## 2.17 调试内存分配
CPython 有内存分配器的调试钩子。
运行:```bash
PYTHONMALLOC=debug ./python script.py
```这使得可以对内存分配进行额外的检查。它可以检测 API 误用、缓冲区溢出、下溢和一些释放后使用模式。
对于分配跟踪:```bash
./python -X tracemalloc script.py
```或者在Python内部:```python
import tracemalloc
tracemalloc.start()
data = [bytearray(1024) for _ in range(1000)]
current, peak = tracemalloc.get_traced_memory()
print(current, peak)
tracemalloc跟踪 Python 级别的内存分配路径。本机堆调试仍然需要较低级别的工具。
2.18 消毒剂构建
对于严肃的 C 级工作,请使用消毒剂进行构建。
AddressSanitizer 可以检测内存错误:bash ./configure --with-pydebug --with-address-sanitizer make -j UndefinedBehaviorSanitizer 可以检测未定义的 C 行为:```bash
./configure --with-pydebug --with-undefined-behavior-sanitizer
make -j
## 2.19 配置文件引导的发布版本
正常的优化 CPython 版本构建可以使用配置文件引导优化。```bash
make profile-opt
```这将构建 CPython、运行训练工作负载并使用配置文件数据进行重建。
在衡量性能时使用它。不要将调试版本与发布版 Python 进行比较,并将数字视为有意义的。
对于内部结构研究:```text
debug build for correctness and inspection
release build for speed comparison
PGO build for performance-sensitive measurement
```## 2.20 常见构建问题
|症状|可能的原因 |
| ---------------------------- | ---------------------------------------------------------- |
|`_ssl`失踪| OpenSSL 标头或库不可用 |
|`_sqlite3`失踪| SQLite开发包不可用 |
|`readline`失踪| readline 或 libedit 标头不可用 |
|围绕语言环境的测试失败环境区域设置不匹配|
|网络测试失败|外部网络测试或平台限制|
|调试构建导入不匹配|运行错误的可执行文件或错误`PYTHONPATH`|
|过时的生成文件 |需要再生|
|链接器错误 |系统库缺失或不兼容|
在调试 CPython 本身之前,请确认您正在运行刚刚构建的解释器:```bash
./python - <<'PY'
import sys
print(sys.executable)
print(sys.version)
PY
```## 2.21 保留多个构建
实用的设置:```text
cpython/
cpython-build-debug/
cpython-build-release/
cpython-build-asan/
```将每个构建用于不同的工作:
|构建 |使用|
| -------- | -------------------------------- |
|调试|内部解读和断言|
|发布 |行为比较 |
|亚山 |内存错误检测|
| PGO |性能测量|
这避免了不断地重新配置一个构建目录。
## 2.22 最小内部工作流程
一个好的第一个工作流程:```bash
git clone https://github.com/python/cpython.git
cd cpython
./configure --with-pydebug
make -j
./python -V
./python -m test test_sys test_gc test_dict
```然后检查字节码:```bash
./python - <<'PY'
import dis
def f(x):
return x + 1
dis.dis(f)
PY
```然后更改一个小文件,重建并运行有针对性的测试。
## 2.23 此构建可以实现什么
本章结束后,您应该拥有一个本地 CPython 可执行文件,可以在本书的其余部分中使用它。
您现在可以检查:```text
how source becomes bytecode
how frames execute
how objects are laid out
how reference counts change
how the garbage collector tracks containers
how built-in types are implemented
how tests protect behavior
how C-level bugs surface
```源代码构建将 CPython 从黑匣子转变为可以单步调试、检测和修改的系统。