2016-09-06T12:05:24+09:00

AppleScript アプレット/ドロップレットをクラッシュさせるバグと ModuleLoader

AppleScript のアプレット/ドロップレットをクラッシュさせるバグを発見しましたので、報告します。OS X 10.11 と 10.10 で再現することを確認しました。

再現のためには、まず、適当なアプレットを用意してください。「Applet.app」としましょう。

そして、スクリプトエディタで、次の AppleScript を実行してください。

use framework "Foundation"
activate application "Applet"

このコードは、だたアプレットをアクティベートしようとしているだけです。このスクリプトを実行すると、アプレット「Applet.app」は起動してすぐさまクラッシュします。

スクリプトエディタの方には、次のようなエラーが発生してスクリプトの実行が中断します。

このエラーの再現条件は次の3つです。

一つ目は、use framework 文を使っていること。どういうわけか、use framework を外せば、上記のスクリプトはエラーが発生しません。

二つ目は、システムで定義されたコマンドを送っていること。上の例では、activate コマンドを送っていたけど、beep などでもエラーが発生する。アプレットで定義されたユーザー定義ハンドラを呼び出す分にはエラーは発生しないようです。

三つ目は、tell 文を使わないでメッセージを送っていること。例えば、次のように activate コマンドを tell ブロックの中に入れてしまうと、エラーは発生しません。

use framework "Foundation"
tell application "Applet"
activate
end tell

アプレット/ドロップレット以外のアプリケーションではこのようなエラーは発生しないので、AppleScript アプレット/ドロップレットにバグが存在していると推測できます。

もし、不可解なアプレットのエラーに悩まされている方のご参考になれば幸いです。

当面の回避策として、tell ブロックを使うのがいいのではないかしら。送るコマンドをかえるとか、use framework を外すとかは本末転倒でしょうから。

さて、なんでこのような AppleScript アプレット/ドロップレットのバグに気づいたかというと、次のような、ModuleLoader を使ったスクリプトでエラーが発生するというご報告をいただいたことがきっかけです。このスクリプトでは、run ハンドラの実行時にアプレット「 my_loader 」があるフォルダからライブラリ「 my_module 」をロードすることを意図しています。このスクリプトは前述のバグによって、アプレット「 my_loader 」に「 module loader 」コマンドを送っているところでエラーが発生します。

use framework "Foundation"
use scripting additions
property my_module : module

on run
boot (module loader of application (get "my_loader")) for me -->ここでエラー
end run

ModuleLoader に不慣れな人は、アプレット「 my_loader 」なるが何の役になっているのか不思議に思うかもしれないので、ModuleLoader の機能について少々解説します。

ModuleLoader はライブラリの置き場所を追加する手段をいろいろ取り揃えています。OS X 10.9 以降で導入されている AppleScript Libraries の場合は、基本的には「Script Libraries」フォルダだけですね。OS X 10.11 から環境変数「OSA_LIBRARY_PATH」で任意のフォルダを設定できるようになったみたいだけど、スクリプトエディタ上やアプレット/ドロップレットとして実行する AppleScript から使いやすい機能じゃないですね。ターミナルで osascript コマンドと一緒に使う際には便利でしょうか。

一方で、ModuleLoader は3種類の方法で、ライブラリの読み込み先となるフォルダをAppleScript 側から自由に設定できます。

  1. スクリプティング機能追加で提供されている 「 set additional module paths to 」コマンド
    • システム全体で有効なフォルダを追加できます。
  2. loader スクリプトで提供されている、「 set_additional_paths 」「 prepend_path 」コマンド
    • ライブラリをスクリプトだけで有効なライブラリの読み込み先を追加できます。
  3. Local Loader アプレット
    • アプレットが置かれている場所からライブラリを読み込みます。

1番目と2番目はパス文字列でフォルダを指定します。しかし、三つ目の Local Loader アプレットはパス文字列に依存しないでライブラリの置き場所を指定できる ModuleLoader の特徴的な機能です。メインのスクリプトからはパス文字列ではなく、アプレットの名前を指定してそのアプレットからライブラリの読み込み先の情報を含んだ loader スクリプトをもらいます。このようなことをすることによって、ライブラリの置き場所を自由に変更できるようになります。環境依存のコードをスクリプトに書かなくても済むようになるので、クールな方法だと僕は思っているのだけど。

上のコードのように、run ハンドラの実行の度に Local Loader アプレットが起動するのはうっとうしいですから、基本的には boot コマンドは property の定義部で実行して、コンパイル時のライブラリのロードを想定しています。上のコードは、ライブラリのテストのための一時的な措置と思われます。

僕は、run ハンドラ内で local loader アプレットを起動したことがないし、use framework "Fondation" もあまりしないので、AppleScript アプレットのバグに遭遇しませんでした。自分以上に、ModuleLoader を使いこなしているユーザーがいることに感激しました。

さて、当面の回避策は、前述のようにアプリケーションへのコマンドの送信を tell ブロック内に収めてしまうのが良いでしょう。

use framework "Foundation"
use scripting additions
property my_module : module

on run
tell application (get "my_loader")
boot (module loader) for me
end tell
end run