macOS 10.15 Catalina 上で phpenv を使って PHP 5.6 を build するのにものすごく苦労した話

## はじめに

この記事は [GMOペパボ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/pepabo) 19日目の記事です。

新しく入ったメンバーが macOS 10.15 Catalina 上で PHP 5.6 を build しようとしてハマっていたのを、いろいろ試行錯誤してなんとか解決した(というか回避した)ので、その話を書こうと思います。

## Mojave + XCode 10 から /usr/include がつくられなくなった

この話は、macOS 10.15 Catalina から始まった話ではなく、macOS 10.14 Mojave の仕様変更がそもそもの発端になっています。

どのような仕様変更があったかというと、Mojave + XCode 10 から `/usr/include` が作られなくなりました。 (`/usr/lib` は今まで通りあります)

じゃあ、ヘッダーファイルはどこにあるんだ？ということですが、次の場所にインストールされています。

- /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include

`xcrun --show-sdk-path` というコマンドを実行すると、SDKのパスが返却されます。

Mojave で実行すると `/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk` が返却されますが、これは `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk` へのシンボリックリンクになっています。

Mojave + XCode 10 では、無理やり `/usr/include` を作るための回避策が準備されており、以下のコマンドを実行すると作ることができました。

```sh
sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target / -allowUntrusted
```

この回避策は、Catalina + XCode 11 では使えなくなっており、Catalina では `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include` といい感じに付き合っていく必要があります。

## そして本題の PHP 5.6 を phpenv でいれる話

Catalina でも PHP 5.6 を使えるようにしようということで、いつもどおり次のコマンドを実行してもらいました。

```sh
phpenv -i development 5.6.40
```

その結果、よく見る Configure エラーが出たので、はいはいって感じで対処していきました。(このあたりはまだ平和)

(注意) ご存知の方はご存知だと思いますが、次のものがまとめて出るのではなく、アレがないといわれえた結果アレを入れると次はコレというように順々に出ていく感じで一つ一つ潰していく感じです

```
configure: WARNING: This bison version is not supported for regeneration of the Zend/PHP parsers (found: 2.3, min: 204, excluded: 3.0).
configure: WARNING: You will need re2c 0.13.4 or later if you want to regenerate PHP parsers.
configure: error: Cannot find OpenSSL's <evp.h>
configure: error: Cannot find libz
configure: error: Cannot find libzconfigure: error: Please reinstall the BZip2 distribution
configure: error: Please reinstall the libcurl distribution -
    easy.h should be in <curl-dir>/include/curl/
configure: error: Please specify the install prefix of iconv with --with-iconv=<DIR>
configure: error: Please reinstall libedit - I cannot find readline.h
configure: error: mcrypt.h not found. Please reinstall libmcrypt.
```

上記のエラーに対応するために、必要なものを homebrew を使ってインストール。

```sh
brew install bison@2.7 re2c zlib bzip2 curl libiconv libedit mcrypt
```

あわせて、 PHP 5.6 では OpenSSL 1.0 が必要なので、つい最近書いた次の記事の通り、OpenSSL 1.0.2t をインストールした。

- [homebrew で openssl 1.0 がインストールできなくなっています](https://qiita.com/kunit/items/0aef44d5e522abde1f9c)

```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/64555220bfbf4a25598523c2e4d3a232560eaad7/Formula/openssl.rb
```

ここまでの下準備をしつつ、次のコマンドでインストールが完了すると思ってた時期がありました。(実際、Mojaveだとこれでちゃんとインストールできます)

```sh
YACC=$(brew --prefix bison@2.7)/bin/bison \
PHP_BUILD_CONFIGURE_OPTS="--with-openssl=/usr/local/Cellar/openssl/1.0.2t --with-zlib-dir=/usr/local/opt/zlib --with-bz2=/usr/local/opt/bzip2 --with-curl=/usr/local/opt/curl --with-libedit=/usr/local/opt/libedit --with-iconv=/usr/local/opt/libiconv --with-mcrypt=/usr/local/opt/mcrypt --with-tidy" \
PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 \
phpenv install -i development 5.6.40
```

上記のコマンドを実行した結果、出たのが次のエラーになります。(config.log の関連部分を抜粋)

```
Undefined symbols for architecture x86_64:
  "_libiconv", referenced from:
      _do_convert in gdkanji.o
      _zif_iconv_substr in iconv.o
      _zif_iconv_mime_encode in iconv.o
      _php_iconv_string in iconv.o
      __php_iconv_strlen in iconv.o
      __php_iconv_strpos in iconv.o
      __php_iconv_appendl in iconv.o
      ...
  "_libiconv_close", referenced from:
      _do_convert in gdkanji.o
      _zif_iconv_substr in iconv.o
      _zif_iconv_mime_encode in iconv.o
      _php_iconv_string in iconv.o
      __php_iconv_strlen in iconv.o
      __php_iconv_strpos in iconv.o
      __php_iconv_mime_decode in iconv.o
      ...
  "_libiconv_open", referenced from:
      _do_convert in gdkanji.o
      _zif_iconv_substr in iconv.o
      _zif_iconv_mime_encode in iconv.o
      _php_iconv_string in iconv.o
      __php_iconv_strlen in iconv.o
      __php_iconv_strpos in iconv.o
      __php_iconv_mime_decode in iconv.o
      ...
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [sapi/cli/php] Error 1
```

どうも iconv 関連がうまくリンクできてないという結果に...

## なぜこうなったのか

- 本来は `/usr/local/opt/libiconv/lib` 以下のライブラリファイルの方をリンクしてほしいのに、XCode 11 の SDK 側のライブラリとリンクしてしまい、シンボルのズレが発生している
- 実際に見てしまっているのは `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib` を優先でみている
- `configure` コマンドを実行した結果生成された `Makefile` を見る限り、`PHP_RPATHS` が次のようになっていた

```
PHP_RPATHS = -R /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -R /usr/local/Cellar/openssl/1.0.2t/lib -R /usr/local/opt/zlib/lib -R /usr/local/opt/bzip2/lib -R /usr/local/Cellar/curl/7.67.0/lib -R /usr/local/opt/curl/lib -R /usr/local/lib -R /usr/local/opt/libiconv/lib -R /usr/local/Cellar/icu4c/64.2/lib -R /usr/local/opt/mcrypt/lib -R /usr/local/opt/libedit/lib
```

## どのように回避したのか

`PHP_RPATHS` を指定することにより、homebrew のライブラリを優先的にリンクするようにしました。

最終的には次のようなコマンドで、Catalina で PHP 5.6 を build することに成功しました。

(注意) 最低限だと `PHP_RPAHTS` に `/usr/local/opt/libiconv/lib` だけを指定すれば回避できるはずですが、他のライブラリも XCode 11  SDK 側が優先されるといやだなぁということで全部指定してます

```sh
YACC=$(brew --prefix bison@2.7)/bin/bison \
PHP_RPATHS="/usr/local/Cellar/openssl/1.0.2t/lib /usr/local/opt/zlib/lib /usr/local/opt/bzip2/lib /usr/local/opt/curl/lib  /usr/local/opt/libedit/lib /usr/local/opt/libiconv/lib /usr/local/opt/mcrypt/lib" \
PHP_BUILD_CONFIGURE_OPTS="--with-openssl=/usr/local/Cellar/openssl/1.0.2t --with-zlib-dir=/usr/local/opt/zlib --with-bz2=/usr/local/opt/bzip2 --with-curl=/usr/local/opt/curl --with-libedit=/usr/local/opt/libedit --with-iconv=/usr/local/opt/libiconv --with-mcrypt=/usr/local/opt/mcrypt --with-tidy" \
PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 \
phpenv install -i development 5.6.40
```

## こんなのやってられるか！！！

という声が多数あがりそうなので、`PHP_RPATHS` や `PHP_BUILD_CONFIGURE_OPTS` を設定するように `php-build` にフィードバックをこの年末年始にしようと思ってます。

今、ちょうど、 `php-build` のメインコミッターが travis の macOS 側の CI ベースを Xcode 10 にあげようとしているので、それに乗っかって、このフィードバックが取り込まれれば、Catalina でも `phpenv install` だけでいけるようになるはずです。

## 最後に

この件に関係するような記事で、 `php-build` の `default_configure_options` を変更するという方法を書かれているものがありますが、正直なところ、このファイルを変更することはおすすめできません。

このファイルは `php-build` に含まれるファイルなので、このファイルを変更してしまうと、 `php-build` が更新されるたびに conflict するので、素直に `php-build` を更新できなくなります。(いちいち毎回毎回変更点をマージする、めんどくさいですよね...)

`php-bulid` の configure のオプションを変更する場合には、今回の対処でやったように `PHP_BUILD_CONFIGURE_OPTS` 環境変数を使うことをおすすめします。

`php-build` へのフィードバックが取り込まれ次第、この記事を更新して、もうこんな苦労しないでいいよという風にしたいと思います。

## (追記) ちなみに...

試行錯誤していた際に一番長くなった時には次のように指定してました(最終的に CPPFLAGS と PHP_LDFLAGS は指定しないでいいことに気がついたので、上記のようなものになっています)

```sh
YACC=$(brew --prefix bison@2.7)/bin/bison \
PHP_RPATHS="/usr/local/Cellar/openssl/1.0.2t/lib /usr/local/opt/zlib/lib /usr/local/opt/bzip2/lib /usr/local/opt/curl/lib  /usr/local/opt/libedit/lib /usr/local/opt/libiconv/lib /usr/local/opt/mcrypt/lib" \
CPPFLAGS="-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include -I/usr/local/Cellar/openssl/1.0.2t/include -I/usr/local/opt/zlib/include -I/usr/local/opt/bzip2/include -I/usr/local/opt/curl/include -I/usr/local/opt/libedit/include -I/usr/local/opt/libiconv/include  -I/usr/local/opt/mcrypt/include" \
PHP_LDFLAGS="-L/usr/local/Cellar/openssl/1.0.2t/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/bzip2/lib -L/usr/local/opt/curl/lib -L/usr/local/opt/libedit/lib -L/usr/local/opt/libiconv/lib -L/usr/local/opt/mcrypt/lib" \
PHP_BUILD_CONFIGURE_OPTS="--with-openssl=/usr/local/Cellar/openssl/1.0.2t --with-zlib-dir=/usr/local/opt/zlib --with-bz2=/usr/local/opt/bzip2 --with-curl=/usr/local/opt/curl --with-libedit=/usr/local/opt/libedit --with-iconv=/usr/local/opt/libiconv --with-mcrypt=/usr/local/opt/mcrypt --with-tidy" \
PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 \
phpenv install -i development 5.6.40
```