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 忽略的未跟踪文件和目录。你应该小心使用。

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

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
贡献者:5
讨论数量: 0
发起讨论 只看当前版本


暂无话题~