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 にパスが通っていなかったため、モジュールを見つけられなかったのです。
- /plugins ディレクトリの中を調べる (opendir~readdir~closedir)
- 拡張子が .pl のファイルであれば、Perl で書かれたプラグインとして読み込む
- 拡張子が .pl 以外のファイルは無視される
- サブディレクトリの中に config.yaml があれば、YAML で書かれたプラグインとして読み込む
- サブディレクトリの中を調べる (opendir~readdir~closedir)
- 拡張子が .pl のファイルであれば、Perl 通常ので書かれたプラグインとして読み込む
- 拡張子が .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 で指定した順番になる
まぁ、判ってしまえば「なーんだ」なのですが、こんなディレクティブがあることに気が付くまでに無駄な時間を使ったので。
ぴろり上西
2011年05月25日
Movable Type 5 ユーザーコミュニティ:MTQ で次のような質問がありました。
プラグインのセットを作りたいのですが
プラグインの名前でyamlを作成してみたりしたのですが、セットとして認識されませんでした。やはり、下記のように .pl で書き直さないとだめでしょうか?
この質問についての答えは Yes です。/plugins の下の一つのサブディレクトリ内に複数のプラグインを配置するには YAML ではなく Perl スクリプトで書かれたプラグインを置かねばなりません。これに関係して、少し調べた部分をまとめておきます。
プラグインを読み込んでいるところのソースコード(/lib/MT.pm, _init_plugins_core 関数、MT5.1 まで変更されていないみたい)を見ると、おおむね以下のような手続きになっていることがわかります。
- /plugins ディレクトリの中を調べる (opendir~readdir~closedir)
- 拡張子が .pl のファイルであれば、Perl で書かれたプラグインとして読み込む
- 拡張子が .pl 以外のファイルは無視される
- サブディレクトリの中に config.yaml があれば、YAML で書かれたプラグインとして読み込む
- サブディレクトリの中を調べる (opendir~readdir~closedir)
- 拡張子が .pl のファイルであれば、Perl 通常ので書かれたプラグインとして読み込む
- 拡張子が .pl 以外のファイルは無視される
という感じです。YAML によるプラグイン定義は config.yaml というファイル名で固定されており、サブディレクトリ内に config.yaml が存在した時点でそれが読み込まれ、次のサブディレクトリに処理が移ります。ですので MTQ での質問のように、一つのディレクトリに複数のプラグインを配置したい場合には、拡張子が .pl の Perl スクリプトとして配置するしかありません。
ところで、話しは逸れますが、MT4 以降、プラグインは従来の Perl スクリプト形式に加えて YAML 形式が採用されました。シックスアパートでは YAML によるプラグイン作成を推奨しているようですが、個人的には従来の Perl スクリプトによる作成方法が好ましいように感じています。理由は以下の通りです。
1. Perl スクリプトで書かれたプラグインは、一つのサブディレクトリ内に一つ以上配置できますが、config.yaml による記述では、一つのサブディレクトリにつき一つのプラグインしか配置できません。そのため、それぞれ関連する機能を提供しあう複数のプラグインの集合という管理ができません。
プラグインによって提供されたある一連の機能について、それらをまとめて削除したい場合、Perl スクリプトで提供されるプラグインは、そのサブディレクトリ一つを削除すれば問題ありませんが、YAML によって提供されたプラグインは、どれらのサブディレクトリを削除すればよいのか人間が知っている必要があります。
MTCMS に搭載されているサイトマップダッシュボード機能はプラグインとして提供されている機能の一つで、ダッシュボード上に某 Windows 風なサイトマップを表示することができます。このダッシュボード上から、ダイアログウィンドゥを開いて、いきなりサブカテゴリやサブフォルダを作成したり、フォルダごとにアセットをフィルタリングするなどの機能が提供されていますが、これらの機能はこのダッシュボードの拡張として存在しています。故に、これらの機能はそれぞれが .pl ファイルとして一つのディレクトリに配置されています。もしサイトマップダッシュボードのプラグイン自体が不要になれば、そのディレクトリ一つを削除すればよいのです。
2. プラグインの下位互換性を考えた場合、例えば、簡単なテンプレートタグを拡張する程度のプラグインであれば、config.yaml を使わずに、更に registry さえも使わずに、MT::Template::Context->add_tag と MT3 時代の方法を使い続けることで、MT3 ユーザさえもその恩恵に預けることができます。
また、registry を利用する場合であっても、例えば、メニュー項目を追加するプラグインでは MT4 と MT5 で registry のキー名が異なる場合、プログラム的に MT のバージョンを判別して処理を分岐する必要があります。
3. config.yaml による記述では、stuff な(単純なくだらない)プラグイン一つのために複数のファイルが必要になります。つまり、プラグインの定義が書かれた config.yaml ファイルと、処理の実体が書かれた Perl のモジュールファイル(*.pm)です。Perl スクリプトによる形式であれば、一つのファイル内にプラグインの登録処理(MT->add_plugin)と処理の実体(sub hogehoge {...})を合わせて書くことができます。
config.yaml 内でもブロックスタイルを用いて処理の実体(Perl スクリプト)を書くことができますが、1) Perl による Syntax Check を行えない、2) エディタの Syntax Hilight 機能に預かれない、3) 一つのファイル中に YAML、Perl という言語仕様が混在することのメンテナンス性の低下、などの問題があります。
4. config.yaml によるプラグイン定義において、その YAML 内に Syntax Error があった場合、そのエラーが判りづらいというのがあります。インデントするつもりで Tab を打ったらホワイトスペース 4 つじゃなくて \t が入っていて構文エラーですとか、ホワイトスペースが一つ足りずに構文エラーですとか → 参考:MT plugin の config.yaml には注意
...という理由です。YAML はスタートダッシュが秀逸で、サクサクッとプラグインを作るのには適していると思います。ただ、長期に渡って複数の開発者で改善改修を行い続ける必要があったり、プラグインがどんどん肥大化、高機能化していくケースを考えると、最初が多少面倒な Perl スクリプトであっても、結果的には保守性や可読性を維持できる方がメリットがあると感じています。
次回は、複数のプラグインがどのような順番でシステムに読み込まれるのかを解説したいと思います(続くのかよ)
onagatani
2011年05月18日
北海道からこんにちわ。onagataniです。 さて以前にIndexRebuilderというプラグインを書いたのですが、少しバージョンアップしました。
-
Entry・WebPageも定期再構築の対象にしました
-
タスクの実行周期を15分にしました(さすがに1時間に1度はないかなと。。)
ということで、
是非新バージョンをお試しください。
使い方は基本的に変更ありませんが、WebPage or EntryのIDを入力できる用になっています。再構築はrun-periodic-tasksが15分に1度行ってくれます。
タスクの実行周期を修正したい場合もあるかと思いますが、現状IndexRebuilder自身では変更できません。MTに登録されている全てのタスクの周期を修正するプラグインも社内にありますので、公開されましたらこちらで設定変更頂ければと思います。こちらは別途公開されると思います(たぶん)。
ダウンロード
IndexRebuilder0.2
インストール
プラグインディレクトリにコピーしてください。
その他いろいろ
-
MT5で動作確認済みです
-
このプラグインはブログウェアです。無償でご利用頂けますが、感想、要望などをあなたのブログで記事として紹介頂けると幸いです。
-
個人利用または商用利用に関わらず無償でご利用いただけます。
-
無保証ですので、十分にテストしてから運用してください。
-
本プラグインをご利用される方は必ずプラグインご利用の際の注意事項をお読みください。
onagatani
2011年05月18日
なぜか一度書いた記事が全部消えて同じ記事を2回書いているonagataniです。 さて、皆さんMTでの開発で最終的な本番化をおこなう際に公開ドメイン名やパスが違うために困った事はありませんか?管理画面でもある程度は一括置換できますが記事内の画像のリンクなどで漏れがでてしまうかと思います。
ということで、直接データベースの全テーブル・全カラム(varchar型、text型のみ)に対して置換処理を行なうSQLReplaceプラグインを紹介します。
本プラグインを導入する事により、MovableTypeのデータ全てに対して置換処理を行なうことが可能になります(導入前にかならずデータベースのバックアップを取得して下さい。動作保証はありません)。
使い方はシステムのプラグインセッティングにて「置換前文字列,置換後の文字列」と書くだけです。あとはrun-periodic-tasksが置換処理を実行してくれます。複数置換したい文字列がある場合は、プラグインセッティングを一度初期化して再度置換文字列を保存して下さい。*なお置換処理は初期化しない限り一度しか実行さませんのでご安心下さい。
ダウンロード
SQLreplace0.1
インストール
プラグインディレクトリにコピーしてください。
その他いろいろ
-
MT5で動作確認済みです
-
このプラグインはブログウェアです。無償でご利用頂けますが、感想、要望などをあなたのブログで記事として紹介頂けると幸いです。
-
個人利用または商用利用に関わらず無償でご利用いただけます。
-
無保証ですので、十分にテストしてから運用してください。
-
本プラグインをご利用される方は必ずプラグインご利用の際の注意事項をお読みください。
谷内
2011年05月18日
谷内@帯広です。
MTプラグインで、設定を yaml に書くことがあるかと思いますが、YAML::Syck や YAML::Tiny といった YAML パーサーでエラーになるようなファイルを config.yaml として作成すると、エラーでMTの管理画面が再起不能になります。
本来はプラグインで致命的エラーがあっても、MTがトラップしてシステムログにエラーが出力されるという仕様になっているのですが、このエラーだけは何故だか回避できないようです。
yaml ファイルは数年前(もう5年以上前でしょうか?)に perl 界ではトレンドになっていましたが、ここ1、2年は普通に pl ファイルとして設定を書くのが主流になっています。MTもperl界の他の製品にならって、yaml を使うのを止めてみてもいいかもしれませんね。