Perlで use
や require
を使ってモジュールを読み込む場合、通常は @INC
配列に入っているパスのどこかに、所定の形式で配置されている必要があります。
具体的には、Foo::Bar
というモジュールがあり、@INC
配列に /opt/lib/site_perl/
といったパス文字列があってそれが対象となる場合、/opt/lib/site_perl/Foo/Bar.pm
というファイルとして存在している必要があります。
最終的には @INC
をいじる必要があるのですが、現在のファイルの場所などを元にして、それをどう抽象化・簡略化するか、様々な手法があるようなのでまとめてみました。
##モジュールが存在して読み込めるかどうかをワンライナーで確認する
例えば LWP::UserAgent
モジュールが存在するかどうかを確認したい場合には以下のようにします。
$ perl -MLWP::UserAgent -e 1
存在すれば何も表示されませんが、存在しなければエラーが表示されます。
##色々な方法がある
このページを参考にしました。
いや、このページがきっかけでまとめようと思いました。ありがとうございます。
この記事、参考になるので、ここで解説されている手法を列挙してみます。
-
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 や plenv などのユーザ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
オプションのように。
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
中のパスを使わせてもらうなどといった戦略を取る必要があるかもしれません。もちろん、労を惜しまず黒魔術を使うならば問題はありません。