生命之风的低语
Whispers in the Wind of Life.

2.1. 编译 PHPphp7php7

2025-12-07 06:46:34

构建 PHP

本章将说明如何以一种适合开发扩展或者内核修改的方式编译 PHP。我们将仅介绍 Unixoid 系统的构建。如果你希望在 Windows 构建 PHP,你可以在 PHP 维基上看下这个逐步构建说明。

本章也概述了 PHP 构建系统的工作方式和工具使用,但是详细的说明不在本书的范围之内。

免责声明:我们对因尝试在 Windows 编译 PHP 而造成不利健康的影响概不负责。

为什么不使用软件包?

如果您目前正在使用 PHP ,则可能使用 sudo apt-get install php 之类的命令通过软件包管理器进行了安装。在解释实际的编译之前,您应该首先理解为什么自己编译是必要的,而不仅仅是使用预编译的程序包。原因有很多:

首先,预构建包只包含生成的二进制文件,但缺少编译扩展所必需的其他东西,例如头文件。这可以通过安装开发包来轻松解决,这个开发包通常被称为 php-dev。为了便于使用 valgrind 或 gdb 进行调试,可以另外安装调试符号,这些符号通常作为另一个名为 php-dbg 的软件包提供。

但是,即使您安装标头和调试符号,您仍将使用PHP的发行版。这意味着它将以较高的优化级别构建,这会使调试变得非常困难。此外,发行版本不会生成有关内存泄漏或数据结构不一致的警告。此外,预构建的包不支持线程安全,这在开发过程中非常有帮助。

另一个问题是几乎所有的发行版都会向PHP应用额外的补丁。在某些情况下,这些补丁只包含与配置相关的微小更改,但有些发行版使用像 Suhosin 这样的高侵入性补丁。已知其中一些补丁会引入与低级扩展(例如 opcache )的不兼容性。

PHP 仅提供对php.net上提供的软件的支持,不对发行版修改的版本提供支持。如果要报告错误,提交补丁或利用我们的帮助渠道进行扩展编写,则应始终对照官方的PHP版本进行工作。当我们在本书中谈论「PHP」时,我们总是指受官方支持的版本。

获取源代码

在构建 PHP 之前,你必须先获得源代码。有两种方式可以获取:一种是从 PHP 下载页面 下载,一种是从 Git 仓库 克隆(或者 Github的镜像)。

这两种情况下构建 PHP 的过程有些许差异:Git 仓库未捆绑 configure 脚本,所以你需要使用 buildconf 脚本来生成自动配置。此外,Git 仓库不包含预生成解析器,所以你还需要安装Bison。

我们推荐你从 Git 上检出源代码,因为这样方便安装更新和尝试不同版本的代码。如果你想要提交修改或者拉取 PHP 的请求,Git 同样需要检出。

要克隆仓库,在你的Shell中运行一下命令:

~> git clone http://git.php.net/repository/php-src.git

~> cd php-src

# 默认情况下是在master分支上

# 开发版本。你可以改为检出稳定分支:

~/php-src> git checkout PHP-7.0

如果你对 Git 检出有疑问,看下 PHP 维基上 Git 常见问题。Git 常见问题也说明了如果你想要为 PHP 本身做贡献的话,如何设置 Git。此外,它包含为不同 PHP 版本设置多种工作目录的说明。如果你需要测试扩展或更改多种 PHP 版本和配置的话,这对你非常有用。

在继续之前,你应该用你的包管理下载了一些基础构建依赖库(你可能已经默认安装了前三个):

gcc 或者其它的编译套件。

libc-dev,提供 C 的标准库,包含头文件。

make,这是 PHP 使用的构建管理工具。

autoconf,用于生成 configure 脚本。

2.59或更高版本(对于 PHP 7.0-7.1)

2.64或更高版本(对于 PHP 7.2)

2.68或更高版本(对于 PHP 7.3)

libtool,帮助管理共享库。

bison,用于生成 PHP 解析器。

2.4或更高版本(对于 PHP 7.0-7.3)

3.0或更高版本(对于 PHP 7.4)

re2c,用于生成 PHP 词法解析器。当从 Git 仓库构建 PHP 时,re2c 词法生成器曾是可选的依赖项。在 PHP > 7.3 分支上,Git 仓库不再捆绑生成词法分析器文件。

在 Debian/Ubuntu 上,你可以使用以下命令安装所有这些文件:

~/php-src> sudo apt-get install build-essential autoconf libtool bison re2c

根据你在 ./configure 阶段启用的扩展, PHP 需要很多额外的库。当安装这些,请检查软件包版本是否以 -dev 或者 -devel 结尾,然后安装它们。没有 dev 的包通常不包含必要的头文件。例如,默认的 PHP 构建会需要libxml,你可以通过 libxml2-dev 软件包进行安装。

如果你使用 Debian 或者 Ubuntu,你可以使用 sudo apt-get build-dep php7一次性安装大量的可选构建依赖项。如果你只是默认构建,这其中的很多都是不需要考虑的。

构建概述

在仔细研究各个构建步骤前,需要你执行这里的“默认” PHP 构建命令:

~/php-src> ./buildconf # only necessary if building from git

~/php-src> ./configure

~/php-src> make -jN

为了快速构建,请用可用的 CPU 内核数替换 N (请见 grep "cpu cores" /proc/cpuinfo)。

默认 PHP 构建将会为 CLI 和 CGI SAPI 构建二进制文件,它们分别位于 sapi/cli/php 和 sapi/cgi/php-cgi 中。若要检查一切是否正常,可尝试运行 sapi/cli/php -v。

另外你可以运行 sudo make install 安装 PHP 到 /usr/local。在配置阶段,目标路径可以通过指定的 --prefix 更改:

~/php-src> ./configure --prefix=$HOME/myphp

~/php-src> make -jN

~/php-src> make install

这里 $HOME/myphp 是将在 make install 步骤中使用到的安装位置。注意不必安装 PHP,但是如果你想要在扩展开发之外使用 PHP 构建,则会更方便。

现在,让我们仔细看看各个构建步骤!

./buildconf 脚本

如果你从 Git 仓库构建,第一件事就是运行 ./buildconf 脚本。这个脚本除了调用 build/build.mk 文件之外没有什么作用,而该文件又调用了 build/build2.mk。

这些生成文件的主要工作是运行 autoconf 生成 ./configure 脚本和 autoheader 生成 main/php_config.h.in 模板。后一个文件将会被 configure 生成最终配置头文件 main/php_config.h。

这两个实用程序均从 configure.in 文件(指定大多数的 PHP 构建过程), acinclude.m4 文件(指定大量特定于PHP 的M4宏)和单个扩展名和 SAPI 的 config.m4 文件(以及一堆其它 m4 文件)生成的。

好消息是编写扩展甚至修改内核都不需要与构建系统进行太多交互。而在这之后,你必须编写小的 config.m4 文件,但是这些文件通常仅使用 acinclude.m4 提供的两或三个高级宏。因此,我们不在这里做进一步详细介绍。

./buildconf 脚本只有两个选项: --debug, 当你调用 autoconf 和 autoheader 时会禁用警告抑制。除非你想要在构建系统上工作,否则你对这个选项没什么兴趣。

第二个选项是 --force,在发行包中将会允许运行 ./buildconf(例如,如果你下载了打包的源代码,并生成一个新的 ./configure 文件)并另外清除配置缓存 config.cache 和 autom4te.cache/。

如果你使用 git pull (或其他一些命令)更新你的 Git 仓库,并且在 make 步骤中出现奇怪的错误,这通常意味着在构建配置中某些东西已更改,你需要运行 ./buildconf --force。

./configure 脚本

一旦生成 ./configure 脚本,你便可以使用它去定制你的 PHP 构建。你可以使用 --help 列出所有已支持的选项:

~/php-src> ./configure --help | less

帮助的第一部分会列出各种通用选项,所有基于 autoconf 的配置脚本均支持这些选项。 其中一个便是已经提到过的 --prefix=DIR ,它更改了 make install 的安装路径。另一个有用的选项是 -C, 它在 config.cache 文件中缓存了各种测试结果并加快了后面的 ./configure 调用。仅当你已经具有可用的构建并且想要在不同配置之间快速更改时,这个选项才有用。

除了通用的 autoconf 选项之外,PHP 也有一些特定的设置。例如,你可以选择使用 --enable-NAME 和 --disable-NAME 开关来选择应编译的扩展和 SAPI。如果扩展或 SAPI 有外部依赖,你必须使用 --with-NAME 和 --without-NAME 代替。如果 NAME 所需要的库不在默认位置(例如,因为你自己编译),你可以使用 --with-NAME=DIR 指定其位置。

PHP 会默认构建 CLI 和 CGI SAPI,以及许多扩展。你可以使用 -m 选项查出你的 PHP 库包含了哪些扩展。对于默认的 PHP 7.0构建,结果将如下所示:

~/php-src> sapi/cli/php -m

[PHP Modules]

Core

ctype

date

dom

fileinfo

filter

hash

iconv

json

libxml

pcre

PDO

pdo_sqlite

Phar

posix

Reflection

session

SimpleXML

SPL

sqlite3

standard

tokenizer

xml

xmlreader

xmlwriter

如果你现在想要停止编译 CGI SAPI,以及 tokenizer 和 sqlite3 扩展,启用 opcache 和 gmp,相应的 configure 命令将是:

~/php-src> ./configure --disable-cgi --disable-tokenizer --without-sqlite3

--enable-opcache --with-gmp

默认情况下,大多数的扩展都是静态编译的,即它们将成为生成的二进制文件的一部分。默认只有 opcache 扩展共享,即它将在 modules/ 目录生成一个 opcache.so 共享对象 。你可以通过 --enable-NAME=shared 或者 --with-NAME=shared 将其他扩展编译成共享对象(但不是所有的扩展支持这个)。我们将在下一节讨论如何利用共享扩展。

了解你需要使用哪个开关和是否默认启用扩展,请检查 ./configure --help 。如果开关是 --enable-NAME 或 --with-NAME ,则该扩展默认不编译,需要显式启用它。另一方面 --disable-NAME 或 --without-NAME 表明该扩展默认情况下已编译,但可以显式禁用。

一些扩展总是会被编译并启用。使用 --disable-all 选项,则会创建一个包含最少扩展的构建:

~/php-src> ./configure --disable-all && make -jN

~/php-src> sapi/cli/php -m

[PHP Modules]

Core

date

pcre

Reflection

SPL

standard

如果你想要快速构建并且不需要很多功能(例如,实现语言更改)时,--disable-all 选项非常有用。对于尽可能最小的构建来说,你可以另外使用 --disable-cgi开关,这仅生成 CLI 二进制文件。

还有两个开关,在开发扩展或使用 PHP 时,你应 始终 指明:

--enable-debug 启用调试模式,它有多种作用:编译将以 -g 运行生成调试符号,且使用最低优化级别 -O0。这将使 PHP 变得很慢,但是使用 gdb 之类的工具使调试变得更加可预测。另外调试模式定义了 ZEND_DEBUG 宏,它将在引擎中启用各种调试助手。除其他事外,还将报告内存泄露以及一些数据结构的不正确使用。

--enable-maintainer-zts 启用线程安全。该开关将定义 ZTS 宏, 这将启用 PHP 使用的整个 TSRM (线程安全资源管理)机制。PHP 的线程安全编写非常简单,但是前提是确保启用了该开关。如果你需要更多关于 PHP 线程安全和全局内存管理的信息,可阅读 全局管理章节

另一方面,如果你想要为你的代码执行性能基准测试,你不应该使用这两个选项,因为这两者都会导致明显且不对称的减速。

注意 --enable-debug 和 --enable-maintainer-zts 会改变 PHP 二进制文件的 ABI,例如,给很多函数添加额外的参数。因此,在调试模式下编译的共享库与在发行模式下构建的 PHP 二进制文件将会不兼容。类似线程安全扩展(ZTS)与 PHP 构建的非线程安全扩展(NTS)不兼容。

由于 ABI 不兼容, make install (和 PECL 安装)会根据这些选项,将共享库放在不同的目录中:

$PREFIX/lib/php/extensions/no-debug-non-zts-API_NO 用于无 ZTS 的发行版本

$PREFIX/lib/php/extensions/debug-non-zts-API_NO 用于无 ZTS 的调试版本

$PREFIX/lib/php/extensions/no-debug-zts-API_NO 用于 ZTS 的发行版本

$PREFIX/lib/php/extensions/debug-zts-API_NO 用于 ZTS 的调试版本

上面的 API_NO 占位符指的是 ZEND_MODULE_API_NO,它只是类似于 20100525 的日期,用于内部 API 版本控制。

上述的配置开关,对于大多数用途来说已经足够了,但是,./configure 当然提供了更多的选项,你可在帮助中找到这些选项。

除了给配置传递选项外,你也可以指定许多环境变量。一些更重要的信息记录在配置帮助输出的末尾(./configure --help | tail -25)。

例如,你可以使用 CC 去使用其他编译器,使用 CFLAGS 去更改使用的编译标志:

~/php-src> ./configure --disable-all CC=clang CFLAGS="-O3 -march=native"

在这个配置中,构建将使用 clang (而不是 gcc),并使用一个很高级别的优化(-O3 -march=native)。

你可以使用另外的编译器警告标志,这可以帮助你发现一些错误。对于 GCC,你可以阅读它们 在 GCC 手册中

make 和 make install

在一切都配置好后,你可以使用 make 去执行实际的编译:

~/php-src> make -jN # N 是内核的数量

这个操作最主要的结果是启用 SAPI 的 PHP 二进制文件(默认 sapi/cli/php 和 sapi/cgi/php-cgi),以及 modules/ 目录下的 共享扩展。

现在你可以运行 make install 安装 PHP 到 /usr/local (默认)或者你使用 --prefix 配置开关指定的任何目录。

make install 只是复制大量的文件到新的位置。除非你在配置中指定 --without-pear,否则它将下载和安装 PEAR。这里是默认 PHP 构建的结果树:

> tree -L 3 -F ~/myphp

/home/myuser/myphp

|-- bin

| |-- pear*

| |-- peardev*

| |-- pecl*

| |-- phar -> /home/myuser/myphp/bin/phar.phar*

| |-- phar.phar*

| |-- php*

| |-- php-cgi*

| |-- php-config*

| `-- phpize*

|-- etc

| `-- pear.conf

|-- include

| `-- php

| |-- ext/

| |-- include/

| |-- main/

| |-- sapi/

| |-- TSRM/

| `-- Zend/

|-- lib

| `-- php

| |-- Archive/

| |-- build/

| |-- Console/

| |-- data/

| |-- doc/

| |-- OS/

| |-- PEAR/

| |-- PEAR5.php

| |-- pearcmd.php

| |-- PEAR.php

| |-- peclcmd.php

| |-- Structures/

| |-- System.php

| |-- test/

| `-- XML/

`-- php

`-- man

`-- man1/

目录结构的简短概述:

bin/ 包含了 SAPI 二进制文件(php 和 php-cgi),以及 phpize 和 php-config 脚本。它同样是各种 PEAR/PECL 脚本的所在地。

etc/ 包含了配置。请注意,默认的 php.ini 文件不在这里。

include/php 包含了头文件,在自定义软件中,这些是构建附加扩展或者 PHP 嵌入所必需的。

lib/php 包含了 PEAR 文件。lib/php/build 目录包含了构建扩展所必需的文件,例如 acinclude.m4 文件包含了 PHP 的 M4 宏。如果我们编译了任何共享扩展,则这些文件将位于 lib/php/extensions 的子目录下。

php/man 显然包含了 php 命令的手册。

如上所述,默认的 php.ini 不在 etc/.。您可以使用PHP二进制文件的 --ini 选项显示位置:

~/myphp/bin> ./php --ini

Configuration File (php.ini) Path: /home/myuser/myphp/lib

Loaded Configuration File: (none)

Scan for additional .ini files in: (none)

Additional .ini files parsed: (none)

如您所见,默认的 php.ini 目录是$ PREFIX / lib(libdir),而不是$ PREFIX / etc(sysconfdir)。您可以使用-with-config-file-path = PATH配置选项来调整默认的 php.ini 位置。

同样也要注意一下 make install 不会创建 ini 文件。如果你想要使用 php.ini 文件,你需要自己创建一个。例如,你可以复制默认的开发配置文件:

~/myphp/bin> cp ~/php-src/php.ini-development ~/myphp/lib/php.ini

~/myphp/bin> ./php --ini

Configuration File (php.ini) Path: /home/myuser/myphp/lib

Loaded Configuration File: /home/myuser/myphp/lib/php.ini

Scan for additional .ini files in: (none)

Additional .ini files parsed: (none)

除了 PHP 二进制文件, bin/ 目录下同样有两个重要的脚本: phpize 和 php-config。

phpize 相当于 ./buildconf 的扩展。它会从 lib/php/build 复制各种文件,并调用 autoconf/autoheader。在下一节,你将会学习更多关于这个工具的知识。

php-config 提供有关于 PHP 构建的配置的信息。试试看:

~/myphp/bin> ./php-config

Usage: ./php-config [OPTION]

Options:

--prefix [/home/myuser/myphp]

--includes [-I/home/myuser/myphp/include/php -I/home/myuser/myphp/include/php/main -I/home/myuser/myphp/include/php/TSRM -I/home/myuser/myphp/include/php/Zend -I/home/myuser/myphp/include/php/ext -I/home/myuser/myphp/include/php/ext/date/lib]

--ldflags [ -L/usr/lib/i386-linux-gnu]

--libs [-lcrypt -lresolv -lcrypt -lrt -lrt -lm -ldl -lnsl -lxml2 -lxml2 -lxml2 -lcrypt -lxml2 -lxml2 -lxml2 -lcrypt ]

--extension-dir [/home/myuser/myphp/lib/php/extensions/debug-zts-20100525]

--include-dir [/home/myuser/myphp/include/php]

--man-dir [/home/myuser/myphp/php/man]

--php-binary [/home/myuser/myphp/bin/php]

--php-sapis [ cli cgi]

--configure-options [--prefix=/home/myuser/myphp --enable-debug --enable-maintainer-zts]

--version [5.4.16-dev]

--vernum [50416]

该脚本类似于由 Linux 发行版使用的 pkg-config 脚本。在扩展构建过程,调用它以获得有关编译器选项和路径的信息。你也可以利用它快速获得有关你的构建的信息,例如,你的配置选项或默认扩展目录。 ./php -i(phpinfo)同样也可以提供这些信息,但是 php-config 以一种更简单的形式提供此信息(可以由自动化工具轻松使用)。

运行测试套件

如果你的 make 命令成功完成,它会打印一条信息鼓励你去运行 make test:

Build complete.

Don't forget to run 'make test'

make test 会针对我们的测试套件运行 PHP CLI 二进制文件,它位于不同的 PHP 资源树下的 tests/ 目录。默认的构建下是运行大约 9000 个测试 (对最小构建来说更少,对启用附加扩展来说则更多),这可能需要几分钟。 make test 命令当前是非并行的,所以指定 -jN 选项也不会让它变快。

如果你的平台是第一次编译 PHP,我们希望你能运行测试套件。根据你的系统和构建环境,在运行测试时你可能会找到错误。如果没有任何错误,该脚本会问你是否要发送一份报告给我们的质量检查平台,这将使贡献者能够分析错误。请注意,有一些失败的测试是相当正常的,只要你没有看到十几个错误,你的构建仍可能正常工作。

make test 命令使用你的 CLI 二进制文件在内部调用 run-tests.php 文件。那你可以运行 sapi/cli/php run-tests.php --help 显示该脚本接受的选项列表。

如果你手动运行 run-tests.php,你必须指定 -p 或 -P 选项(或者一个难看的环境变量):

~/php-src> sapi/cli/php run-tests.php -p `pwd`/sapi/cli/php

~/php-src> sapi/cli/php run-tests.php -P

-p 是测试使用的显式指定一个二进制文件。请注意,为了正确地运行所有测试,它应该是一个绝对路径(或者独立于它调用的目录)。 -P 是调用 run-tests.php 的二进制文件的快捷方式。在上面的例子中,这两种方式都是相同的。

除了运行整个测试套件,你也可以通过将它们作为参数传递给 run-tests.php ,使其限制在某些目录中。例如,只测试 Zend 引擎、reflection 扩展和数组函数:

~/php-src> sapi/cli/php run-tests.php -P Zend/ ext/reflection/ ext/standard/tests/array/

这非常有用,因为它允许你快速运行只与你的更改有关的测试套件部分。例如,如果你做了语言的修改,你可能不关心扩展的测试,只想要验证 Zend 引擎是否仍然正确的工作。

使用 run-tests.php 时,你不需要传递选项或限制目录。除非你可以通过 make test 使用 TESTS 变量去传递另外的参数。例如,与先前的命令相等的是:

~/php-src> make test TESTS="Zend/ ext/reflection/ ext/standard/tests/array/"

在之后,我们将会更详细地查看 run-tests.php 系统,尤其是会讨论怎么编写我们的测试和调试失败的测试。查看专用测试章节。

修复编译问题并 make clean

你可能知道 make 是增量构建的,即不会重新编译所有文件,而是重新编译那些在最后调用中改变的 .c 文件。这是一个很好的缩短构建时间的方式,但是它并不是总能做好:例如,如果你在头文件修改了一个结构, make 不会自动重新编译使用该头文件的所有 .c 文件,从而导致构建失败。

如果运行 make时遇到奇怪的错误或生成的二进制文件损坏(例如,在运行第一次测试之前, make test 就崩溃了),你应该尝试运行 make clean。它会删除所有已编译的对象,强制下一次 make 调用运行完整构建。

有时候,你必须在更改 ./configure 选项之后运行 make clean。 如果只是启用额外的扩展,则增量构建应是安全的,但是改变其他的选项可能需要完全重建。

通过 make distclean 命令可以达到更强效的清理目标。它除了运行正常的清理,还会回滚所有 ./configure 命令调用带来的的文件。它会删除配置缓存、make文件、配置头文件和其他各种文件。顾名思义,该目标是“分布清理”,所以通常由发行管理者使用。

另一个编译问题的来源是 config.m4 文件或 PHP 构建系统中的其他文件的修改。如果像这样的文件被修改,则必须运行重新 ./buildconf 脚本。如果你自己做了修改,你可能会记得运行该命令,但如果它是作为 git pull(或其他一些更新命令)的一部分发生的,则问题可能不会很明显。

如果你遇到一些奇怪的编译问题,但是通过 make clean 不能解决,运行 ./buildconf --force 有机会修复这个问题。避免优先命令 ./configure 在后面输入,你可以使用 ./config.nice 脚本(它包含了你的最后一次 ./configure 调用):

~/php-src> make clean

~/php-src> ./buildconf --force

~/php-src> ./config.nice

~/php-src> make -jN

PHP 提供的最后一个清理脚本是 ./vcsclean。它只有在你从 Git 检出源代码才有效。 它有效地归结为对 git clean -X -f -d 的调用,它会移除所有 Git 忽略的未跟踪文件和目录。你应该小心使用。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接

我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/php-internals/p...

译文地址:https://learnku.com/docs/php-internals/p...