y_uti のブログ

統計、機械学習、自然言語処理などに興味を持つエンジニアの技術ブログです

phpenv から PHPNG を使う

anyenv + phpenv の環境では、~/.anyenv/envs/phpenv/versions のサブディレクトリにインストールされた PHP がバージョン切り替えの対象になります。php-build を用いずにソースコードから独自にビルドする場合でも、./configure の --prefix オプションを適切に設定すれば、phpenv の管理対象に含められます。

今回は、この実例として、PHPNG をソースコードからビルドして phpenv で切り替えられるようにしてみます。インストール手順は公式サイトの記載にしたがいます。詳細は下記のウェブページを参照してください。

PHPNG のインストール

まず、リポジトリから最新のソースコードを取得します。

$ git clone https://git.php.net/repository/php-src.git

公式サイトの説明どおりにビルドを進めます。

$ cd php-src
$ ./buildconf
$ ./configure \
    --prefix=$HOME/.anyenv/envs/phpenv/versions/phpng \
    --with-config-file-path=$HOME/.anyenv/envs/phpenv/versions/phpng/etc \
    --with-libdir=lib64 \
    --enable-mbstring \
    --enable-zip \
    --enable-bcmath \
    --enable-pcntl \
    --enable-ftp \
    --enable-exif \
    --enable-calendar \
    --enable-sysvmsg \
    --enable-sysvsem \
    --enable-sysvshm \
    --enable-wddx \
    --with-curl \
    --with-mcrypt \
    --with-iconv \
    --with-gmp \
    --with-pspell \
    --with-gd \
    --with-jpeg-dir=/usr \
    --with-png-dir=/usr \
    --with-zlib-dir=/usr \
    --with-xpm-dir=/usr \
    --with-freetype-dir=/usr \
    --with-t1lib=/usr \
    --enable-gd-native-ttf \
    --enable-gd-jis-conv \
    --with-openssl \
    --with-mysql=/usr \
    --with-pdo-mysql=/usr \
    --with-gettext=/usr \
    --with-zlib=/usr \
    --with-bz2=/usr \
    --with-recode=/usr \
    --with-mysqli=/usr/bin/mysql_config \
    --with-apxs2=/usr/bin/apxs

./configure に渡すオプションで、ウェブサイトの記載内容から変更したものは以下のとおりです。なお、さまざまな --with-* が指定されていることもあり、依存関係はそれなりに多いようです。私の環境でも、yum で *-devel パッケージをいくつか追加する必要がありました*1

--prefix
phpenv の管理対象にするため、${HOME}/.anyenv/envs/phpenv/versions/phpng を指定します。phpng の部分は自由に変更できます
--with-config-file-path
--prefix の指定に合わせて、${HOME}/.anyenv/envs/phpenv/versions/phpng/etc を指定します
--with-libdir
これは phpenv には無関係な設定ですが、私の環境では、ここに lib64 を指定する必要がありました*2
--with-apxs2
Apache のモジュールを作成するため、このオプションを指定します。私の環境では /usr/bin/apxs を指定しました

make を実行する前に、Makefile を編集して libphp7.so の出力先を変更しておきます。これは、前々回の記事で紹介した tkuchiki 氏のパッチと同様の修正です。詳細は「php-build が libphp5.so を上書きしないようにするパッチ - tkuchikiの日記」を参照してください。以下のように INSTALL_IT ではじまる行を編集します。${exec_prefix} は、デフォルトでは --prefix と同じディレクトリになるので、~/.anyenv/envs/phpenv/versions/phpng/libexec 以下に libphp7.so が出力されるようになります。

$ diff -u Makefile- Makefile
--- Makefile-   2014-10-20 22:41:57.000218259 +0900
+++ Makefile    2014-10-20 22:43:13.786665139 +0900
@@ -105,7 +105,7 @@
 INCLUDES = -I/home/y-uti/src/php-src/ext/date/lib -I/home/y-uti/src/php-src/ext/ereg/regex -I/usr/include/libxml2 -I/usr/X11 -I/usr/include/freetype2 -I/home/y-uti/src/php-src/ext/mbstring/oniguruma -I/home/y-uti/src/php-src/ext/mbstring/libmbfl -I/home/y-uti/src/php-src/ext/mbstring/libmbfl/mbfl -I/usr/include/mysql -I/home/y-uti/src/php-src/ext/sqlite3/libsqlite -I/usr/include/pspell -I/home/y-uti/src/php-src/ext/zip/lib -I$(top_builddir)/TSRM -I$(top_builddir)/Zend
 EXTRA_INCLUDES =
 INCLUDE_PATH = .:/home/y-uti/.anyenv/envs/phpenv/versions/phpng/lib/php
-INSTALL_IT = $(mkinstalldirs) '$(INSTALL_ROOT)/usr/lib64/httpd/modules' && $(mkinstalldirs) '$(INSTALL_ROOT)/etc/httpd/conf' && /usr/bin/apxs -S LIBEXECDIR='$(INSTALL_ROOT)/usr/lib64/httpd/modules' -S SYSCONFDIR='$(INSTALL_ROOT)/etc/httpd/conf' -i -a -n php7 libphp7.la
+INSTALL_IT = $(mkinstalldirs) '${exec_prefix}/libexec' && $(mkinstalldirs) '$(INSTALL_ROOT)/etc/httpd/conf' && /usr/bin/apxs -S LIBEXECDIR='${exec_prefix}/libexec' -S SYSCONFDIR='$(INSTALL_ROOT)/etc/httpd/conf' -i -a -n php7 libphp7.la
 LFLAGS =
 LIBTOOL = $(SHELL) $(top_builddir)/libtool --silent --preserve-dup-deps
 LN_S = ln -s

Makefile を編集した後、make コマンドを実行して PHPNG をインストールします。php.ini は自動的には作られないようなので、php.ini-production ファイルをコピーしておきます。

$ make
$ make install
$ cp ./php.ini-production ~/.anyenv/envs/phpenv/versions/phpng/etc/php.ini

これで、phpenv から PHPNG を選択できるようになりました。phpenv versions で確認してみます。

$ phpenv versions
phpenv v0.0.4-dev

* system (set by /home/y-uti/.anyenv/envs/phpenv/version)
  5.3.29
  5.5.18
  5.6.2
  phpng

phpenv global で切り替えて、PHP のバージョンを確認します。バージョン 7.0.0-dev となっていることがわかります。

$ phpenv global phpng
phpenv v0.0.4-dev

phpng
$ php -v
PHP 7.0.0-dev (cli) (built: Oct 20 2014 22:50:33)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.8.0-dev, Copyright (c) 1998-2014 Zend Technologies

Apache module の切り替え

ここまでの手順で PHPNG をインストールした状態では、httpd からモジュールをロードすることができません。原因は次の二つです。

  1. phpenv の現在の実装は、モジュールのファイル名が libphp5.so であることを前提にしている
  2. httpd から libphp7.so をロードするには、モジュール名に php7_module を指定しなければいけない


一つ目の問題を確認します。PHPNG をインストールすると、以下のように libphp7.so が作成されます。

$ ls -l ~/.anyenv/envs/phpenv/versions/phpng/libexec
合計 36888
-rwxr-xr-x. 1 y-uti users 37770539 1020 22:51 libphp7.so*

ところが、phpenv global でバージョンを切り替えると、libphp5.so へのシンボリックリンクが作成されます。リンク先の libphp5.so ファイルは存在しません。

$ ls -l ~/.anyenv/envs/phpenv/lib
合計 0
lrwxrwxrwx. 1 y-uti users 68 1021 05:36 libphp5.so -> /home/y-uti/.anyenv/envs/phpenv/versions/phpng/libexec/libphp5.so

二つ目の問題は、libphp7.so をロードするために conf ファイル自体を切り替える必要があるということです。PHP 5.x 同士の切り替えであれば、conf ファイルに次のように書いておけば問題ありませんでした。指定されているファイルはシンボリックリンクで、PHP のバージョンに応じてリンク先のファイルがロードされる仕組みでした。

$ cat /etc/httpd/conf.modules.d/10-php.conf
#
# PHP is an HTML-embedded scripting language which attempts to make it
# easy for developers to write dynamically generated webpages.
#
<IfModule prefork.c>
# LoadModule php5_module modules/libphp5.so
  LoadModule php5_module /home/y-uti/.anyenv/envs/phpenv/lib/libphp5.so
</IfModule>

ところが、PHPNG の libphp7.so をロードするにはモジュール名を php7_module としなければいけないので、so ファイルの切り替えだけでは上手くいきません。conf ファイル自体を切り替える仕組みを作り込む必要があります。

以下では、これらの問題を解決して httpd から libphp7.so をロードできるようにする方法を紹介します*3

まず、以下のようにして、PHP 5.x 用と PHP 7.x 用の conf ファイルをそれぞれ作成します。php5.conf ではモジュール名を php5_module に、php7.conf では php7_module にします。モジュールのファイル名は普通は libphp5.so または libphp7.so なのですが、シンボリックリンクを作成して切り替えることを意識して、敢えてバージョン番号を削っています。

$ mkdir ~/.anyenv/envs/phpenv/etc/httpd
$ cd ~/.anyenv/envs/phpenv/etc/httpd
$ vi php5.conf
#
# PHP is an HTML-embedded scripting language which attempts to make it
# easy for developers to write dynamically generated webpages.
#
<IfModule prefork.c>
  LoadModule php5_module /home/y-uti/.anyenv/envs/phpenv/lib/libphp.so
</IfModule>

$ sed 's/php5_module/php7_module/' php5.conf >php7.conf

次に、phpenv-global スクリプトの実装を変更します。配布元のリポジトリとの差分は以下のようになります。

$ git diff remotes/origin/HEAD phpenv-global
diff --git a/libexec/phpenv-global b/libexec/phpenv-global
index 96f5b39..089dc88 100755
--- a/libexec/phpenv-global
+++ b/libexec/phpenv-global
@@ -44,12 +44,17 @@ echo ${PHPENV_VERSION}

 # Link Apache apxs lib
 APXS=""
+if [ "${PHPENV_VERSION}" == "system" ]; then
+    APXS="$(which apxs 2>/dev/null)"
+fi
 php-config --configure-options 2>/dev/null | grep -q apxs  && \
     APXS="$(php-config --configure-options| sed 's/.*=\(.*apxs[^ ]*\) .*/\1/')"

 [[ -d "${PHPENV_ROOT}/lib" ]] || mkdir "${PHPENV_ROOT}/lib"
 if [ -n "${APXS}" ]; then
+    LIBPHP_VERSION="$(php -r 'echo substr(phpversion(), 0, 1);')"
     [[ "${PHPENV_VERSION}" == "system" ]] && \
-        ln -fs "$(${APXS} -q LIBEXECDIR)/libphp5.so" "${PHPENV_ROOT}/lib/libphp5.so" || \
-        ln -fs "${PHPENV_ROOT}/versions/${PHPENV_VERSION}/libexec/libphp5.so" "${PHPENV_ROOT}/lib/libphp5.so";
+        ln -fs "$(${APXS} -q LIBEXECDIR)/libphp${LIBPHP_VERSION}.so" "${PHPENV_ROOT}/lib/libphp.so" || \
+        ln -fs "${PHPENV_ROOT}/versions/${PHPENV_VERSION}/libexec/libphp${LIBPHP_VERSION}.so" "${PHPENV_ROOT}/lib/libphp.so";
+    ln -fs "${PHPENV_ROOT}/etc/httpd/php${LIBPHP_VERSION}.conf" "${PHPENV_ROOT}/etc/httpd/php.conf"
 fi

前半の三行の差分は、phpenv global system を実行したときにデフォルトの libphp5.so にリンクを張るための設定です。これは以前の記事で紹介した内容で、今回の記事には関係しません。

次の差分が、今回の変更のポイントです。ここで、PHP のメジャーバージョンに合わせて LIBPHP_VERSION が 5 または 7 になります。

+    LIBPHP_VERSION="$(php -r 'echo substr(phpversion(), 0, 1);')"

あとは、${LIBPHP_VERSION} を使って、so ファイルと conf ファイルへのシンボリックリンクを作成します。

これで、~/.anyenv/envs/phpenv/etc/httpd/php.conf を読み込めば正しくモジュールがロードされるようになります。最後に、このファイルへのシンボリックリンクを作成します。

$ cd /etc/httpd/conf.modules.d
$ sudo mv 10-php.conf 10-php.conf.orig
$ sudo ln -s ~/.anyenv/envs/phpenv/etc/httpd/php.conf 10-php.conf

結果を確認してみます。このように PHP/7.0.0-dev の表示が得られました。もちろんウェブブラウザからアクセスして確認しても同じです。

$ phpenv global phpng
$ sudo systemctl reload httpd.service
$ curl http://localhost/phpinfo.php 2>/dev/null | grep 'Apache Version'
<tr><td class="e">Apache Version </td><td class="v">Apache/2.4.6 (CentOS) PHP/7.0.0-dev </td></tr>

*1:大半のパッケージは名前どおりのものが見つかり、特に悩むことはありませんでしたが、一つだけ、pspell-devel というパッケージは存在せず、代わりに aspell-devel をインストールする必要がありました。

*2:指定しないと libmysqlclient.so を探せないようでした。

*3:あくまでも解決方法の一例です。ここで紹介する方法の他にも、いろいろなやり方があると思います。