编译php

本章将介绍如何以适合开发扩展或者核心修改的方式编译php。我们仅介绍在unix系统上的编译.如果你希望在Windows上编译,你应该在php wiki看一下 step-by-step build instructions

本章只提供了一个概要关于php编译系统是如何工作的和需要使用到的工具,细节描述是在本书的另外章节

[1] 免责声明:我们对试图在windows上编译php造成的不良后果概不负责.

为什么不使用安装包?

如果你正在使用php,你可能通过包管理器安装它,只需使用像这样的一个命令 sudo apt-get install php .在介绍真正编译之前,我们首先要明白为什么我们自己编译php是必要的,而不是使用已经编译好的安装包,下面有几个原因:

首先,编译好的包仅包含二进制结果,缺少其他一些编译扩展必要的一些东西,比如头文件.但可以很容易的通过安装一个开发包来弥补这个问题,比如典型的叫php-dev的工具。为了方便使用valgrind或gdb进行调试,可以另外安装调试符号工具,通常使用另一个叫php-dbg的包。

但是即使你安装了头文件和调试符号工具,你仍然不能工作在一个php的发行版本.这意味着它是在一个高度优化的级别被编译,调试是非常困难的,而且发布版本的编译不能生成一些关于内存泄露或者不一致的数据结构的警告信息。另外,编译好的包没有启用线程安全,它是非常有用的在开发期间.

另一个问题是几乎所有的其他分布版本都会给php应用额外的补丁,有些时候这些补丁仅包含和配置有关的很小的改变,但是这些版本使用了高侵入的补丁,像Suhosin。这其中的一些补丁众所周知是不兼容的与低版本 扩展,比如opcache.

PHP只为在php.net提供的软件提供支持,不会为其他分布修改的版本.如果你想要去报告bugs,你可以提交补丁或者利用写扩展的方式,你应该总是在php官方版本下工作。我们在本书谈论的php,通常指的是php官方支持的版本.

获取源代码

在你编译php之前首先要获取它的源代码.通常有两种方式:你可以从php官网的下载页面下载一个文件档案或者clone git仓库从git.php.net上(或者github上的其他镜像)。

这两种方式的构建过程稍有不同:git仓库没有捆绑configure脚本,因此你需要使用autoconf工具的buildconf脚本去生成一个。另外git仓库没有包含一个已经预生成的解析器,因此你也要去安装bison。

我们建议从git上检出源代码,因为它让你用很容易方式保持安装更新,并尝试使用不同版本的代码。如果你想要提交补丁或者拉取请求,从git仓库检出也是必须的。

从仓库clone代码,在你的shell运行下面的命令:

~> git clone http://git.php.net/repository/php-src.git
~> cd php-src
# by default you will be on the master branch, which is the current
# development version. You can check out a stable branch instead:
~/php-src> git checkout PHP-7.0

如果在检出代码的过程中遇到问题,看一下PHP wiki 上的Git FAQ。Git FAQ也介绍了如何去设置git如果你想要为php做贡献。另外它还包含一些关于对不同php版本设置多个不同工作目录的说明。这是非常有用的让你测试你的扩展或者针对多个php版本和配置做出改变。

在继续之前,你应该用包管理器安装一些基本的编译依赖工具(默认情况下,你可能已经安装了前三个)

  • gcc or some other compiler suite.
  • libc-dev, which provides the C standard library, including headers.
  • make, which is the build-management tool PHP uses.
  • autoconf(2.59 or higher), which is used to generate theconfigurescript.
  • automake(1.4 or higher), which generatesMakefile.infiles.
  • libtool, which helps manage shared libraries.
  • bison(2.4 or higher), which is used to generate the PHP parser.
  • (optional)re2c, which is used to generate the PHP lexer. As the git repository already contains a generated lexer you will only need re2c if you wish to make changes to it.

在Debian/Ubuntu系统上使用下面的命令安装它们:

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

取决于你在./configure期间启用的扩展,php可能需要一些额外的库。当安装它们的时候,如果一个包有以-dev或者-devel结尾的版本,则尽量安装它们。没有dev的包通常没有包含必要的头文件,比如默认编译一个php将需要libxml库,你能通过libxml12-dev包安装。

如果你正在使用Debian或者Ubuntu系统,你可以使用sudo apt-get build-dep php5 命令去一次性安装所有编译依赖的库,如果你的目标仅是默认编译,然而他们中大多数都不是必须的。

编译概述

在仔细查看每个编译步骤具体是干什么的之前,对于php默认天意这里有一些命令需要去执行

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

为了快速编译,把N设置成你当前机器可用的cpu核心数(看 grep "cpu cores" /proc/cpuinfo)

默认php将CLI和CGI SAPIs编译成二进制程序,分别放在sapi/cli/php和sapi/cgi/php-cgi.尝试运行sapi/cli/php -v去检查是否编译完成

另外还可以娙sudo make install 命令去安装php到usr/local.这个目录可以在configure阶段通过指定一个 --prefix来改变。

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

这里$HOME/myphp是执行make install后的安装目录。注意安装php不是必须的。但是如果想要在扩展开发之外使用php编译将会是很方便 (这里安逸可能有点问题,我个人理解应该是方便以后编译一些php扩展)。

现在我们仔细看一下每个单独 编译步骤!

The./buildconf script

如果你正在从git仓库拉取代码进行编译,首先要做的就是执行./buildconf脚本.这个脚本仅仅是调用了 build/build.mk的makefile,而makefile又调用了build/build2.mk

这些makefile的主要工作就是运行autoconf和autoheader分别去生成./configure脚本和main/phpconfig.h.in模板。之后的文件将用来用过configure生成最后的配置头文件main/php_config.h

两个实例程序都产生它们的结果,configure.in文件(指定大部分php编译过程),acinclude.m4文件(指定了大量php的特定m4宏),还有每个扩展的config.m4文件以及SAPIs(以及一堆其他m4文件)

好的消息是写扩展甚至是修改核心都不是必须与编译系统交互,只需要去写很小的config.m4文件,通常只使用两到三个acinclude.m4文件里面的高级宏。之后我们也不会深入去讲。

./buildconf脚本只有两个选项:--debug 在调用autoconf和autoheader的时候抑制警告信息,除非你想在编译系统上工作,否则这个选项对你没什么意义

第二个选项是 --force,让你在发布包执行./buildconf (如果你下载了源码包想要生成一个新的./configure)另外会清理config.cache和autom4te.cache缓存

如果你使用git pull 更新git仓库(或者其他一些命令)或者在make期间遇到奇怪的错误,这通常意味着一些东西在编译配置期间已经被改变了,这时你可以运行 ./buildconf --force.

The./configure script

一旦./configure文件生成了,你能利用它去自定义你的php编译。使用--help选项去列出所有支持的选项

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

help的第一部分将列出多个普通的选项,这些选项由所有基于autoconf的配置脚本支持。它们中的一个之前已经提到过 --prefix=DIR 改变make install的安装目录。另一个有用的选项是-c,将缓存多个测试结果进config.cache文件并且加快后面./configure调用的速度。使用这个选项仅在你已经编译过一次又想换不同的配置进行快速编译才会有作用。

除了常规的几个autoconf选项外,这里还有许多对php的设置。比如,你可以选择哪个扩展和SAPI可以被编译,通过--enable-NAME和disable-NAME进行切换。如果扩展或者SAPI有额外的依赖,你需要使用--with-NAME和--without-NAME.如果需要的库在默认目录没有找到,可以通过--with-NAME=DIR指定它的位置

默认php将编译CLI和CGI SAPIs,和一些扩展。你能通过使用-m选项找到你的php二进制程序包含哪些扩展,php7.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,下面是相应的配置命令:

~/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-cgi选项,仅生成CLI二进制包。

这里有两个更多的选项开关,在开发扩展或者在php上工作的时候总是应该指定:

--enable-debug 启用debug模式,有多个作用:编译将以-g运行生成debug符号信息,另外使用最小优化级别。这会使php变的非常慢,但使像gdb这样的调试工具进行调试更加可预测。另外debug模式定义了ZEND_DEBUG宏,将启动引擎中的各种调试助手。另外像内存泄露,不正确数据结构都将会被提示。

--enable-maintainer-zts 启用线程安全。这个选项将定义ZTS宏,反过来php将启用完整的TSRM(线程安全资源管理)。为php写线程安全的扩展是很简单的,如果你确保已经启用了该选项。

注意 --enable-debug和--enable-maintaniner-zts会改变PHP二进制的API(application binary interface),比如给许多函数额外增加了参数。因此在debug模式编译的共享扩展是不兼容的与在发布模式。相似的一个线程安全的扩展是不兼容的与一个非线程安全的扩展。

因为ABI的不兼容,make install(PECL install)将把共享扩展放进不同的目录,取决于下面的选项:

  • $PREFIX/lib/php/extensions/no-debug-non-zts-API_NO for release builds without ZTS
  • $PREFIX/lib/php/extensions/debug-non-zts-API_NO for debug builds without ZTS
  • $PREFIX/lib/php/extensions/no-debug-zts-API_NO for release builds with ZTS
  • $PREFIX/lib/php/extensions/debug-zts-API_NO for debug builds with ZTS

上面API_NO占位符指的是ZEND_MODULE_API_NO,像是一个20100525这样的日期,用来作为内部API的版本号。

对于大部分目的,上面的配置选项应该是足够的了,当然./configure提供了更多的选项,你可以在帮助手册中找到。

除了给configure添加选项外,你也可以指定许多环境变量。最重要的一些是放在configure帮助输出的尾端(./configure --help | tail -25)

例如可以使用CC去指定不同的编译器或者使用CFLAGS去改变编译flags:

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

在这个配置中将使用clong编译器(代替gcc)去编译,使用一个非常高度优化的级(-O3

-march=native)。

你也可以使用附加的编译器警告标志来帮助你发现一些错误,可以阅读GCC手册

make and make install

所有都配置好以后,可以使用make去执行真正的编译

~/php-src> make -jN    # where N is the number of cores

这个操作主要是生成启用了SAPIs(sapi/cli/php和sapi/cgi/php-cgi)的php二进制程序和在modules/目录生成共享扩展.

现在可以执行make install 命令去安装php到/usr/local目录(默认)或者你使用--prefix配置选项指定的其他任何目录

make install仅仅是拷贝一些文件到新的位置。除非在configure期间指定了--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的二进制,phpize和php-config脚本,也是各个PEAR/PECL脚本的主目录

etc/ 包含一些配置 。注意默认php.ini文件不是在这个目录

include/php 包含一些编译php扩展或者自定义软件嵌入php的头文件

lib/php 包含PEAR文件。lib/php/build目录包含一些编译扩展必要的文件,比如acinclude.m4文件包含了一些php的M4宏。如果我们已经编译过任何扩展,这些文件将放在lib/php/extensions的子目录中。

php/man 显然包含一些php命令手册

之前提到的默认php.ini文件不是放在etc/目录。你可以使用--ini选项去显示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目录而不是在$PREFIX/etc目录。你能使用--with-config-file-path=PATH配置选项调正php.ini默认位置。

你可能注意到make install 并不会创建ini文件。如果你想要使用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)

bin/目录除了包含php二进制文件,还包含了两个重要的脚本:phpize和php-config。

phpize 对扩展说是和./buildconf脚本相等的。它将从lib/php/build目录拷贝几个文件,然后执行autoconf和autoheader。下一章节你将学到多关于phpize

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相似的。在扩展编译期间被调用,去获取一些关于编译器选项和路径的信息。你也能使用它去快速的获取你的编译信息,比如你的configure选项或者默认扩展的目录。./php -i(phpinfo)也能提供这些信息,但是php-config能以更简单的方式提供(很容易的被automated tools使用)。

运行测试套件

如果make命令成功执行了,它会打印一条消息,鼓励你去执行make test:

Build complete.
Don't forget to run 'make test'

make test将运行php cli二进制在我们的测试套件上,这些测试套件被放置在PHP源码目录的不同tests/目录。默认编译会花费几分钟运行大概9000个测试(这是最少的编译,如果你启用了更多的扩展将会更多)。make test命令当前还不是并行的,所以指定-jN选项不会运行的更快。

如果你是在你的平台上第一次编译php,我们建议你去运行测试套件。依赖于你的系统和编译环境,在运行测试期间可能发一些php的bug。如果有任何失败,脚本将会提示你是否想要去发送错误给我们的QA平台,让贡献者去处理优化这些错误。注意遇到一些测试失败是很正常的,不影响工作,只要不出现几十个错误。

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~/php-src> sapi/cli/php run-tests.php -P Zend/ ext/reflection/ ext/standard/tests/array/

-p用来明确执行要测试的二进制程序。注意:为了正确运行所有的测试应该使用一个绝对路径(或者其他所调用的独立目录)。-P是使用run-tests.php调用二进制文件的快捷方式。在上述示例中,两种方法都是相同的。

你可以通过传递参数给run-tests.php来限制测试目录,而不是运行整个测试套件。比如仅测试Zend engine,反射扩展和数组函数:

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

这是非常有用的,因为它允许你仅测试相关修改的部分。比如你正在修改语言部分,不可能关心扩展的测试仅想要验证Zend engine引擎是否正常的工作。

你可以不用明确的传递参数给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在它运行第一个测试之前崩溃),应该尝试运行make clean命令,它将会删除所有编译的对象,强制下次执行make的时候是完全全量编译。

有时候你也需要去运行make clean在改变了./configure选项之后,如果你仅仅是启用单独的扩展,增量编译将不会有什么问题,但是改变了其他选项必须进行重新全量编译。

通过make distclean会更深入地清理目标对象。这将执行正常清理,但也会回滚./configure命令调用生成的任何文件。它会删除配置缓存,MakeFiles,配置头文件和各种其他文件。顾名思义,它的目标是“发布清理”,所以它主要是由发布的管理员使用。

另一个源码编译的问题是修改了config.m4文件或者其他php编译系统的一些文件。如果有一个文件改变了,必须去重新运行./buildconf脚本.如果是你自己修改的,你可能记得去运行这个命令,但是如果是通过git pull

而导致修改了文件(或者其他一些更新命令)这个问题将不会很明显。

如果你遇到了任何通过make clean不能解决的编译问题,有可能运行./buildconf --force会解决问题。你可以使用./config.nice脚本(包含上一次./configure脚本的调用),以避免之后输出以前的./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忽略的所有未跟踪的文件和目录。你应该谨慎使用。

results matching ""

    No results matching ""