はじめに

この記事は 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.pmMyCMS/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_Amy_B は、最初は モジュールとしてもコマンドとしても問題なく動きます。 しかし、後から my_BlibC/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::BinlibA を指します。 この状態で 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 を紹介しました。

ここまで読んで下さりありがとうございました。