php-src をフォークして PHP をソースからビルドする



概要
---

PHP のビルド方法や git のコマンド、PHP のソースコードを学ぶために取り組んだことを振り返るために書きました。github の php-src にパッチを提供したり、改善案を示すとき、リポジトリを維持するために、時間が経過するとともにフォークした php-src の更新を取り込む必要があります。


バグの報告や機能リクエストを登録する
------------------------------

[このページ](https://bugs.php.net/report.php)から登録します。ページを登録した後 gihub で公開しているプルリクエストを追加することができます。

PHP のビルドツールを導入する
------------------------

後述の ./configure の段階で不足するツールが表示されるので、その都度導入してゆけばよいでしょう。Ubuntu の場合、一連のビルドツールを導入するためのコマンドがあります。

```bash
sudo apt-get build-dep php7
```

PHP をビルドする
---------------

私の場合、github のアカウントの名前が masakielastic なので、クローンするリポジトリは次のようになります。`./configure` オプションに `--enable-debug --enable-maintainer-zt` を指定してビルドすれば、スクリプトを実行したときにメモリリークが検出されます。システムにインストールしたくないので、インストール先をソースコードのディレクトリ内とします。

```bash
git clone git@github.com:masakielastic/php-src.git
cd php-src
./buildconf
./configure --enable-debug --enable-maintainer-zts --prefix=`pwd`/local
```

ビルドしましょう。

```bash
make
```

インストールしなくても次のパスからコマンドツールを使うことができます。

```bash
sapi/cli/php -v
```

インストールする場合、次のコマンドを実行します。
```
make install
```

インストール先のコマンドツールを確かめてみましょう。

```bash
local/bin/php -v
```

使い捨てのブランチを扱う
---------------------

### 最新の履歴だけをもつリポジトリをつくる

`--depth` オプションを指定して shallow clone をつくります。

```bash
git clone https://github.com/php/php-src.git --branch master --depth=1 
```

### shallow clone を更新する

shallow リポジトリでは `git fetch` は `.git/shallow` の更新を要求する refs を拒否するので、次のオプションを指定する
必要があります。

```bash
git pull --update-shallow
```

### shallow clone を full clone に変換する

shallow clone では `git pull` を実行しても自動的にマージされません。更新するために shallow clone を full clone に変換します。

```bash
git pull --unshallow
```

### 単独のブランチを clone する

shallow clone の代わりに単独のブランチを clone することでダウンロードの時間を節約することができます。`--depth` オプションを指定した場合、暗黙のうちに `--singple-branch` オプションが指定されます。

```bash
git clone https://github.com/php/php-src.git --branch master --single-branch
```

リポジトリの管理
--------------

### 上流のブランチを登録する

登録されているブランチの一覧を表示します。

```bash
git remote -v
```

上流のブランチである `php/php-src` を登録しましょう。

```bash
git remote add upstream git@github.com:php/php-src.git
```

登録されたか再度確認しましょう。


### ブランチの確認と切り換え

作業中のブランチを確認しましょう。

```bash
git branch
```

作業したいブランチに切り換えてみましょう。

```bash
git checkout master
```

### ローカルブランチの作成と削除

ローカルブランチを新しくつくってみましょう。

```bash
git branch hello_world
```

ブランチ作成と作業ブランチへの移動を同時に実行することもできます。

```bash
git checkout -b hello_world
```

ブランチの削除には次のコマンドを実行します。ただし、作業中のブランチは削除できませんので、別のブランチに切り換える必要があります。

```bash
git branch -d hello_world
```

リモートブランチを削除するには `--delete` オプションを指定します。

```bash
git push origin --delete hello_world
```

### 上流の変更を追随する

上流の変更を取得します。

```bash
git fetch upstream
```

上流の変更をマージします。

```bash
git merge upstream/master
```

github のリモートブランチ (origin) に変更を反映させましょう。

```bash
git push origin master
```

自分が作ったブランチを更新するには rebase を使います。

```bash
git checkout my_branch
git rebase upstream/master
git push --force-with-lease origin my_branch
```

`--force` よりも `--force-with-lease` のほうが望ましい理由については[こちらの記事](http://japan.blogs.atlassian.com/2015/05/force-with-lease/)をご参照ください。

ビルドできないリビジョンに遭遇した場合、ビルドできるリビジョンに rebase します。

```bash
git rebase --onto <new-parent> <old-parent>
```

モジュールの開発
--------------

### 拡張モジュールをビルドする

mbstring をビルドしてみましょう。

```bash
cd ext/mbstring
../../local/bin/phpize
./configure
```

Makefile をテキストエディタで開いて、`prefix` や `EXTENSION_DIR` などを修正します。私の環境では `/Users/masakielastic/test/php-src/local`、`/Users/masakielastic/test/php-src/local/lib/php/extensions/no-debug-non-zts-20141001` のような値になりました。

修正作業が終わったらビルドしてテストを実行してみましょう。

```bash
make
make test
```

### コマンドラインから拡張モジュールを読み込む

ビルドしたモジュールは modules フォルダのなかにあります。コマンドラインでモジュールを読み込みには `-dextension` オプションでパスを指定します。

```php
php -dextension=modules/mbstring.so test.php
```


### テスト範囲を絞る

テストの範囲を絞りたいなら次のようにディレクトリを指定します。

```bash
make test TESTS=ext/standard/tests/strings
```

### 拡張モジュールのひながたを生成する

`ext/ext_skel` コマンドを実行します。

```bash
cd /path/to/ext
./ext_skel --extname=helloworld
cd helloworld
```

`config.m4` の次の3行のコメントを解除します。

```bash
PHP_ARG_WITH(helloworld, for helloworld support,
Make sure that the comment is aligned:
[  --with-helloworld             Include helloworld support])
```

後はおきまりのコマンドでビルドできます。前述のように Makefile の `prefix` や `EXTENSION_DIR` を修正する必要があります。

```bash
../../local/bin/phpize
./configure
make
```

関数の例として `confirm_helloworld_compiled` が登録されています。次のように実行してみましょう。

```php
var_dump(
    confirm_helloworld_compiled(str_repeat('1234567890', 100))
);
```

`confirm_helloworld_compiled` は次のように定義されています。引数の解析には `zend_parse_parameters` 関数、戻り値の指定には `RETURN_STRINGL` のようなマクロを使うということを理解すればじゅうぶんです。

```c:helloworld.c
PHP_FUNCTION(confirm_helloworld_compiled)
{
    char *arg = NULL;
    int arg_len, len;
    char *strg;
    
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
        return;
    }

    len = spprintf(&strg, 0, "Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "helloworld", arg);
    RETURN_STRINGL(strg, len, 0);
}
```

今度は `helloworld` という名前の関数を登録してみましょう。

```c:php_helloworld.h
PHP_FUNCTION(helloworld);
```

```c:helloworld.c
const zend_function_entry helloworld_functions[] = {
    PHP_FE(confirm_helloworld_compiled, NULL)           /* For testing, remove later. */
    PHP_FE(helloworld, NULL)
    PHP_FE_END  /* Must be the last line in helloworld_functions[] */
};

PHP_FUNCTION(helloworld)
{
    printf("Hello, World!\n");
    return;
}
```

### PHP のソースコードをどのように学べばよいのか？

`ext/ext_skel` コマンドで生成した拡張モジュールや標準の文字列や配列関数の定義を読むとよいでしょう。PHP 初心者のときを思い出して、概念をどのように学んできたのか振り返ることも必要です。

最初は意味がまったくわからなくても、気になる関数やクラスの定義を定期的に読むことを習慣にします。使い方のわからない内部関数は github で検索して用例を調べます。

標準関数の読み方について strlen などの nikic さんの[記事](http://nikic.github.io/2012/03/16/Understanding-PHPs-internal-function-definitions.html)があります。`strlen` の定義マクロには `ZEND_FUNCTION` が使われているので、知らないと見つけづらいです。

拡張モジュールであれば cURL が学びやすいです。包括的なドキュメントとして [PHP Internals Book](http://www.phpinternalsbook.com/) がありますが、最初に読んでも頭に入らないでしょう。

オライリーの Programming PHP の[古い版](g/php/ch14_01.htm)はオンラインで読むことができます。メモリ管理の関数、関数の引数 (`zend_parse_parameters`) や戻り値の選択肢を調べるのに重宝しています。ほかに紙の書籍を求めるのであれば「Extending and Embedding PHP」(2006) という本があります。

PHP 7.0 への対応は別に勉強する必要があります。[Fast Parameter Parsing API](https://wiki.php.net/rfc/fast_zpp) が導入され、`zend_parse_parameters` からの置き換えが進められています。また PHP 5 から PHP 7 への内部のアップグレードのための[まとめ記事](https://wiki.php.net/phpng-upgrading)があります。

文字列に関して、zend_string API が導入されました。また smart_str API の置き換えを図る smart_string API も導入されました。