はじめに
この記事は Perl Advent Calendar 2021 の 2日目の記事です。
昨日の記事 モジュール兼コマンド(Modulino - モジュリーノ)再考 では モジュールに CLI コマンドとしての機能を兼ねさせることの メリットと、そのためのコードの書き方について解説しました。
この記事では Perl で Modulino を開発の中心に据えた時に遠からず直面する、 ライブラリ・ロードパスの設定の問題と、その解決策としての hkoba 作 File::AddInc について解説します。
Modulino と @INC 問題
Modulino を本格的に使い始めた時に問題となるのが、モジュールのロードパス @INC
をどう設定するか、です。
例えばある Modulino MyCMS.pm
が同じディレクトリの別の Modulino MyConfig.pm
を使っているとします。この場合、 MyCMS.pm
は FindBin と lib を使って
@INC
の調整を行う必要があります。
#!/usr/bin/env perl
package MyCMS;
use strict;
use warnings;
# ↓この2行が無いと、このディレクトリ以下の他のモジュールを use 出来ない
use FindBin;
use lib $FindBin::Bin;
use MyConfig;
use MyCMS::DB;
unless (caller) {
print "This is ", __PACKAGE__, "\n";
}
1;
@INC
の調整を行わなかった場合、(お馴染みの)次のようなエラーが出るでしょう。
% ./MyCMS.pm
Can't locate MyConfig.pm in @INC (you may need to install the MyConfig module)...
同様に MyCMS/DB.pm
も MyCMS/Util.pm
を読み込むために、
以下のような @INC
調整の宣言が必要です。
use FindBin;
use lib "$FindBin::Bin/../";
Modulino に FindBin を使う限界
もし Modulino とそれが使う他のモジュールが同一のディレクトリツリー(もしくはサイトのデフォルトライブラリパス)に存在する場合は、FindBin だけでも対応は可能です。
しかし、ライブラリディレクトリが複数存在する状況では、
FindBin だけでは全ての Modulino の
@INC
を矛盾なく調整しきれない場合があります。
以下に具体例を挙げます。
% tree
.
├── libA
│ └── my_A.pm
└── subproject
├── libB
│ └── my_B.pm
└── libC
└── my_C.pm <-- これが後から加わった
最初に toplevel のプロジェクトのライブラリディレクトリ libA
と、
subproject のライブラリディレクトリ libB
があるとします。
2つの modulino, my_A
と my_B
は、最初は
モジュールとしてもコマンドとしても問題なく動きます。
しかし、後から my_B
で libC/my_C.pm
を use した途端に、
最初の my_A
が、ロードも実行も失敗するようになります。
my_A.pm
#!/usr/bin/env perl
package my_A;
use FindBin;
use lib "$FindBin::Bin/../subproject/libB";
use my_B;
1;
my_B.pm
#!/usr/bin/env perl
package my_B;
use FindBin;
use lib "$FindBin::Bin/../libC";
# ↓これを加えた途端に、A のロードが失敗するようになる
# use my_C;
1;
原因
この問題の原因は、FindBin が $0
の値に依存しているからです。
つまり、 my_A.pm
を起動した時、$FindBin::Bin
は libA
を指します。
この状態で my_B.pm
の中で
use lib "$FindBin::Bin/../libC";
と宣言しても、足されるパスは ./libC
となり、期待した subproject/libC
にはならないのです。
File::AddInc - __FILE__
ベースの FindBin + lib
前節で述べた FindBin の問題を抜本的に解決するには、
ライブラリパスを追加する時に、 $0
からではなく、
Perl の現在のファイル名 __FILE__
に基づいて
ライブラリパスを導出する方法が考えられます。
この考えに基づき hkoba が開発したのが File::AddIncです。これを使って FindBin + lib を 書き換えると以下のようになります。
use FindBin;
use lib $FindBin::Bin;
# ↓↓
use File::AddInc;
cpm などが作る local/perl5
を直接サポートするオプションもあります。
use FindBin;
use lib $FindBin::Bin, "$FindBin::Bin/../local/perl5";
# ↓↓
use File::AddInc -local_lib;
前節で取り上げた FindBin で問題が出るケースについても、
より安全な $FindBin::Bin
の代用となる変数を定義する機能が利用可能です。
use FindBin;
use lib "$FindBin::Bin/../subproject/libB";
# ↓↓
use File::AddInc qw($libdir);
use lib "$libdir/../subproject/libB";
まとめ
Modulino における @INC
調整の必要を解説し、
問題の解決策として File::AddInc
を紹介しました。
ここまで読んで下さりありがとうございました。