DSLはスタイルによって幾つかに分類できますが、ここではプログラミング言語指向のものに注目します。プログラミング言語指向のDSLは、言語内(internal)DSLと異語外(external)DSLに大別できます。言語内DSLとは実行時に利用される言語(ホスト言語)とDSLが同じものを、言語外DSLはホスト言語とDSLが異なるものを指します。いずれも以前から行われているアプローチであり、例えば言語内DSLはLispやSmalltalkで、言語外DSLは多くのUNIXツール(例えばawkやprocmail、PostScript)で使用されています。
マーティン・ファウラー氏は、make、Ant、Rakeの各ビルド言語をDSLのスタイルに従って次のように整理しています。
DSLとしてホスト言語を利用するメリットは、学習コストの節約と、ホスト言語の文法や機能、周辺ツールを活用できる点にあります。「ドメインに特化した言語」という観点からはデメリットともなりますが、ビルドツールのようなユーザー層がプログラマーである分野では、デメリットよりもメリットの方が大きいでしょう。
それでは、実際にRakeを使ってみましょう。Railsが導入済みであれば、Rakeもすでにインストールされています。Rakeが正常にインストールされているかどうかは、rakeコマンドを実行することで確認できます。
$ rake --version
rake, version 0.7.2
なお、ヘルプを表示したい場合は、「--help」オプションを指定します。
ここでは、Cで書かれた「Hello World」を出力するプログラムをビルドします。ソースコードの内容は省略しますが、全体の依存関係が図2のようになっているケースを想定します。
コンパイル対象のソースコードとヘッダファイル、RakeのビルドファイルであるRakefileは、すべて同じディレクトリに配置しておきます。
$ ls
greet.c greet.h main.c rakefile
helloプログラムをビルドするには、リスト1のようなRakefileが必要となります。Rubyの基本的な文法を理解していれば、Rakeの詳細を知らなくても処理内容が読み取れるのではないでしょうか。Rakefileの内容は次のとおりです。
1 require 'rake/clean'
2 CLEAN.include %w(hello *.o)
3
4 task :default => ['hello']
5
6 file 'main.o' => ['main.c', 'greet.h'] do
7 sh 'cc -c -o main.o main.c'
8 end
9
10 file 'greet.o' => ['greet.c'] do
11 sh 'cc -c -o greet.o greet.c'
12 end
13
14 file 'hello' => ['main.o', 'greet.o'] do
15 sh 'cc -o hello main.o greet.o'
16 end
1行目では、Rakeがビルトインで提供しているcleanタスクを読み込んでいます。2行目では、cleanタスクで削除対象とするファイルを配列で指定しています。ここでは最終成果物であるhelloと、*.cをコンパイルして生成するオブジェクトファイルが削除対象です。
4行目は、rakeコマンドを引数なしで起動した場合に実行されるデフォルトのタスクです。Rakeの基本的な文法は、
task タスク名 => [依存タスク] do
処理内容
end
というものです。タスク名の指定にはシンボルを使用することに注意してください。
6行目以降では、ビルドに必要な各処理をそれぞれタスクとして定義しています。「task」ではなく「file」と記述しているのは、ファイル生成に特化したタスクだからです。Rakeではこれを「ファイルタスク」と呼んでいます。ファイルタスクが通常のRakeタスクと異なる点は次の2つです。
ブロックには、ファイルタスク名で指定したファイルを生成するための処理を記述します。「sh」はRakeが提供しているメソッドであり、与えられた文字列をシステムコマンドとして評価・実行します。
なお、実際にRakeを利用する際には、タスク定義の重複個所は「ルール」と呼ばれるRakeの機能や、Rakeが提供する変数などを使ってパターン化できます。この例では説明を簡単にするため、重複個所はそのままにしてあります。
ファイルタスクと同様の位置づけのタスクとして、ほかに「ディレクトリタスク」が提供されています。名前のとおり、ディレクトリの生成に特化したタスクです。書式は、
directory "ディレクトリ名"
であり、ディレクトリ名はファイルタスクと同様に文字列で指定します。
Rakefileで定義したタスクを実行するには、Rakefileを置いたディレクトリでrakeコマンドを実行します。リスト1のRakefileではdefaultタスクを定義しているので、引数なしで起動するとhelloのファイルタスクが実行されます。
$ rake
(in /path/to/rakefile/directory)
cc -c -o main.o main.c
cc -c -o greet.o greet.c cc -o hello main.o greet.o
生成されたファイル群を確認し、helloが実行できることを確認します。
$ ls
greet.c greet.h greet.o hello main.c main.o rakefile
$ ./hello
Hello, World
再度rakeコマンドを実行しても、ファイルに変更がなければ何も実行されません。そこで、greet.oを削除してからrakeを実行してみると、
$ rm greet.o
$ rake
(in /path/to/rakefile/directory)
cc -c -o greet.o greet.c
cc -o hello main.o greet.o
のようにgreet.oが再生成され、greet.oに依存しているhelloが再コンパイルされます。これによって、Rakefileに記述した成果物の時系列的な依存関係がRakeによって解決されていることが分かります。
cleanタスクを実行すると、削除対象としたファイルが削除されます。
$ rake clean
(in /path/to/rakefile/directory)
rm -r hello
rm -r main.o
rm -r greet.o
$ ls
greet.c greet.h main.c rakefile
すでに紹介したとおり、言語内DSLはホスト言語そのものを利用してDSLを構築します。したがって、言語内DSLではホスト言語の機能をフル活用できます。Rubyで構築された言語内DSLであれば、Rubyの制御構造の利用はもちろん、独自にクラスやメソッドを定義できますし、サードパーティーのRubyライブラリを利用することも可能です。次回はこのあたりを解説します。
本記事は、オープンソースマガジン2006年3月号「Ruby on Rails 1.0の世界」を再構成したものです。
Copyright © ITmedia, Inc. All Rights Reserved.