PEAR::CodeGen_PECLを使ってPECLモジュールを作ってみる
PEARにはいろいろなライブラリが整備されていますが、PhpDocumentorやPHP_BeautifierなどPHPアプリケーションの開発を手助けするものもあります。変わり者として、PHP自体。。。といってもPECLモジュールになりますが、開発を手助けするパッケージがあります。これがPEAR::CodeGen_PECLです。
PEAR::CodeGen_PECLはPHP5専用のパッケージで、specファイルと呼ばれるXMLファイルから、PECLモジュールを作るのに必要な各種ファイルを生成してくれるツールで、PECLモジュールのCソースやヘッダファイルのひな形も出力してくれます。
PECLモジュールは基本的にC/C++で書かれていますが、モジュールの初期化や関数・クラスの定義、パラメータの取扱などに多くの「お約束事」があります。PEAR::CodeGen_PECLでは、生成するCソースにある程度の「決まり事」をドサッと追記してくれます。
また、PEAR::CodeGen_PECLは、PEAR::CodeGenパッケージに依存しています。PEAR::CodeGenは、何となく想像が付くと思いますが、「specファイルから何ら火を生成・出力するツール」の抽象パッケージになっていて、基本的な動作はPEAR::CodeGen側に書かれています。PEAR::CodeGenを継承したPEARパッケージとしては、2007/03/30時点で以下のものがあります。
今回はPEAR::CodeGen_PECLの基本的な使い方と簡単な関数群を持つPECLモジュールの作り方についてまとめてみました。
使用した環境
今回の環境は、CentOS4.4+PHP5.2.1です。PHPは以下のようなconfigureオプションを付けてソースからbuildしています。
●PHP5.2.0のconfigureオプション--with-apxs2=/usr/local/apache2/bin/apxs \
--prefix=/usr/local/lib/php5 \
--with-pear=/usr/local/lib/php5/pear \
--with-config-file-path=/usr/local/lib/php5/ini/5.2.1 \
--with-config-file-scan-dir=/usr/local/lib/php5/ini.d \
--enable-zend-multibyte \
--enable-mbstring=shared \
--with-dom \
--with-gd=shared \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-ttf \
--with-freetype-dir \
--enable-gd-jis-conv \
--enable-soap=shared \
--enable-pdo=shared \
--with-pdo-sqlite=shared \
--with-sqlite=shared \
--with-pspell=shared \
--enable-debug \
--with-openssl=shared \
--with-mysql=shared \
--with-pdo-mysql=shared \
--with-curl=shared \
--enable-bcmath=shared \
--enable-pcntl=shared \
--enable-sockets=shared \
--enable-htscanner=shared
いずれも「--prefix」オプションを付けているため、phpコマンドのパスがデフォルトとは変わっていますが適宜読み替えてください。
インストール
まずはインストールですが、pearコマンドを使ったお決まりの手順です。PEAR::CodeGen_PECLをインストールすると、pecl-genコマンドが追加されますので、動作確認しておきましょう。
$ sudo pear install -a codegen_pecl
$
$ which pecl-gen
/usr/local/lib/php5/bin/pecl-gen
$
$ pecl-gen
Usage:
pecl-gen [-h] [--force] [--experimental] [--version]
[--extname=name] [--proto=file] [--skel=dir] [--stubs=file]
[--no-help] [--xml[=file]] [--full-xml] [--function=proto] [specfile.xml]
-h|--help this message
-f|--force overwrite existing directories
-d|--dir output directory (defaults to extension name)
-l|--lint check syntax only, don't create output
--linespecs generate #line specs
-x|--experimental deprecated
--function create a function skeleton from a proto right away
--version show version info
the following options are inherited from ext_skel:
--extname=module module is the name of your extension
--proto=file file contains prototypes of functions to create
--xml generate xml documentation to be added to phpdoc-cvs
these wait for functionality to be implemented and are ignored for now ...
--stubs=file generate only function stubs in file
--no-help don't try to be nice and create comments in the code
and helper functions to test if the module compiled
these are accepted for backwards compatibility reasons but not used ...
--full-xml generate xml documentation for a self-contained extension
(this was also a no-op in ext_skel)
--skel=dir path to the skeleton directory
(skeleton stuff is now self-contained)
$
$ pecl-gen --version
pecl-gen 1.0.3,Copyright (c) 2003-2005 Hartmut Holzgraefe
$
PECLモジュールの生成手順
先に生成手順を見ておきましょう。手順は以下の通りです。
- specファイルの作成
- pecl-genコマンドの実行
- phpize・configure・make・make test・make install
たったこれだけですが、ほとんどの時間は(当然)specファイルの作成に費やされます。
では、とりあえずPECLモジュールを実際に作ってみます。
まずはspecファイルです。今回は以下のspecファイルを使います。
●firstpecl.xml<?xml version="1.0" ?>
<extension name="firstpecl" version="1.0.0">
<function name="foo">
<proto>string foo()</proto>
<code>
RETURN_STRING("OK", 1);
</code>
<test>
<code>
echo foo();
</code>
</test>
</function>
</extension>
次にpecl-genコマンドを実行します。-fオプションを使って上のfirstpecl.xmlファイルを指定します。ほとんど一瞬で終わります :-)
●pecl-genコマンドの実行$ pecl-gen -f firstpecl.xml
Creating 'firstpecl' extension in './firstpecl'
Your extension has been created in directory ./firstpecl.
See ./firstpecl/README and ./firstpecl/INSTALL for further instructions.
$ cd firstpecl/
$
次はbuildの実行です。その他のPECLモジュールを手動インストールする場合の手順と全く同じですね。configureオプションには、「--enable-firstpecl」を指定します。
●build実行$ phpize
$ ./configure --enable-firstpecl
$ make
$
buildが終わったらテストを実行します。firstpecl.xmlファイルには1つのテストが定義されていますので、実行してみましょう。
●テスト実行$ make test
Build complete.
(It is safe to ignore warnings about tempnam and tmpnam).
=====================================================================
CWD : /home/shimooka/public_html/pear/CodeGen_PECL/firstpecl
PHP : /usr/local/lib/php5/bin/php
PHP_SAPI : cli
PHP_VERSION : 5.2.1
ZEND_VERSION: 2.2.0
PHP_OS : Linux - Linux hrimfaxi 2.6.9-42.0.10.ELsmp #1 SMP
Tue Feb 27 10:11:19 EST 2007 i686
INI actual : /usr/local/lib/php5/ini/5.2.1/php-cli.ini
More .INIs :
Extra dirs :
=====================================================================
Running selected tests.
PASS foo() function [tests/foo.phpt]
=====================================================================
Number of tests : 1 1
Tests skipped : 0 ( 0.0%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Tests passed : 1 (100.0%) (100.0%)
---------------------------------------------------------------------
Time taken : 0 seconds
=====================================================================
$
なお、テストを実行する際は、他の拡張モジュールがロードされない可能性がありますので、php.iniもしくはphp-cli.iniのextensionディレクティブをコメントアウトしておいた方が無難です。これは、Makefilenaiを見てもらうと分かりますが、テスト実行時にextension_dirの設定がカレントディレクトリ直下の「modulesディレクトリ」に変更されるためです。そのため、正常動作しているのにテスト結果がエラーとなる場合があります。
テストがうまくいったら、インストールします。
●インストール実行$ sudo make install
$
最後にApache+PHP+firstpeclモジュールで動作確認します。php.iniにextensionディレクティブを追加し、Apacheを再起動します。
●php.iniに追記して、Apache再起動$ echo "extension=firstpecl.so" >> /usr/local/lib/php5/ini/5.2.1/php.ini
$ sudo /usr/local/apache2/bin/apachectl graceful
$
phpinfoを表示し、以下のような項目が現れれば、まずは成功です。
ついでに、テストコードの内容を実行してみましょう。当然「OK」と表示されるハズです ;-)
●動作テスト(test.php)<?php
echo foo();
?>
specファイルについて
PECLモジュールのAPIを定義するspecファイルですが、サンプルのspecファイルで何となく分かったと思います。その他のXML要素や属性はマニュアルを参照して。。。と言いたいところですが、PEAR::CodeGen_PECLのページからリンクされているユーザー向けドキュメントがリンク切れになっています(2007/03/30現在)。仕方がないので、Internet Archive Wayback Machineから持ってきたドキュメントを保存しておきましたので、見てみてください。
[2007/04/05]pear.php.netでバグ報告を挙げたらすぐに対応してもらえました :-) 今は正しくリンクされていて、ドキュメントのバージョンも上がっています。
が、このドキュメントも完全ではありません。利用可能な要素や属性については、cvs.php.netにあるCodeGen_PECL用のDTDを参照するのがもっとも正確です。ちなみに、PEAR::CodeGen_PECLは内部でXMLパーサ関数を使っています。
specファイルのその他サンプルは、cvs.php.netのリポジトリにあります。いろいろなパターンについて用意されているので、非常に参考になります。
ついでにcheckoutして手元に置いておくと、何かと便利かも知れません。
$ cvs -d:pserver:cvsread@cvs.php.net:/repository login
Logging in to :pserver:cvsread@cvs.php.net:2401/repository
CVS password:(「phpfi」と入力)
$ cvs -d:pserver:cvsread@cvs.php.net:/repository co pear/CodeGen_PECL/docs/examples
$
libuuidバインディングを作ってみる
2007/03/28に株式会社ノッキングオンで行われた第22回PHP勉強会で発表したネタですが、実際にPECLモジュールを作ってみました。すでに勉強会用の資料(pdf形式)やspecファイルを公開していますが、ここでは実際の作業の流れをライブ風味でまとめています。何かの参考になれば、ということでお願いします :-)
なお、uuid拡張モジュールは、すでにPECLに登録されています ;-)
まず、いきなりすべてのAPIを決めて実装するのがつらいので、1つだけAPIを決めます。libuuidはUUID(Universally Unique Identifier)、要は一意なIDを生成するためのライブラリで、libuuid(3)によると、利用可能なAPIは以下の通りとなります。
- uuid_clear(3)
- uuid_compare(3)
- uuid_copy(3)
- uuid_generate(3)
- uuid_is_null(3)
- uuid_parse(3)
- uuid_time(3)
- uuid_unparse(3)
ここでは、UUIDを生成するuuid_generate(3)を使った関数をまずは実装することにしました。proto要素にこの関数のプロトタイプを書いていますが、「呼び出すとUUIDを返す」といったイメージです。
●uuid.xml<?xml version="1.0" ?>
<extension name="uuid" version="0.0.1">
<function name="uuid">
<proto>string uuid()</proto>
</function>
</extension>
pecl-genコマンドを使ってひな形をはき出します。
●pecl-genコマンドの実行$ pecl-gen -f uuid.xml
$ cd firstpecl/
$
ここで、生成されたCソースを編集して具体的な実装を行います。また、同時に生成されているテストスクリプト(uuid.phpt)も次のように編集します。
●編集後のテストスクリプト(uuid.phpt)--TEST--
uuid() function
--SKIPIF--
<?php
if(!extension_loaded('uuid')) die('skip');
?>
--FILE--
<?php
echo strlen(uuid());
?>
--EXPECT--
36
とりあえず、phpizeとconfigureを実行し、Makefileを生成します。
●phpize・configureの実行$ phpize
$ ./configure --enable-uuid
$
libuuidをリンクさせるため、Makefileを以下のように編集します。
●Makefileの編集UUID_SHARED_LIBADD = -luuid
buildとテストを実行します。
●buildとテストの実行$ make
$ make test
Build complete.
(It is safe to ignore warnings about tempnam and tmpnam).
=====================================================================
CWD : /home/shimooka/public_html/pear/CodeGen_PECL/uuid
PHP : /usr/local/lib/php5/bin/php
PHP_SAPI : cli
PHP_VERSION : 5.2.1
ZEND_VERSION: 2.2.0
PHP_OS : Linux - Linux hrimfaxi 2.6.9-42.0.10.ELsmp #1 SMP
Tue Feb 27 10:11:19 EST 2007 i686
INI actual : /usr/local/lib/php5/ini/5.2.1/php-cli.ini
More .INIs :
Extra dirs :
=====================================================================
Running selected tests.
PASS uuid() function [tests/uuid.phpt]
=====================================================================
Number of tests : 1 1
Tests skipped : 0 ( 0.0%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Tests passed : 1 (100.0%) (100.0%)
---------------------------------------------------------------------
Time taken : 0 seconds
=====================================================================
$
念のため、libuuid.soがリンクされているか確認してみます。生成されたモジュールは、直下のmodulesディレクトリにあります。
●リンクの確認$ ldd modules/uuid.so
libuuid.so.1 => /lib/libuuid.so.1 (0x009a6000)
libc.so.6 => /lib/tls/libc.so.6 (0x006ba000)
/lib/ld-linux.so.2 (0x00b64000)
$
モジュールのインストールを行い、Apacheとの組み合わせでも確認してみます。以下のようなスクリプトで動作を確認します。
●インストール~Apache再起動$ sudo make install
$ echo "extension=uuid.so" >> /usr/local/lib/php5/ini/5.2.1/php.ini
$ sudo /usr/local/apache2/bin/apachectl graceful
$
●動作テスト(uuid.php)<?php
phpinfo();
echo uuid();
?>
ここまでで一区切りです。今回は最終的な結果をspecファイルに書き戻しました。書き戻したspecファイルは次のようになります。
●書き戻したuuid.xml<?xml version="1.0"?>
<extension name="uuid" version="0.0.1">
<summary>uuid extension</summary>
<description>libuuid binding</description>
<deps language="c">
<with name="libuuid" defaults="/lib:/usr:/usr/local" testfile="include/uuid/uuid.h">
<header name="uuid/uuid.h" />
<lib name="uuid" />
</with>
</deps>
<function name="uuid">
<proto>string uuid()</proto>
<code>
<![CDATA[
/**
* @see http://e2fsprogs.sourceforge.net/
* @see uuidgen.c
* @see uuid_generate(3)
* @see uuid_unparse(3)
*/
uuid_t uu;
char str[37];
uuid_generate(uu);
uuid_unparse(uu, str);
RETURN_STRING(str, 1);
]]>
</code>
<test>
<code>
<![CDATA[
echo strlen(uuid());
]]>
</code>
<result>36</result>
</test>
</function>
</extension>
このような手順で、
したものが、すでに公開しているspecファイルになります。
PECLパッケージの作成
pecl-genコマンドで生成されるファイルの中に、package.xmlとpackage2.xmlがあります。これはpeclコマンド(pearコマンド)でインストール可能なパッケージを作る際に必要なファイルです。これにより、peclコマンドで簡単にパッケージングすることができます。
この際、ちょっとだけ注意が必要です。specファイルの最終形であるuuid_0_0_5.xmlにはmaintainer要素がありますが、その子要素のrole要素に必ず「lead」を含むものが必要になります。たとえば、開発者が3人いる場合、1人以上は「lead」にする必要があります。
●書き戻したuuid.xml<?xml version="1.0"?>
<extension name="module_name" version="0.0.1">
:
<maintainer>
<user>foo</user>
<name>FOO Hoge</name>
<email>foo@doyouphp.jp</email>
<role>lead</role>
</maintainer>
<maintainer>
<user>bar</user>
<name>BAR Moge</name>
<email>bar@doyouphp.jp</email>
<role>developer</role>
</maintainer>
<maintainer>
<user>baz</user>
<name>BAZ Mohe</name>
<email>bar@doyouphp.jp</email>
<role>helper</role>
</maintainer>
:
</extension>
先ほどのuuid_0_0_5.xmlを使ってパッケージングする場合、role要素の値を「lead」に変更する必要があります。修正してパッケージング対応ものは以下からダウンロードできます。
パッケージングの方法はpeclコマンドのヘルプにもありますが、第1引数に「package」、第2引数にpackage.xmlもしくはpackage2.xmlを指定します。以下は、uuid_0_0_6.xmlを使った場合の例です。
●uuid拡張モジュールのパッケージング実行例$ pecl package package2.xml
Package uuid-0.0.6.tgz done
$
ここまで来れば、他のPECLモジュールと同じくpeclコマンドだけでインストールが可能になります。他のディレクトリにコピーしてインストールを実行します。今のところ、libuuidのインストールディレクトリが聞かれますが、リターンを押下することで問題ないと思います。
●パッケージングしたuuidモジュールのインストール$ cp uuid-0.0.6.tgz /path/to/
$ cd /path/to/
$ pecl install uuid-0.0.6.tgz
:
1. libuuid installation directory? : autodetect
1-1, 'all', 'abort', or Enter to continue:(リターン)
:
$
$ pecl list
Installed packages, channel pecl.php.net:
=========================================
Package Version State
PDO 1.0.3 stable
PDO_USER 0.2.0 beta
docblock 0.2.0 alpha
htscanner 0.7.0 alpha
uuid 0.0.6 alpha
xdebug 2.0.0RC3 beta
zip 1.8.6 stable
$
なお、package.xmlやpackageには要求されるPHPの最低バージョンが記載されますが、PEAR::CodeGen_PECL 1.0.3の場合、[PEARのインストールディレクトリ]/CodeGen/PECL/Extension.phpのminPhpVersionメソッドを見てみると、
- 他の拡張モジュールに依存している場合は5.1.0rc
- クラスまたはインターフェースが定義されている場合は5.0.0
- それ以外は4.0.0
となるようです。
まとめ
途中からは、Cソースではなく直接specファイルを編集したりする場面もありましたが、
- 具体的な実装の変更はCソースやヘッダファイルを編集
- 「PHP的」な変更(定数の追加とかクラス化)はspecファイルを編集
した方がお手軽です(当然ですね)。いずれにしても、今までよりも全然楽にPECLモジュールを作れると思います。
最近ではPEARモジュールを作って公開している日本人開発者が増えてきましたが、ここらで一つ「日本発のPECLモジュール」ってのはどうでしょうか?
|