Perlでモジュールにパスを通す方法


Perlで `use` や `require` を使ってモジュールを読み込む場合、通常は `@INC` 配列に入っているパスのどこかに、所定の形式で配置されている必要があります。

具体的には、`Foo::Bar` というモジュールがあり、`@INC` 配列に `/opt/lib/site_perl/` といったパス文字列があってそれが対象となる場合、`/opt/lib/site_perl/Foo/Bar.pm` というファイルとして存在している必要があります。

最終的には `@INC` をいじる必要があるのですが、現在のファイルの場所などを元にして、それをどう抽象化・簡略化するか、様々な手法があるようなのでまとめてみました。

##モジュールが存在して読み込めるかどうかをワンライナーで確認する

例えば `LWP::UserAgent` モジュールが存在するかどうかを確認したい場合には以下のようにします。

```Shell
$ perl -MLWP::UserAgent -e 1
```

存在すれば何も表示されませんが、存在しなければエラーが表示されます。

##色々な方法がある

このページを参考にしました。

* [@INC にみる Perl のやりかたがいっぱい - JPerl Advent Calendar 2010 Casual Track](http://perl-users.jp/articles/advent-calendar/2010/casual/18)

いや、このページがきっかけでまとめようと思いました。ありがとうございます。

この記事、参考になるので、ここで解説されている手法を列挙してみます。

* `use lib` を使って相対パスで指定 (コアモジュールだけでできる)
* `use lib` と `FindBin` を組み合わせた指定 (コアモジュールだけでできる)
* `FindBin::libs` を使った指定
* `Project::Libs` を使った指定
* `__FILE__`、`File::Basename`、`File::Spec` による合わせ技
* ワンライナーであれば `-I` オプションを使った指定
* `@INC` をコンパイル時 (BEGIN時) に直にいじる

色々なスクリプトを見ると、コアモジュールだけで出来る `FindBin` による方法が多い印象を覚えます。もちろん、最初から `@INC` のパスにモジュールを配置したほうが良いことに変わりはないのですが。

ただ、どれも分かりづらいと言えば分かりづらいような気もします。FindBinは古くからのコアモジュールだから重宝されているだけで、イディオムとしてこれらを覚えるまでは、初心者からは何をしているのかイマイチ理解できないコードができあがってしまうでしょう。

##PSGIファイルとPlackとモジュール探索

上記の記事は2010年の記事なので、現状に即していない記述があります。

`plackup` 時に FindBin が使えないというバグは FindBin 側が修正をしているようで、2014年のPerlでは特に問題はありません。

`plackup` の仕組みは、引数の `*.psgi` ファイルの中身を起動時に一度だけ文字列evalして、その結果としてのサブルーチンリファレンスをキャッシュして処理を行うというものです。昔のFindBinは、この「特定のファイルの中で文字列evalされた中で使われると現在のディレクトリ位置を判別できない」というバグがあったようですが、それは2014年のPerlでは解決されています。

このFindBinの昔のハマりポイントは、mod_perlのCGI高速実行環境(`Apache::Registry`、`ModPerl::RegistryPrefork`)でも同様の問題がありましたが、今のFindBinでは同様に問題が無いはずです。

##その他の手法

その他、私が取っている手法を列挙していきます。

### 環境変数 PERL5LIB を使う

実行ファイルのパスを通す `PATH` 環境変数のように、Perlにもモジュールにパスを通す `PERL5LIB` という環境変数があります。これは`PATH` 環境変数と同様に使うことができます。

起動時に読み込まれる設定ファイル、例えば `~/.bash_profile` などに以下の記述を書いておけば、コロン区切りで列挙したパス群が `@INC` に入っているのと同様の効果があります。

    export PERL5LIB=/home/xtetsuji/lib:/var/lib/site_perl

ただ、この方法は環境変数を伝えづらい cron などで混乱が発生する可能性があります。cronのような場合は `perl` コマンドや `plackup` コマンドの `-I` オプションのほうが良かったりするかもしれません。場合に応じて使い分けて下さい。

### 既に `@INC` に入っているパスを利用する

プログラム中から `@INC` をいじるのはやめて、既にある `@INC` に合わせるやり方です。

Debian wheezy のシステムPerl (最初からシステムに入っていたり、ディストリビューション公式のパッケージとして提供されているPerl) では以下のようなコマンドを打つと、以下のような出力が返ってきます


    $ perl -E 'say for @INC'
    /etc/perl
    /usr/local/lib/perl/5.14.2
    /usr/local/share/perl/5.14.2
    /usr/lib/perl5
    /usr/share/perl5
    /usr/lib/perl/5.14
    /usr/share/perl/5.14
    /usr/local/lib/site_perl
    .

現在のディレクトリといった相対パスを含めた9個のパスが出てきます。その他のディストリビューションのPerlでも同様の出力があると思います。

実際に最初から存在するディレクトリはこのうちのいくつかのみです。

    $ perl -E '-d and say for @INC'
    /etc/perl
    /usr/lib/perl5
    /usr/share/perl5
    /usr/lib/perl/5.14
    /usr/share/perl/5.14
    .


私はDebianユーザなのですが、よく`/usr/local/lib/site_perl` を作って使います。

    $ sudo mkdir -vp /usr/local/lib/site_perl
    $ sudo chown www-data: /usr/local/lib/site_perl
    $ sudo -u www-data mkdir /usr/local/lib/site_perl/Foo
    $ sudo -u www-data cp /path/to/Bar.pm /usr/local/lib/site_perl/Foo/

root所有でも良いのですが、`*.pm` ファイルのパーミッションは特に誰でもいいし、root に頻繁になることはあまり個人的に好ましくないので、Debian (やUbuntu) でWebサーバをインストールしたら作られる www-data ユーザ (id=33) 所有にしています。

また、プロジェクトディレクトリとプロジェクトユーザが決まっている場合には、 **そのシンボリックリンクを作る** のが早いと思います。

    $ sudo ln -s /usr/local/myproject/lib/MyProject /usr/local/lib/site_perl/MyProject

実際にモジュールをロードするPerlプログラムはモジュールの読み込み権限さえあれば良いので、これで特に問題はありません。

ただ、このシンボリックリンクによる解説が、検索などをしてもほとんど見られないところを見ると、パフォーマンス上の不利な点や、その他の事情があるのかもしれません。

### ユーザPerlの作成時に `@INC` に好きなディレクトリを入れる

最近では [Perlbrew](http://perlbrew.pl/) や [plenv](http://blog.papix.net/entry/2013/06/04/081554) などのユーザPerl (システムPerlの環境を汚さずに、自分の手元で独自のPerl環境をコンパイルして構築できるもの) を使ってPerl環境を作る場合が多いのではないでしょうか。RubyやPythonでも同様でしょう。

plenv では、Perlのインストール・コンパイル時にコンパイルオプションを渡すことができます。この時 **otherlibdirs というオプション** を使うことで `@INC` に自分の好きなディレクトリが入った状態のユーザPerlを作ることができます。

下記のように指定することでコロン区切りで 3つのディレクトリを `@INC` に入れた Perl5.18.2 をコンパイルしています。

    $ plenv install 5.18.2 -Dusethreads -Dotherlibdirs=/home/xtetsuji/lib:/opt/perl5/lib:/var/www/site_perl/lib --as 5.18 

引数 `-Dotherlibdirs=` がポイントです。

通常のコンパイル時にはスレッドが有効にならないのですが、`-Dusethreads` を指定することでスレッドが使えるようになるというところで、コンパイルオプションが取り沙汰されることがあります。ただ、Perlのスレッド(ithreads)は不安定で使いものにならないという、もっぱらの評判なので、もし非同期的処理をしたい場合にはforkによる方法や`AnyEvent`などのモジュールの採用を検討したほうがよいでしょう。

    $ perl -E 'say for @INC'
    /home/xtetsuji/.plenv/versions/5.18/lib/perl5/site_perl/5.18.2/i686-linux-thread-multi
    /home/xtetsuji/.plenv/versions/5.18/lib/perl5/site_perl/5.18.2
    /home/xtetsuji/.plenv/versions/5.18/lib/perl5/5.18.2/i686-linux-thread-multi
    /home/xtetsuji/.plenv/versions/5.18/lib/perl5/5.18.2
    /home/xtetsuji/lib
    /opt/perl5/lib
    /var/www/site-perl/lib
    .

###mod_perlの `PerlSwitches` オプションはあまりオススメではない

mod_perl では `PerlSwitches` というオプションがあって、Apacheの設定ファイルで `@INC` にパスを加えることができます。そうperlコマンドの `-I` オプションのように。

```Apache
    PerlSwitches -I/home/xtetsuji/lib
```

その他にも、Apacheの設定ファイルの段階でApacheに読み込まれたmod_perlに対して初期コードを実行させる方法はいくつもあるので、それを使って `@INC` を操作する方法はいくつもあるのですが、これはあまりオススメできる方法ではありません。なぜならば **コマンドラインスクリプトで環境を合わせるのが手間だから** です。

この意見には賛否両論あるとは思うのですが、システムの全てのPerlがmod_perlであることはまれだと思うので、この「`@INC` パスをApacheの設定ファイル上で管理する」という方法はあまり好ましくはないと私は考えます。

ただ、`@INC` に絶対パスを挿入するようなPerlプログラムを書いておいて、それをmod_perlとコマンドラインツールの両方で読むという方法は、良い方法かもしれません。`@INC` はどこで定義してもグローバル(具体的にはどこで宣言・参照しても `main` パッケージの `@INC` を指しています) なので、この方法が通用します。

mod_perl の場合、Apacheからmod_perl、そしてPerlも全てビルドするか、それともシステム・ディストリビューションに用意されたものをそのまま使うか、戦略がわかれると思います。前者の場合には `otherlibdirs` オプションを使うなどして対処し、後者の場合は既にある `@INC` 中のパスを使わせてもらうなどといった戦略を取る必要があるかもしれません。もちろん、労を惜しまず黒魔術を使うならば問題はありません。