エンジニアブログ

エンジニアブログ
MT技術情報

プラグイン周りについて ~ プラグインのロード順

ぴろり上西 2011年05月27日

 前回の記事に続いて、今回はプラグインの読み込まれる順番について情報展開しておきたいと思います。プラグインを利用される際に、プラグインが読み込まれる順番というのは普段は全く意識することはありませんし、それを意識しなくても問題が起こることはありません。しかし、複数のプラグインから構成されるプラグインセットのようなものを作ろうとすると、プラグインのロードされる順番が問題になることがあります。そういう意味では、この問題はプラグイン開発者が留意するべき問題で、一般にプラグインの利用者が気にするところではありませんが。

背景

 どういう時に問題になるのか具体的な例を挙げてみます。Movable Type を利用したあるプロジェクトがあり、そこでは必要な機能をプラグインとして実装することになりました。複数の開発メンバーが、夫々に必要な機能をプラグインとして作成し、plugins フォルダ以下に配置していきます。途中、共通する処理や便利関数などをライブラリとして使えるよう、ProjectCommon というプラグインを作って、その lib ディレクトリ内にプロジェクト独自に作った共通モジュールファイルを置くようにしました。これは、MT の既存ライブラリファイル群を汚さずに独自の共通モジュールを追加する良いアイディアです。そしてディレクトリ構成は以下のようになりました。

+- /plugins
   +- ProjectCommon
      +- ProjectCommon.pl
      +- lib
         +- ProjectCommon
            +- Foo.pm
            +- Bar.pm
   +- HogePlugin
      +- HogePlugin.pl
   +- FugaPlugin
      +- FugaPlugin.pl
   :

 /plugins/ProjectCommon/lib にはパスが通っていたので、HogePlugin.pl などからは、以下のようにして共通関数を呼び出すことができました。

use ProjectCommon::Foo;
my $ret = ProjectCommon::Foo::do_hogehoge ();

問題発生とその原因

 一見、うまく機能していた共通モジュールを提供するプラグインですが、この後、サーバによって ProjectCommon のモジュールが見つからないという問題に遭遇します。開発サーバでは問題なかったものが、本番サーバ上ではモジュールファイルが見つからず、Perl がコンパイルエラーになるというものです。ファイル内容、ファイル構成など、あらゆる物理的要素をチェックしても、開発サーバと本番サーバでは差異がありません。何が原因でしょう?

 ようやく本題です。その理由は、プラグインが読み込まれる順番にありました。つまり、HogePlugin.pl が共通モジュールを利用できるためには、HogePlugin プラグインに先立って ProjectCommon プラグインが読み込まれている必要があるということです。なぜなら、プラグインが読み込まれた段階で、そのプラグインのディレクトリ以下にある lib と extlib に Perl モジュールのパス(@INC)が通されるのですが、開発サーバでは、ProjectCommon プラグインがロードされ、/plugins/ProjectCommon/lib にパスが通った後に HogePlugin がロードされていたためこの問題が顕在化しませんでした。しかし、本番サーバでは、ProjectCommon プラグインがロードされるより前に、ProjectCommon のライブラリファイルを必要とする他のプラグインがロードされていて、/plugins/ProjectCommon/lib にパスが通っていなかったため、モジュールを見つけられなかったのです。

  1. /plugins ディレクトリの中を調べる (opendir~readdir~closedir)
    1. 拡張子が .pl のファイルであれば、Perl で書かれたプラグインとして読み込む
    2. 拡張子が .pl 以外のファイルは無視される
    3. サブディレクトリの中に config.yaml があれば、YAML で書かれたプラグインとして読み込む
    4. サブディレクトリの中を調べる (opendir~readdir~closedir)
      1. 拡張子が .pl のファイルであれば、Perl 通常ので書かれたプラグインとして読み込む
      2. 拡張子が .pl 以外のファイルは無視される

これは、MT がプラグインを読み込むところの概要手順ですが、ここで使われている readdir という Perl 関数がカギになります。readdir 関数は、先に opendir 関数で指定されたディレクトリ内に存在するファイル名やディレクトリ名のリストを取得するための関数なのですが、ここで取得されるリストは名前順でも日付順でもなんでもないという仕様なのです。ディスクドライブ上の論理環境などが異なると、リストに含まれる内容は同じでも、リストの順番が異なります。つまり、HogePlugin などより先に必ず ProjectCommon がロードされることを保証できないということになります。これは困った。

解決方法

 これを回避するには、PluginPath ディレクティブを利用します。このディレクティブを用いると、プラグインを配置したディレクトリを変更することができます。また、複数のディレクトリにプラグインを分散して配置することができます。これを利用できないでしょうか?

PluginPath ./plugins_first
PluginPath ./plugins_second
PluginPath ./plugins

 上記のように書いた場合、MTは plugins_first → plugins_second → plugins の順にプラグインを探すことがソースコード上、保証されます(/lib/MT.pm、init_plugins 関数)。各ディレクトリ内においてプラグインの読み込まれる順は先述の通り不定ですが、これを用いるとプラグインの読み込まれる順番をある程度制御できるというわけです。つまり、他のプラグインから共通して利用されるモジュールやコンポーネントを提供するプラグインは plugins_first ディレクトリに配置し、それらを利用する側のプラグインは plugins_second や plugins に配置する、という方法です。

まとめ

  • 一つのプラグイン ディレクトリ内において、プラグインの読み込まれる順番は環境に依存し、それを制御することはできない
  • PluginPath ディレクティブで複数のプラグイン ディレクトリを指定することができ、ディレクトリの走査される順番は PluginPath で指定した順番になる

 まぁ、判ってしまえば「なーんだ」なのですが、こんなディレクティブがあることに気が付くまでに無駄な時間を使ったので。