2.1. 编译 PHP
构建 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 忽略的未跟踪文件和目录。你应该小心使用。