Module::Install::XSUtil - Manages XS modules

Makefile.PLを書くためのビルドユーティリティの一つであるModule::Installは非常に便利だが,XSモジュールのサポートが弱い。
そこで,Module::InstallのプラグインとしてModule::Install::XSUtilを書いてみた。

XSモジュールを利用は,Perlから利用する場合とXSから利用する場合の二つの状況がある。ほとんどのXSモジュールはPerlから利用するが,まれにXSからの利用するモジュール(メタXSモジュール)もある。XSUtilはこの二つを両方サポートする。

通常のXSモジュールの場合

XSモジュールでは一般的に,コンパイラの有無のチェックやppportのサポート,ExtUtils::ParseXS*1やCライブラリの依存処理が必要である。また,ヘッダファイルやソースファイルを一つのディレクトリにまとめたいときはさらに複雑で,インクルードパスの設定やソースファイルの設定なども行わなければならない。こ特にインクルードパス・ソースディレクトリの設定は至難であり,ディストリビューションディレクトリ直下にすべてのソースファイルとヘッダファイルをまとめて置いておく場合が少なくない。

XSUtilはこれらの設定を一挙に担う。

# Devel::PPPortをconfigure_requiresに入れ,ppport.hを作成する
# よってppport.hをディストリビューションに含める必要はない
use_ppport 3.19;

# コンパイラの警告オプションを有効にする
# use warnings同様,常に有効にすることが望ましい
cc_warnings;

# ヘッダファイルを入れておくディレクトリを設定する
# *.hが自動的に検索される
cc_include_paths 'include';

# ソースファイルを入れておくディレクトリを設定する
# *.xs, *.c, *.cpp, *.cc *.cxxなどが自動的に検索される
cc_src_paths 'src';

コンパイラの有無のチェック,ExtUtils::ParseXSの依存処理はXSUtilが提供するいずれかのコマンドを呼び出すと自動的に行われる。

従来はppport.hをディストリビューションに含めるのが一般的だった。それはそれで構わないとしても,ディストリビューションのユーザーがMakefile.PLでppport.hを作れるならそのほうがいい。ただしこれを行う場合はユーザーがDevel::PPPortをインストールする必要がある。なお,Module::Installコアにもppport()というコマンドがあるが,これはディストリビューションの開発環境でppport.hを作成し,利用の際はppport.hの有無をチェックするというものである。

cc_include_paths()やcc_src_paths()では定められたファイルを検索し,MakeMakerの引数として渡す。Module::InstallコアでもModule::Install::Compilerというコンポーネントで類似のコマンドを提供しているが,機能が単純すぎて実用的ではないので新規に作成した。

メタXSモジュール

今までメタXSモジュールのサポートを担ってきたモジュールはExtUtils::Dependsである。これを利用したモジュールとしては,たとえばB::Hooks::OP::AnnotationB::Hooks::OP::Checkがある。ExtUtils::Dependsはその名の通りモジュールの依存関係を処理するモジュールなのだが,ヘッダファイルをインストールする機能があるため,B::Hooks::*ではそのためにのみ使っている。
しかし,たとえばB::Hooks::OP::Annotationや,それを利用するDevel::PragmaなどのMakefile.PLをみるとわかるように,ExtUtils::Dependsは非常に使いにくい。たとえば,ヘッダファイルをインストールするよう指示する場合,ほかにインストールするすべての*.pmモジュールを手動で指定しなければならなくなる。
XSUtilは,このヘッダファイルをインストールし,そのモジュールの二次ユーザーに対してインクルードパスを適切に設定するという処理を自動化する。

# ヘッダファイルの場所
cc_include_paths 'include';

# cc_include_pathsで設定したパスにあるヘッダファイルをすべてインストールする
install_headers;

# 特定のヘッダファイルのみインストールしたいときはその旨を記述する
installl_headers qw(mylib.h);

なお,メタXSモジュール本体では,今のところXSLoaderではなくDynaLoaderを利用し,dl_load_flagsメソッドで0x01を返さなければならない*2

package XS::Foo;
{
    use DynaLoader;
    our @ISA;
    local @ISA           = qw(DynaLoader);
    local *dl_load_flags = sub{ 0x01 };
    __PACKAGE__->bootstrap($VERSION);
}

さて,このようなメタXSモジュールがあるとき,このモジュールのユーザーは以下のようにすればよい。

require_xs 'XS::Foo' => 1.414;

これでまずXSUtilの基本機能としてコンパイラの有無をチェックした後,XS::Fooをrequires()に入れ,XS::Fooが提供するヘッダファイルのためにインクルードパスを設定する。あとは普通にモジュール内部でuse XS::Fooするだけだ。もちろん必要に応じてuse_ppport()やcc_warnings()も行える。

TODO

さて,ここまでの基本機能は実装したものの,まだテストは不十分である。

特に,メタXSモジュールのための機能のテストをほとんどしておらず,その方法も確立できていない。そもそも,メタXSモジュールのテストは至難を極める。これは一般的な問題であるようで,たとえばB::Hooks::OP::AnnotationやB::Hooks::OP::Checkでは機能のテストはしておらず,唯一のテストはモジュールのロードの成否である。これはもっと気軽にテストできるようにする仕組みが必要がある。

なお,Perlから利用するXSモジュールを作成する際には,機能のテストもあり,特に問題はないと思われる。

*1:xsubpp(1)の実体

*2:この機能をXSLoaderに実装するパッチはすでに書いてある(RT #43344)。