2. 从源代码构建 CPython

克隆存储库、安装依赖项、运行 ./configure 并从源代码编译 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 |
 | 压缩| zlibbzip2xz |
 | 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 上构建

 On Windows, CPython uses Visual Studio build files.

 从开发人员命令提示符:```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`| 检查字节码 |
 |`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 从黑匣子转变为可以单步调试、检测和修改的系统。