Perl入門ゼミ

テキスト処理、Linuxサーバー管理、Web開発ならPerl
  1. Perl
  2. XS
  3. here

ExtUtils::MakeMaker - Makefile.PLのカスタマイズ

ExtUtils::MakeMakerを使ったMakefile.PLの記述方法を解説します。

テストスクリプトを指定する

デフォルトでは「t」ディレクトリ以下の試験スクリプトが実行されますが、さらに深い階層のサブディレクトリの試験スクリプトを実行したい場合があると思います。

そういう場合はtestオプションを使用します。以下の例は、3階層下までの試験スクリプトが実行されるようにするものです。

  test => {TESTS => 't/*.t t/*/*.t t/*/*/*.t'}

make clean,make realcleanをするときに削除するファイルを追加する

Makefile.PLは自動的にMakefileを作成しますが、このときに「make clean」と「make realclean」で実効されるコマンドが定義されます。

ときには、特定のファイルを「make clean」「make realclean」するときに、削除したいと思うときがあると思います。

そのような場合は、Makefile.PLの中のWriteMakefile関数で、以下のオプションを記述することで対応することができます。

clean => {FILES => "*.xyz foo"}
realclean => {FILES => '$(INST_ARCHAUTODIR)/*.xyz'}

FILESにおいては、makeの文法規則でファイル名を記述する必要があるので、注意してください。

XSファイルの名前を変更する方法

h2xsを使って、XSモジュールを作成すると、名前空間に「::」が入っていると、XSファイル名は、「末尾の名前.xs」というのになる。

たとえば「Some::Module」という名前でh2xsを使ってXSモジュールを作成すると、XSファイル名は「Module.xs」となる。これは少し気持ちが悪い。

自由なXSファイル名をつけるには、Makefile.PLの中で、XSファイル名を指定する必要がある。

たとえば「SomeModule.xs」という名前でXSファイルを作成したければ次のように記述する。コメントのついている「XSオプション」と「OBJECT」オプションの部分だけ見てほしい。他の部分は自動生成されたところです。

use ExtUtils::MakeMaker;

WriteMakefile(
    NAME              => 'Rstats::Element',
    VERSION_FROM      => 'lib/Rstats/Element.pm',
    PREREQ_PM         => {},
    ($] >= 5.005 ?
      (ABSTRACT_FROM  => 'lib/Rstats/Element.pm',
       AUTHOR         => 'A. U. Thor <kimoto@sakura.ne.jp>') : ()),
    LIBS              => [''],
    # XSオプション
    XS     => {'SomeModule.xs' => 'SomeModule.c'},
    DEFINE            => '',
    INC               => '-I.',
    # OBJECTオプション
    OBJECT => 'SomeModule$(OBJ_EXT)'
);

XSオプションで「SomeModule.xs」を「SomeModule.c」という名前にすると指定します。これはXSファイルをC言語ソースファイルに変換するxsubppへの命令になります。

ただしこれだけではだめで、コンパイル対象のC言語ファイル名(これはオブジェクト名によって記述)も記述する必要があります。それを、OBJECTオプションで指定します。「$(OBJ_EXT)」の部分は、makeの特別な変数のようなもので「.o」に置換されます。

このように記述すると「SomeModule.c」がコンパイル対象になります。ややこしいですね。

gccの最適化オプションを指定する

XSでgccの最適化オプションを指定するにはMakefile.PLの中のWriteMakefileのオプションとして、OPTIMIZEオプションを指定します。

OPTIMIZE => '-O3'

-O3を指定すると、一番強い最適化が行われるようです。

以下はOPTIMIZEオプションを使ったサンプルです。

use strict;
use warnings;
use ExtUtils::MakeMaker;

WriteMakefile(
    NAME                => 'Rstats',
    AUTHOR              => 'Yuki Kimoto <kimoto.yuki@gmail.com>',
    VERSION_FROM        => 'lib/Rstats.pm',
    ABSTRACT_FROM       => 'lib/Rstats.pm',
    ($ExtUtils::MakeMaker::VERSION >= 6.3002
      ? ('LICENSE'=> 'perl')
      : ()),
    PL_FILES            => {},
    PREREQ_PM => {
        'Test::More' => 0,
        'Object::Simple' => '3.10',
        'Math::Round' => '0.06',
        'Text::UnicodeTable::Simple' => '0.09'
    },
    dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
    clean               => { FILES => 'Rstats-*' },
    CC =>'g++',
    OPTIMIZE => '-O3',
    LD => 'g++',
    LIBS              => [''],
    DEFINE            => '',
    INC               => '-I.',
    OBJECT            => '$(O_FILES)',
);

Makefile.PLでC/C++言語のヘッダファイルの検索パスを追加する方法

Makefile.PLを書いていると、C言語/C++のヘッダファイルの場所を変更したり、追加したくなる場合があります。ExtUtils::MakeMakerの場合ですと、WriteMakefileのINCHというオプションでそれを設定できます。

以下の設定は、「Rstats_lib/include」にC/C++のヘッダファイルをおいていることを想定しています。

    INC               => '-I. -I./Rstats_lib/include',
    H => ['ppport.h', glob('Rstats_lib/include/*.h')],

ヘッダファイルのインクルードパスの追加

ヘッダファイルのインクルードパスを追加するのに「INC」というオプションを使用します。デフォルトではカレントディレクトリの設定になっていますので、これに「./Rstats_lib/include」を追加しています。

    INC               => '-I. -I./Rstats_lib/include'

カレントディレクトリの設定は消さないでください。なぜなら「ppport.h」というヘッダファイルを読み込むために必要だからです。

これはコンパイラ(gccなど)に渡されるオプションになっています。ヘッダファイルの検索パスを複数指定した場合でも、配列のリファレンスで指定するのではなくって、「-I」というオプションを複数回記述することに注意してください。

ヘッダファイル名の一覧を指定

「H」というオプションには、ヘッダファイルの一覧を渡します。すべてのヘッダファイルを配列のリファレンスで指定してください。「ppport.h」とglob関数を使って「Rstats_lib/include」以下のすべてのヘッダファイルを指定しています。

    H => ['ppport.h', glob('Rstats_lib/include/*.h')],

この指定がないと、makeを実効したときの依存関係の解決が正しく行われませんので、必ず指定するようにしてください。「INC」オプションだけではも、初回のコンパイルは成功しますが、makeが依存関係を正しく理解できません。

Makefile.PLでC/C++言語のソースファイルの検索パスを追加する方法

XSを書くときに、C/C++言語のソースファイルを別のディレクトリに格納するには、どうすればよいのでしょうか。Makefile.PLをうまく記述するとそれを行うことができます。Makefile.PLのなかのWriteMakefileのオプションを次のように記述します。

    C => ['Rstats.c', glob('Rstats_lib/src/*.cpp')],
    OBJECT            => '$(O_FILES)',
    DEFINE => '-o $@'

これは、モジュール名がRstatsで、ソースファイルディレクトリが「Rstats_lib/src」の場合です。Rstats.xsというファイルが、トップ「/」に置かれていることを想定しています。ソースファイルの種類はC++のソースファイルです。

イメージがしやすいように、ディレクトリの構造と、Makefile.PLの全体のサンプルを見せます。

.
|-- Changes
|-- MANIFEST
|-- MANIFEST.SKIP
|-- Makefile.PL
|-- README.md
|-- Rstats.xs
|-- Rstats_lib
|   |-- include
|   |   `-- Rstats.h
|   `-- src
|       |-- Rstats_ElementFunc.cpp
|       |-- Rstats_Func.cpp
|       |-- Rstats_Main.cpp
|       |-- Rstats_Util.cpp
|       |-- Rstats_Vector.cpp
|       `-- Rstats_VectorFunc.cpp
|-- lib
|   |-- Rstats
|   |   |-- Class.pm
|   |   |-- Func.pm
|   |   |-- Object.pm
|   |   `-- Util.pm
|   `-- Rstats.pm
|-- ppport.h
`-- t

Makefile.PLのソースコードです。

use 5.010001;

use strict;
use warnings;
use ExtUtils::MakeMaker;

use Config;

# C++ compiler
my $cpp_compilers = {
  gcc => 'g++',
  clang => 'clang++',
  CC => 'CC'
};
my $cc = $cpp_compilers->{$Config{ccname}};
my $ld = $cc;

WriteMakefile(
    NAME                => 'Rstats',
    AUTHOR              => 'Yuki Kimoto <kimoto.yuki@gmail.com>',
    VERSION_FROM        => 'lib/Rstats.pm',
    ABSTRACT_FROM       => 'lib/Rstats.pm',
    ($ExtUtils::MakeMaker::VERSION >= 6.3002
      ? ('LICENSE'=> 'perl')
      : ()),
    PL_FILES            => {},
    META_MERGE   => {
      requires  => {perl => '5.010001'},
      resources => {
        license    => 'http://www.opensource.org/licenses/artistic-license-2.0',
        bugtracker => 'https://github.com/yuki-kimoto/Rstats/issues',
        repository => 'https://github.com/yuki-kimoto/Rstats.git'
      }
    },
    PREREQ_PM => {
        'Object::Simple' => '3.10',
        'Math::Round' => '0.06',
        'Text::UnicodeTable::Simple' => '0.09'
    },
    dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
    clean               => { FILES => 'Rstats-*' },
    CC => $cc,
    OPTIMIZE => '-O3',
    LD => $ld,
    LIBS              => [],
    DEFINE            => '',
    INC               => '-I. -I./Rstats_lib/include',
    C => ['Rstats.c', glob('Rstats_lib/src/*.cpp')],
    OBJECT            => '$(O_FILES)',
    DEFINE => '-o $@'
);

C言語ソースファイルの記述

このままでは理解しづらいと思うので、WriteMakefileのオプションの解説をします。

    C => ['Rstats.c', glob('Rstats_lib/src/*.cpp')],
    OBJECT            => '$(O_FILES)',
    DEFINE => '-o $@'

まず「C」というオプションで、コンパイルに必要なソースコードのすべてを記述します。「Rstats.c」というのは「Rstats.xs」に対応するものです。glog関数を使って「Rstats_lib/src」以下にあるcppファイルを取得しています。もしC言語だった場合はこの部分のオプションは次のようになります。

    C => ['Rstats.c', glob('Rstats_lib/src/*.c')],

内部的なことをいうと、「C」オプションで指定されたファイルはMakefileの「C_FILES」という変数に格納されます。また「C_FILES」の拡張子を「.o」に変更した「O_FILES」という変数が自動的に作成されます。

オブジェクトファイルの出力場所の変更

「DEFINE」というオプションでは、何を記述しているのでしょうか。

  DEFINE => '-o $@'

これは、オブジェクトファイルの出力場所を変更しています。「DEFINE」というオプションを使用すれば、gccの引数を追加することができます。そして、gccの「-o」オプションを使用すれば、オブジェクトファイルの出力先を変更することができます。

「$@」はなんでしょうか。これはMakefileの文法規則で、ターゲット名が自動的に代入される変数です。ターゲット名が、オブジェクトファイル名なので、このように記述すると、オブジェクトファイルが、ソースコードファイルと同じディレクトリ内に出力されることになります。

リンカのターゲット指定

最後にリンカのターゲット指定を記述する必要があります。すべてのオブジェクトファイルを一つに合体させて、「.so」という拡張子で終わるダイナミックリンクライブラリが最終的に作成されるので、リンカにオブジェクトファイル名を知らせる必要があります。

そのためには、以下のように指定します。

    OBJECT            => '$(O_FILES)',

オブジェクトファイル名の一覧は「O_FILES」という変数に入っていたのでした。「O_FILES」を展開するには「$(O_FILES)」と記述します。

そして「OBJECT」オプションは、自動的に「LDFROM」というオプションに設定されて、リンカに渡されることになります。