ソースコード(php-src)からPHPをビルドする流れと仕組みを手を動かしながら理解する
PHP8がリリースされました。
さまざまな言語仕様が追加される中、やはりそれを触る最速の方法は手元でビルドして確かめることでしょう。本記事では、手元のPC[1]で、PHPのソースコード(php-src)からビルドし、テスト・CLIとして実行するまで、実際に手を動かせる形で順序立てて解説しています[2]。
ただの手順書とするだけではあまり面白くないので、都度「ここではなにが行われているのか」について解説を含めていきます。具体的には、PHPコードの実行プロセスのために必要なライブラリや、CやC++の開発文脈で当たり前のように出てくるconfigure
などの基礎知識・CLI実行時の処理構造(SAPIなど)について触れることで、PHPのソースコードの前提知識をインプットできる時間とします。
そしてこちらは、PHP Advent Calendar 2020の5日目の記事となります。
手順
途中途中で説明を挟むため、まず全体像としてどういう手順を踏むかについて抑えておきます。
- PHPソースコードをcloneする
- 依存ソフトウェアを事前インストール
- configureスクリプトを生成・実行
- makeでビルドする
- テストを実行する
- ビルドしたものをinstallする
- CLIでバージョンを表示して動作確認
1. PHPソースコードをcloneする
PHPのソースコードはこちらです。cloneするとmaster
ブランチを指したソースコードを手に入れることができます。
2. 依存ソフトウェアを事前インストール
PHPのビルドに必要なものを事前にインストールします。具体的には、次のページにコードからビルドしたい場合に必要なものが記載されています。
今回の動作環境が注記したとおりMacOSであるため、UNIXのページを参照していますがお使いの環境に応じて参照するべきページは異なります。たとえば、Windows PCの場合は、Windows システムへのインストールを参照いただくことになります。
以降、UNIXを前提としますが、UNIXの場合は次のものが必要になる点が記載されています。
- autoconf
- automake
- libtool
- re2c
- flex
- bison
ここは環境に依存する可能性がありますが、自分が手元のMacbook Proでビルドする際に実際にHomebrewからインストールしたものは以下です。
brew install autoconf
brew install re2c
brew install bison
brew install oniguruma
brew install libiconv
これらが具体的にどういう役割・経緯で必要なのかをそれぞれ記します。
autoconf
PHPのソースコードを実行ファイルにビルドする過程で、configure
スクリプトを生成する過程があります。その過程において、buildconf
という実行スクリプトによって生成するのですが、このbuildconf
はAutoconfのラッパーとなっています。
A wrapper around Autoconf that generates files to build PHP on *nix systems.
Autoconfの説明は以下の公式ページのとおりですが、まさにconfigureスクリプトの自動生成ツールとなっています。
Autoconf is an extensible package of M4 macros that produce shell scripts to automatically configure software source code packages.
configureは次の記事にもありますが、手動で作るのではなくGNU Autotoolsと呼ばれるパッケージによって自動生成します。
re2c
Its main goal is generating fast lexers: at least as fast as their reasonably optimized hand-coded counterparts.
lexer とは字句解析のことを指します。日本語では知っていますか? あなたの書いたPHPのコードが実行される4つのプロセスあたりでPHPコードの実行プロセスを4つに分解して説明しています。
- 字句解析(Lexing)
- 構文解析(Parsing)
- コンパイル(Compilation)
- 実行(Interpretation)
PHPはre2cを使用して、zend_language_scanner.l定義ファイルからレキサー(字句解析器)を生成します。
そのため必要になります。
bison
こちらは、字句解析後の構文解析に用いられるものです。PHPはBisonを用いています。
Bison is a general-purpose parser generator that converts an annotated context-free grammar into a deterministic LR or generalized LR (GLR) parser employing LALR(1) parser tables.
$ brew install bison
==> Downloading https://homebrew.bintray.com/bottles/bison-3.7.4.catalina.bottle
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/6252edf4d591cf1de3e94
######################################################################## 100.0%
==> Pouring bison-3.7.4.catalina.bottle.tar.gz
==> Caveats
bison is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
If you need to have bison first in your PATH run:
echo 'export PATH="/usr/local/opt/bison/bin:$PATH"' >> ~/.zshrc
For compilers to find bison you may need to set:
export LDFLAGS="-L/usr/local/opt/bison/lib"
==> Summary
🍺 /usr/local/Cellar/bison/3.7.4: 94 files, 3.3MB
また、「OSXで最新のPHPをビルドする方法」にて説明がありましたが、後ほどbisonのパスを用いるためダウンロード先を確認・メモしておきます(/usr/local/Cellar/bison/3.7.4
)。
oniguruma
これは後のconfigure
にてconfigure: error: Package requirements (oniguruma) were not met:
というエラーが発生したためインストールしています。
dockerでのビルドでは様々なケースが報告されていて対応策が明示されていました。
Mac OSの場合は、MacOS に anyenv + phpenv で PHP 7.4.1 をインストールするにて、Homebrewからinstallしています。
libiconv
configure: error: Please specify the install prefix of iconv with --with-iconv=<DIR>
というエラーが後のconfigureで発生したためインストールしています。
でのコメント内にて紹介されている、Homebrew で libiconv をインストールする方式を取りました。
3. configureスクリプトを生成・実行
必要なものがインストールできたらコマンドを打っていきます。
まずはbuildconfスクリプト(前述したとおりAutoconfのラッパースプリプトです)を実行することでAutoconfを通じてconfigureスクリプトを生成します。
$ ./buildconf
buildconf: Checking installation
buildconf: autoconf version 2.69 (ok)
buildconf: Cleaning cache and configure files
buildconf: Rebuilding configure
buildconf: Rebuilding main/php_config.h.in
buildconf: Run ./configure to proceed with customizing the PHP build.
Run ./configure to proceed with customizing the PHP build.
というメッセージの通り、生成したconfigureスクリプトを実行(./configure
)していきます。なお、ここでbisonのパス指定が必要なので、環境変数YACC
にはbisonのパスを指定します。
また、onigurumaのpkgconfigへのパスを環境変数に設定します(これは後に説明するエラーを回避するためです)。
$ YACC="/usr/local/Cellar/bison/3.7.4/bin/bison" \
PKG_CONFIG_PATH="/usr/local/opt/oniguruma/lib/pkgconfig" \
./configure --prefix="/opt/php-master" \
--enable-cli \
--enable-mbstring \
--enable-debug \
--with-iconv=$(brew --prefix libiconv)
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for a sed that does not truncate output... /usr/bin/sed
checking build system type... x86_64-apple-darwin19.6.0
checking host system type... x86_64-apple-darwin19.6.0
checking target system type... x86_64-apple-darwin19.6.0
checking for pkg-config... /usr/local/bin/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for cc... cc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether cc accepts -g... yes
checking for cc option to accept ISO C89... none needed
...(省略)
+--------------------------------------------------------------------+
| License: |
| This software is subject to the PHP License, available in this |
| distribution in the file LICENSE. By continuing this installation |
| process, you are bound by the terms of this license agreement. |
| If you do not agree with the terms of this license, you must abort |
| the installation process at this point. |
+--------------------------------------------------------------------+
Thank you for using PHP.
configureに対するオプションは、エラーに対するトラブルシューティングの結果次のコマンドになっています。どのようなエラーが発生しうるかについて以下に列挙いたします。
trouble shooting: Please specify the install prefix of iconv
次のようなエラーが発生する場合があります。
Please specify the install prefix of iconv
こちらのエラーについてはすでに前述していますが、必要なものをインストールしてオプションで指定する必要があります。
configure: error: Please specify the install prefix of iconv with --with-iconv=<DIR>
また、上の記事で --enable-debug
というオプションがビルドデバックに役立つという情報があります。デバックで詰まった場合は--enable-debug
を有効にして情報量を増やしてみてください。
対応策としては、
--without-iconv
-
brew install libiconv
して、--with-iconv=$(brew --prefix libiconv)
を設定
があり、2を選択しています。
trouble shooting: No package 'oniguruma' found
これも前述していますが、onigurumaが足りないというエラーです。
configure: error: Package requirements (oniguruma) were not met:
No package 'oniguruma' found
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
Alternatively, you may set the environment variables ONIG_CFLAGS
and ONIG_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.
そのため、brew install oniguruma
にてインストールし、PKG_CONFIG_PATHに追加しました。
4. makeでビルドする
configureスクリプトを実行し各種チェック成功後、Makefile
等が生成されます。続けて make
の実行に続きます。
$ make
/bin/sh /Users/kazukihigashiguchi/src/github.com/php/php-src/libtool --silent --preserve-dup-deps --mode=install cp ext/opcache/opcache.la /Users/kazukihigashiguchi/src/github.com/php/php-src/modules
...(省略)
Build complete.
Don't forget to run 'make test'.
Build complete.
と出れば成功です。
Makefileの仕様は最初のターゲットを対象とするのが仕様です[3][4]。
make starts with the first target (not targets whose names start with ‘.’)
生成されたMakefileの最初のターゲットはall
なため、次のようなスクリプトが実行されます。
all: $(all_targets)
@echo
@echo "Build complete."
@echo "Don't forget to run 'make test'."
@echo
$(all_targets)
が指す先で、各モジュールのビルドが定義されています。
5. テストを実行する
$ make test
これでテストスクリプト phpt が実行されます。
次のようなメッセージが出ればテスト終了です。
=====================================================================
TIME END 2020-11-21 22:49:38
=====================================================================
TEST RESULT SUMMARY
---------------------------------------------------------------------
Exts skipped : 45
Exts tested : 27
---------------------------------------------------------------------
Number of tests : 16033 11240
Tests skipped : 4793 ( 29.9%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 4 ( 0.0%) ( 0.0%)
Expected fail : 31 ( 0.2%) ( 0.3%)
Tests passed : 11205 ( 69.9%) ( 99.7%)
---------------------------------------------------------------------
Time taken : 678 seconds
=====================================================================
今回は開発時点最新のブランチを利用している&&とくに実運用のサーバーに突っ込むことを前提としていないのでTests failed
が4つ(0.0%)あることはスルーしてインストールへ進みます。
なお、テスト失敗したときにメーリングメストに連絡するフローがあります。必要であればレポーティングしましょう。
You may have found a problem in PHP.
This report can be automatically sent to the PHP QA team at
http://qa.php.net/reports and http://news.php.net/php.qa.reports
This gives us a better understanding of PHP's behavior.
If you don't want to send the report immediately you can choose
option "s" to save it. You can then email it to qa-reports@lists.php.net later.
Do you want to send this report now? [Yns]:
PHP内部コードのテスト実行構造
さて、make test
した際の挙動ですが簡略化した図にするとこのような実行フローとなっています。
make test
時の挙動ですがまずPHPが実行可能かどうかを検証しています。ここでいうとPHPが実行可能かどうかは、CLI SAPIがあるかを確かめています。後ほど詳述しますが、SAPIとはPHPと「外」の間を取り持つメカニズムです。CLI SAPIはその中の一つで、「CLIという外」とPHPの間を取り持っています。
PHP_EXECUTABLE = $(top_builddir)/$(SAPI_CLI_PATH)
# (省略)
test: all
@if test ! -z "$(PHP_EXECUTABLE)" && -x "$(PHP_EXECUTABLE)"; then \
# (省略)テスト実行
else \
echo "ERROR: Cannot run tests without CLI sapi."; \
fi
CLI SAPI有効でない場合はその時点でテスト実行は不可能として終了しています。
CLI SAPIが有効でありテストが実行できる場合if
の中でCLI SAPIを通じてrun-tests.php
というPHPコードを実行します。
Makefileでは次のようなコードがそれに該当します。
TEST_PHP_EXECUTABLE=$(PHP_EXECUTABLE) \
TEST_PHP_SRCDIR=$(top_srcdir) \
CC="$(CC)" \
$(PHP_EXECUTABLE) -n -c $(top_builddir)/tmp-php.ini $(PHP_TEST_SETTINGS) $(top_srcdir)/run-tests.php -n -c $(top_builddir)/tmp-php.ini -d extension_dir=$(top_builddir)/modules/ $(PHP_TEST_SHARED_EXTENSIONS) $(TESTS); \
様々な環境変数を設定して長くなっていますが、超絶簡略化するとこのコードは次のようなコマンドを実行しているのと同義です。
$ ./sapi/cli/php ./run-tests.php
CLI SAPIの実行ファイルである./sapi/cli/php
を用いてrun-tests.php
を実行しています。Makefileで発行されるコマンドはこれに対して様々なオプションを付けています。
$ ./sapi/cli/php -n \
-c ./tmp-php.ini \
-d open_basedir= -d output_buffering=0 \
-d memory_limit=-1 \
./run-tests.php -n -c ./tmp-php.ini \
-d extension_dir=./modules/ \
-d zend_extension=./modules/opcache.so
(※ 正確には./sapi/cli/php
などの箇所は絶対パスですが、記事内での視認性のため相対パスにしています。)
そして、run-tests.php
は「*.phpt
ファイルをあつめ」、「phptファイル形式を解釈してテスト対象の実行」、「検証」・「結果をレポート」しています。
if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) {
if (substr($argv[$i], -5) == '.phpt') {
$pattern_match = glob($argv[$i]);
} else {
if (preg_match("/\*$/", $argv[$i])) {
$pattern_match = glob($argv[$i] . '.phpt');
} else {
die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL);
}
}
*.phpt
ファイルはこのような形式のテストスクリプトです。
--TEST--
strtr() function - basic test for strtr()
--FILE--
<?php
/* Do not change this test it is a README.TESTING example. */
$trans = array("hello"=>"hi", "hi"=>"hello", "a"=>"A", "world"=>"planet");
var_dump(strtr("# hi all, I said hello world! #", $trans));
?>
--EXPECT--
string(32) "# hello All, I sAid hi planet! #"
このファイルをPHPで解釈しファイルを実行・var_dump()
等で出力した内容を期待値(EXPECT)と突き合わせます。
// compare and leave on success
if (!strcmp($output, $wanted)) {
$passed = true;
そして、最後この結果はサマリーとして出力されます。
$summary .= '
Tests passed : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
---------------------------------------------------------------------
Time taken : ' . sprintf('%4d seconds', $end_time - $start_time) . '
=====================================================================
';
このような設計・実装によって、PHPの内部コードはテストされています。
詳述: CLI SAPIとはなにか
さきほど、ERROR: Cannot run tests without CLI sapi.
とある通り、CLI SAPIがないとテストは実行できません。そもそも、CLI SAPIとはなにかを説明します。
PHPユーザーに身近な場所ではここに出ています。
$ /opt/php-master/bin/php -v
PHP 8.1.0-dev (**cli**) (built: Nov 22 2020 07:37:38) ( NTS DEBUG )
Copyright (c) The PHP Group
Zend Engine v4.1.0-dev, Copyright (c) Zend Technologies
「バージョンを確認するぞ」とコマンドを打ったときに表示されてるcli
を指します。これは echo php_sapi_name()
で出てくる値と同一です。
意外と身近なところで出ているこちらの概念ですが、そもそもSAPIとはServer Application Program Interface
の略です。
PHPにおいては、PHP/Zend Engineと「外」の相互作用を制御するメカニズムです。SAPIには、apache
やcgi
などさまざまなSAPIのバリエーションがあります。具体的には次のリストです。
- apache
- apache2handler
- cgi (until PHP 5.3)
- cgi-fcgi
- cli
- cli-server
- embed
- fpm-fcgi
- litespeed
- nsapi
- phpdbg
PHPではCLI
も提供しており、それがCLI SAPIです。さらに細かい点ではCLI SAPIは他のSAPIとはいくつか相違点があることを公式ドキュメントでは示しています。
- CGI SAPIと異なりヘッダに出力を書き込まない点
- CGI SAPIにはHTTPヘッダを抑制する方法を提供しているが、CGI SAPIにはその有効スイッチがない。
- 出力はHTMLフォーマットではなくプレインテキストである。
-
html_errors
やmax_execution_time
などWebベースのスクリプト用途の php.ini のディレクティブはCLI SAPIによって上書きされる
長々と来たが、ゆえにconfigureで作成されるMAKEFILE内のSAPI_CLI_PATH定義にはと、デフォルト例として示したsapi/cli/php
が設定されている。
さきほどconfigureスクリプトを実行する際に--enable-cli
オプションをつけましたが、その設定によってCLI SAPIが有効化されます[5]。
./configure --prefix="/opt/php-master" \
--enable-cli \
--enable-mbstring \
--enable-debug \
--with-iconv=$(brew --prefix libiconv)
The CLI SAPI is enabled by default using --enable-cli, but may be disabled using the --disable-cli option when running ./configure.
6. ビルドしたものをinstallする
ビルドが完了したら最後インストールします。--prefix="/opt/php-master"
にて指定した際にインストールされます。
$ sudo make install
Installing shared extensions: /opt/php-master/lib/php/extensions/debug-non-zts-20201009/
Installing PHP CLI binary: /opt/php-master/bin/
Installing PHP CLI man page: /opt/php-master/php/man/man1/
Installing phpdbg binary: /opt/php-master/bin/
Installing phpdbg man page: /opt/php-master/php/man/man1/
Installing PHP CGI binary: /opt/php-master/bin/
Installing PHP CGI man page: /opt/php-master/php/man/man1/
Installing build environment: /opt/php-master/lib/php/build/
Installing header files: /opt/php-master/include/php/
Installing helper programs: /opt/php-master/bin/
program: phpize
program: php-config
Installing man pages: /opt/php-master/php/man/man1/
page: phpize.1
page: php-config.1
/Users/kazukihigashiguchi/src/github.com/php/php-src/build/shtool install -c ext/phar/phar.phar /opt/php-master/bin/phar.phar
ln -s -f phar.phar /opt/php-master/bin/phar
Installing PDO headers: /opt/php-master/include/php/ext/pdo/
7. CLIでバージョンを表示して動作確認
ビルド先でPHPのバージョンが表示されれば完了です。
% /opt/php-master/bin/php -v
PHP 8.1.0-dev (cli) (built: Nov 22 2020 07:37:38) ( NTS DEBUG )
Copyright (c) The PHP Group
Zend Engine v4.1.0-dev, Copyright (c) Zend Technologies
おわりに
当記事では、実際にPHPソースコード php-src をクローンしてきてからビルドするまでの流れとその過程で現れる概念について解説いたしました。
PHP8をきっかけに「PHPの内部コードをちょっと覗いてみよう」といった好奇心や「自分でビルドしてみよう」といったお試し心ができた際に参考になれば幸いです。
Discussion