この記事はBASE Advent Calendar 2019の16日目の記事です。
エンジニアの田中(@tenkoma)です。
あなたのマシンにインストールされているPHPのバージョンは何ですか?
仮想マシンやコンテナで開発環境を作ることが増えているので、ホストOSにはPHPが入ってない・気に掛けたことがない、ということも多いかもしれません。 僕は、新しいバージョンを試すためにphp-buildを使ってmacOSでビルド・インストールしています。(また、プロジェクト毎にバージョンの切り替えがしやすいようdirenvを使っています)
今回はphp-buildを使った複数バージョンビルドを、コードを書いて少し省力化してみたので紹介します。
前提
この記事で紹介するコードは以下の環境で実行しています。
- OS: macOS 10.15 Catalina
- 依存ライブラリのインストールはHomebrew
- php-buildはmotemen/ghqでローカル環境にclone
php-buildの導入・トラブルシュートは以下の記事が参考になります。
- 複数バージョンの PHP をインストールして使う - OTOBANK Engineering Blog
- Macのphpenv(php-build)でビルドしようとしたら出るエラーと解決まとめ - Qiita
作った理由
php-buildを使うと、自分でソースコードをダウンロードしてビルドするよりは楽に、ビルド・インストールができます。(ただし、ビルドエラー時に必要な依存ライブラリについて調査したりするので、導入時にある程度の知識や調査の時間が必要です)
例えば、以下のようなコマンドでビルド+インストールします。
$ php-build -i development 7.3.12 ~/local/php/7.3.12/
これでインストールできたらめでたいのですが、macOS をアップグレードしていくと、なぜか依存ライブラリが見つからなくなるようになってきたので、ビルドのためのオプションを付けて以下のように実行しています。(macOS Catalina 10.15.1にて実行)
$ PHP_BUILD_CONFIGURE_OPTS="--with-zlib-dir=$(brew --prefix zlib) --with-bz2=$(brew --prefix bzip2) --with-iconv=$(brew --prefix libiconv) --with-libedit=$(brew --prefix libedit) --with-openssl=$(brew --prefix openssl) --with-libxml-dir=$(brew --prefix libxml2) --with-curl=$(brew --prefix curl) --without-tidy" YACC=$(brew --prefix bison)/bin/bison PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 php-build -i development 7.3.12 ~/local/php/7.3.12/
さて、PHP の新しいバージョン(ポイントリリース)はだいたい1〜2ヶ月に1度リリースされているようですが、このとき、7.3と7.2と7.1の新しいバージョンがほぼ同時にリリースされるという感じなので、そのたびに以下のようなコマンドを実行することになります。
$ ghq look php-build $ git pull $ ./install.sh $ exit $ export PHP_BUILD_CONFIGURE_OPTS="--with-zlib-dir=$(brew --prefix zlib) --with-bz2=$(brew --prefix bzip2) --with-iconv=$(brew --prefix libiconv) --with-libedit=$(brew --prefix libedit) --with-openssl=$(brew --prefix openssl) --with-libxml-dir=$(brew --prefix libxml2) --with-curl=$(brew --prefix curl) --without-tidy" $ export YACC=$(brew --prefix bison)/bin/bison $ export PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j4 $ php-build -i development 7.3.12 ~/local/php/7.3.12/ $ php-build -i development 7.2.25 ~/local/php/7.2.25/ $ php-build -i development 7.1.33 ~/local/php/7.1.33/
php-build
コマンドを打つこと自体は対して大変ではありませんが、マイナーバージョンごとの最新バージョン番号を確認するのが面倒ですし、自動化出来そうだったのでやってみました。
複数のPHPバージョンをビルドするスクリプト
以下のスクリプトを作りました。
php-build-auto.sh
#!/usr/bin/env bash function usage_exit() { echo "Usage: $0 [OPTIONS] <version1> [<version2> [...]]" echo echo "Options:" echo " -h, --help" echo " --parallel num (default: CPU physical core number)" echo " --install-root-path path (default: \$HOME/src/local/php" echo " --override" echo " --show-versions" echo exit 1 } # option defalut PARALLEL=$(sysctl -n hw.physicalcpu_max) INSTALL_ROOT_PATH="$HOME/local/php" OVERRIDE=false SHOW_VERSIONS=false param=() for OPT in "$@" do case $OPT in -h | --help) usage_exit exit 1 ;; --parallel) PARALLEL=$2 shift 2 ;; --install-root-path) INSTALL_ROOT_PATH=$2 shift 2 ;; --override) OVERRIDE=true shift 1 ;; --show-versions) SHOW_VERSIONS=true shift 1 ;; *) if [[ -n "$1" ]] && [[ ! "$1" =~ ^-+ ]]; then param+=( "$1" ) shift 1 fi ;; esac done if [ -p /dev/stdin ]; then IFS=$'\n' for line in $(cat -) do param+=( "$line" ) done fi BUILD_VERSIONS=() SKIP_VERSIONS=() if [ $OVERRIDE = true ]; then BUILD_VERSIONS=$param else for VERSION in "${param[@]}" ; do if [ -e "$INSTALL_ROOT_PATH/$VERSION/bin/php" ]; then SKIP_VERSIONS+=($VERSION) else BUILD_VERSIONS+=($VERSION) fi done fi echo "skip versions:" echo "${SKIP_VERSIONS[@]}" echo "build versions:" echo "${BUILD_VERSIONS[@]}" if [ $SHOW_VERSIONS = true ]; then exit 0 fi export PHP_BUILD_CONFIGURE_OPTS="--with-zlib-dir=$(brew --prefix zlib) --with-bz2=$(brew --prefix bzip2) --with-iconv=$(brew --prefix libiconv) --with-libedit=$(brew --prefix libedit) --with-openssl=$(brew --prefix openssl) --with-libxml-dir=$(brew --prefix libxml2) --with-curl=$(brew --prefix curl) --without-tidy" export YACC="$(brew --prefix bison)/bin/bison" export "PHP_BUILD_EXTRA_MAKE_ARGUMENTS=-j$PARALLEL" echo "${BUILD_VERSIONS[@]}" | xargs -n1 -t -I@ php-build -i development @ "$INSTALL_ROOT_PATH"/@/
使い方ですが、引数でPHPバージョンを指定すると、まとめてビルドしてくれます。
$ ./php-build-auto.sh 7.0.33 7.1.33 7.3.12 skip versions: 7.0.33 build versions: 7.1.33 7.3.12 [Info]: Loaded extension plugin [Info]: Loaded apc Plugin. (以下略)
すでにインストール済みのバージョンがあれば、ビルドはスキップされます。もし再ビルドしたい場合は --override
オプションを付けます。
$ ./php-build-auto.sh --override 7.0.33 7.1.33 7.3.12
このスクリプトですが、作りはじめたときは xargs -P
を使って php-build
コマンドを並列実行させるのが最大の特徴でした。しかし、PHPビルド後のXdebugビルドは、同じディレクトリで実行されるので、複数のXdebugビルドを1ディレクトリで同時にやってしまい、エラーになってしまったのでその機能を削除しています。
マイナーバージョン毎の最新バージョン番号を列挙する
php-build-auto.sh
は、新しいポイントリリースが出たときにまとめてビルドしたいときに使います。そこで、最新のポイントリリースバージョンを列挙するスクリプトを別途作りました。
listversion.php
#!/usr/bin/env php <?php declare(strict_types=1); /** * usage: php listversion.php [--filter stable|minor-head] [--oldest-version version] [--definitions-path path] */ class PhpVersion { const VERSION_PATTERN = '/(?P<major>\d+)\.(?P<minor>\d+)\.(?P<point>\d+)/'; public static function getMinorVersion(string $version) { preg_match(self::VERSION_PATTERN, $version, $matches); return sprintf('%s.%s', $matches['major'], $matches['minor']); } public static function isStable(string $version) { return preg_match(self::VERSION_PATTERN, $version) === 1; } } $argvOptions = getopt('', ['filter:', 'oldest-version:', 'definitions-path:']); $options = [ 'filter' => !empty($argvOptions['filter']) ? $argvOptions['filter'] : 'minor-head', 'oldest_version' => !empty($argvOptions['oldest-version']) ? $argvOptions['oldest-version'] : '5.6.0', 'definitions_path' => !empty($argvOptions['definitions-path']) ? $argvOptions['definitions-path'] : '/usr/local/share/php-build/definitions/', ]; $definitionsIter = new DirectoryIterator($options['definitions_path']); $versions = []; foreach ($definitionsIter as $definition) { if ($definition->isDot()) { continue; } $version = $definition->getFilename(); if (! PhpVersion::isStable($version)) { continue; } if (version_compare($version, $options['oldest_version'], '<')) { continue; } if ($options['filter'] === 'minor-head') { $minorVersion = PhpVersion::getMinorVersion($version); if (!isset($versions[$minorVersion]) || version_compare($version, $versions[$minorVersion], '>')) { $versions[$minorVersion] = $version; } } else { $versions[] = $version; } } echo implode("\n", $versions) . PHP_EOL;
実行すると、各マイナーバージョン毎の最新バージョンを列挙します。
$ ./listversion.php 7.2.25 7.3.12 7.1.33 7.0.33
僕の環境だと 7.0.19 未満のバージョンはビルドエラーになったので、列挙しないようにしています。列挙したい場合は --oldest-version
オプションを使います。
$ ./listversion.php --oldest-version=5.3 7.2.25 5.4.45 7.3.12 7.1.33 5.3.29 5.5.38 7.0.33 5.6.40
php-build-auto.sh
は引数と標準入力の両方でビルド対象を指定できるので、 listversion.php
と組み合わせて、「マイナーバージョン毎の最新バージョンをまとめてビルド」、が実現できます。
$ ./listversion.php | ./php-build-auto.sh skip versions: 7.1.33 7.0.33 build versions: 7.2.25 7.3.12 php-build -i development 7.2.25 /Users/kojitanaka/local/php/7.2.25/ [Info]: Loaded extension plugin [Info]: Loaded apc Plugin. (以下略)
2つのスクリプトを作った結果、PHPのバージョンアップ時の作業は以下のように単純化できました。
$ ghq look php-build $ git pull $ ./install.sh $ exit $ cd ~/src/github.com/tenkoma/php-build-tools $ ./listversion.php | ./php-build-auto.sh
今回実装したスクリプトはtenkoma/php-build-toolsにて公開しています。
まとめ
複数のPHPバージョンを手元にそろえるときに使えるphp-buildの運用を多少楽にするためにコードを書き、バージョンアップ時に考える要素を減らしました。最初やりたかった複数バージョンの並列ビルドはできていません。
また、Homebrew でライブラリをアップグレードすると、php-build でビルドしたPHPが動作しなくなることもあります。2年前は5.3〜7.1まで揃えられましたが、Catalinaではまだ5.5, 5.6 のビルドができていません。
最新のmacOSで古いPHPを動かしにくくなってきている気がするので、PHPの古いバージョンを揃えたい場合は、phpallのDocker版を作ってみた話 - hamacoの日記のように、Docker を使った方がいいかもしれません。
明日はPlatform Devマネージャーの大窪さんとData Strategyの杉さんです。