PHP4をAlpineLinuxでビルドする話

# はじめに

この記事は[PHP Advent Calendar 2018](https://qiita.com/advent-calendar/2018/php)の24日目の記事です。

[西日本応援プロジェクト真夏の大LT大会](https://live.line.me/channels/253884/broadcast/8924403)で徳丸さんが古くはphp-3.0.18から最新のバージョンまでmacOS上に環境構築しているという話をしていたので、同じようなことをAlpineLinuxでやったらどうなるのか、という記録がこの記事です。
あらかじめ書いておくと、徳丸さんの発表とちがって、この記事は基本的に無益です。

# 前提

- docker for Mac を利用
- AlpineLinux 3.8
- AlpineLinuxのパッケージングシステム(abuild/apk)を利用する
- Apache1.3用のmod_php版とCLI版（php-4.2.x以前はCGI版）をビルド

初めてabuildを使いましたが、RPMより書きやすい気がしますね。

# 成果物

作ったものは、http://github.com/odz/a-php にあります。


```dockerfile:Dockerfile
FROM alpine:3.8

ARG PHP_VERSION

ADD https://odz.github.com/a-php/oda.org@gmail.com.rsa.pub /etc/apk/keys
RUN echo 'https://odz.github.com/a-php/php' >> /etc/apk/repositories && \
        apk add \
                php-apache1.3~${PHP_VERSION} \
                php-pear~${PHP_VERSION}

CMD ["/usr/bin/apache", "-F"]
```

このようなDockerfileを作成して、

```shell
docker build --build-arg PHP_VERSION=4.0.0 -t php:4.0.0 .
```

などとすれば、Apache 1.3、PHP 4.0.0のDocker Imageができます。
Document Rootは `/var/www/localhost/htdocs`です。

# Apache 1.3

mod_php版をビルドするためにはまず、Apache 1.3が必要だが、さすがにいまどきの環境ににApache 1.3のパッケージはないので、自力でビルドする。が、Apache 1.3もそれなりにパッチをあてないとビルドできない。

## [getline.patch](https://github.com/odz/a-php/blob/master/php/apache1.3/getline.patch)

getlineという関数が標準ライブラリのものと名前が衝突しているので、名前を変更しないと行けない。

## [libc-decl.patch](https://github.com/odz/a-php/blob/master/php/apache1.3/libc-decl.patch)

glibcには `sys_sliglist` というシンボルが定義されているのだが、Alpine Linuxで利用している musl libc にはそんなものはないのと、判定をミスっていて
glibc 2.0以前向けに`rlim_t`を`typedef`しようとしているのでこれを削除。

## [busybox_sed.patch](https://github.com/odz/a-php/blob/master/php/apache1.3/busybox_sed.patch)

busybox の sed が GNU sed と違って `\+` を解釈できないので、修正。よくわかっていないけど、`\+`　は GNU sed の拡張ということで良いよね？

## [no-setsid.patch](https://github.com/odz/a-php/blob/master/php/apache1.3/no-setsid.patch)

これはあてなくてもビルド自体はできる。

Apache 1.3 は foreground で動かす場合(-Fオプション指定)もsetsid(2) をコールし、エラーの場合はプロセス終了してしまうのである。説明はめんどいので飛ばすが、Docker で Apache を動かす場合に、この仕様は大変つらい感じなので、setsid(2) をコールしないように修正。

## -fgnu89-inline

gcc の古いバージョンでは `extern inline` を指定された関数はインライン展開されたあと、関数定義自体は残らないらしい。いまのデフォルト動作はそうではないのだが、そのような動作にするためには`-fgnu89-inline`オプションを指定すれば良いらしい。これを指定しないとmultiple definitionでリンク時にエラーになる。

Apache 1.3 でいうと以下のコードですね。

```c:os.h
#define INLINE extern ap_inline

INLINE int ap_os_is_path_absolute(const char *file);

#include "os-inline.c"
```

```c:os-inline.c
INLINE int ap_os_is_path_absolute(const char *file)
{
  return file[0] == '/';
}
```

# PHP 4.0.0

- https://github.com/odz/a-php/tree/master/php/php-4.0.0

## number4.tar.gz

bcmath を有効にしようとすると

```
You do not have the bcmath package. Please read the README.BCMATH file.
```

といわれそのとおりに README.BCMATH を読むとライセンスの問題でBC Math ライブラリは PHP とは別配布になったので、http://www.php.net/extra/number4.tar.gz からダウンロードしてくるべし、みたいなことが書いてあるわけです。が、そのURLにアクセスしてもダウンロードできない。

結構苦労して探してどこかからダウンロードしてきたのだけど、どこだったかはよく覚えていない。

ちなみに、ライセンス問題が解決されたのか PHP 4.0.4 からは同梱されるようになっている。

## [extern-static.patch](https://github.com/odz/a-php/blob/master/php/php-4.0.0/extern-static.patch)

古い gcc では同一変数名で`extern`付きの宣言と`static`付きの宣言が重複していても
問題なかったらしい。GCC 4 以降はそのようなコードはエラーになるようになっているので、
変数名を変更して対応。`ap_php_opterr` と `ap_php_optind` に関してはそもそも`extern`宣言の方が不要なので削除。

ちなみに、PHP 4.0.5 で問題は修正され、このパッチは不要になる。

## [mysql-obsolete.patch](https://github.com/odz/a-php/blob/master/php/php-4.0.0/mysql-obsolete.patch)

PHP 4.0.0 は MySQL 3.x 時代に存在していた`mysql_createdb`と`mysql_dropdb`を参照しているため、リンクまでうまく行っても実行時にシンボルが足りないため、実行時にエラーになる。古い MySQL を持ってくるのでも良いのだけど、めんどいのでPHP側を修正して対応。

なお、これも PHP 4.1.0 で同様の対応が取られるのでパッチ不要になる。

## [mariadb-port.patch](https://github.com/odz/a-php/blob/master/php/php-4.0.0/mariadb-port.patch)

Alpine Linux には MySQL ではなく、MariaDB が提供されているので、
この記事でのPHPのビルドにも MariaDB を利用しているんだが、
MariaDB の Client Library は関数名、ヘッダ名もオリジナルと合わせているし、
ライブラリの名前も libmariadb.so だけでなく、libmysqlclient.so も提供されているくせに
`MYSQL_PORT`がdefineされないという状態である。おしいな。

ということで、`#define MYSQL_PORT MARIADB_PORT`としておく。

## [zend_optimization.patch](https://github.com/odz/a-php/blob/master/php/php-4.0.0/zend_optimization.patch)

`sed s/-O[0-9]*//` として最適化オプションを削除したコンパイルオプションを生成しようとしているのだが、Alpine Linuxのデフォルト状態だと`-Os`オプションが指定されているので、
この状態だと`s`だけ残って悲しいことになる。ということで、`sed s/-O[s0-9]*//`に修正して対応。

これも PHP 4.0.5 で同様の対応が取られる。

## [zlib.patch](https://github.com/odz/a-php/blob/master/php/php-4.0.0/zlib.patch)

zlib には

```c
gzFile gzopen(const char *, const char *);
int    gzclose(gzFile file);
int    gzgetc(gzFile file);
```

といった関数群があって、PHP はこれらへのbindingが提供されているのだが、PHP 4.0.0の時期のコード自体は全般的に `gzopen` の戻り値や `gzgetc` へのパラメータの型を`gzFile`ではなく、`gzFile *`と勘違いしている模様である。

zlib 1.2.5 までは `typedef void *gzFile`なので問題ないが、zlib 1.2.6 以降は `typedef struct gzFile_s *gzFile`であり、`gzgetc`はこの構造体のメンバにアクセスするマクロになっているので、
この辺の兼ね合いで zlib 1.2.6 以降だとコンパイルエラーになるという。

`gzgetc`だけ care しておけばとりあえずビルドはできる。テストはしてない。

この問題は PHP 4.3.0 で `gzgetc` 他の実装が修正されパッチ不要になる。

## その他

いろいろ試していて気づいたのだが、PHP 4.0.0 は `php.ini` の `extension_dir`ディレクティブの設定が効かないというバグがあるようだ。このため、`extension`ディレクティブはフルパスで指定する必要がある。
なぜ、そんなバグが放置したままリリースされたのか・・・

# 時間切れ

ここまで書いて時間切れ。
いろいろあるので、あとで追記する。