はじめに
C言語/C++でコンパイルするときは、makeするのが当たり前だと思っていた昨今、rakeなるビルドツールがあるとのことで、調査してみた。
まずは、世の中の人気度。。。。makefileの5%にも満たないシェア(2013年現在)
環境
- Cygwin
- Ruby 1.9.3
- Rake version 10.0.4
目次
rakeとmakeの比較とメリット
rakeとは、makefileをrubyの文法で記述するためのツール。もう少し正当にいうと、繰り返し使うようなスクリプトを「タスク」にまとめて、必要に応じて実行できるツール。
makeとrakeを比較すると、
- makeはmakefileを作ってmakeする。
- rakeはrakefileを作ってrakeする。
なんだか偽物のミッキーマウスみたい(-_-;)しかし、makeに対するrakeのメリットは以下。
-
記述にRubyが使える
汎用的なRubyの文法をそのまま使ってビルド手順がかける。なのでmakeのような独特な言語を覚えて(しかも他では使えない)使う必要はない。
-
美しく記述できる
Ruby自体が見やすい記法を心がけて開発されているので、記述がわかりやすい。
Let’s インストール!━⊂( ・∀・)彡
> gem install rake
はじめてのrake
まずは、hogehoge.c hogehoge.h uhauha.c uhauha.h をgccでコンパイルして、hogehoge.exeを作成して実行する、というものをやってみる。
ディレクトリ構成
% tree
.
├── include
│ ├── hogehoge.h
│ └── uhauha.h
└── src
├── hogehoge.c
└── uhauha.c
コマンドライン
% gcc -Iinclude src/uhauha.c src/hogehoge.c -o hogehoge.exe && ./hogehoge.exe
makefile
C_COMPILER = gcc
TARGET=hogehoge.exe
#Source FIles
SRCS =src/hogehoge.c
SRCS +=src/uhauha.c
#Include Files
INC_DIR =-Iinclude
all: target
target:
$(C_COMPILER) $(INC_DIR) $(SRCS) -o $(TARGET)
./$(TARGET)
clean:
rm $(TARGET)
rakefile
C_COMPILER = "gcc"
TARGET="hogehoge.exe"
INC_DIR ="include"
task :all => ["clean", "run"]
task :default => "run"
task :run => "hogehoge" do
puts "---- run ----"
sh "./#{TARGET}"
end
task :clean do
puts "---- clean ----"
sh "rm src/*.o *.exe"
end
file "hogehoge" => ["src/hogehoge.o", "src/uhauha.o"] do |t|
puts "---- link ----"
sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end
rule '.o' => '.c' do |t|
sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end
rakefileの解説
rakefileについて、順に解説。
ちなみに、一番網羅的でわかりやすいのは、Ruby公式リファレンス。
https://doc.ruby-lang.org/ja/1.9.3/library/rake.html
Rake Tasks
rakefileはタスクからできている。タスクを順次、実行するための手順を書くのがrakefile。ということで、タスクの概念はrakeには必須。
- rakeではタスクを見つけると、そのタスクを実行させるために処理を芋づる式に探して実行していく。
- タスクの定義方法は、 「task :(タスク名) 」。
- タスクに依存関係を記述する場合は、「task :(タスク名) => “(依存するタスク)"」。依存関係は配列で渡すこともできる。
- タスクに実施させる処理(action)は、ブロック(do end)で与える。
task :all => ["clean", "run"]
task :default => "run"
task :run => "hogehoge" do
puts "---- run ----"
sh "./#{TARGET}"
end
task :clean do
puts "---- clean ----"
sh "rm src/*.o *.exe"
end
File Tasks
ファイル作成のためのタスクかファイルタスク。定義の仕方はタスクと同じ。
rakeタスクは必ず実行されるが、ファイルタスクはタイムスタンプをチェックする。ファイルに変更があった場合のみ実行される。これが、makeと同じところ。
file "hogehoge" => ["src/hogehoge.o", "src/uhauha.o"] do |t|
puts "---- link ----"
sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end
- t.nameは、タスク名(hoehoge)
- t.prerequisitesは、依存先タスク名の配列 [“src/hogehoge.o”, “src/uhauha.o”]
をそれぞれ指す。詳しくは以下のURL。
rule
異なるファイルに対して、同一の処理を実行するためには、ruleを定義する。ruleを満たす場合に実行される処理を書く。
rule '.o' => '.c' do |t|
sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end
t.sourceは、依存するファイルパス(src/c)
Rakeのもう一歩進んだ使い方
コメント
タスクにコメントを記述するには、desc “hogehoge"と書きます。
desc "Clean and Run"
task :all => ["clean", "run"]
desc "Run build"
task :default => "run"
記述は、 rake -Tで見れます。
[tsu-nera]% rake -T
rake all # Clean and Run
rake clean # Remove old files
rake default # Run build
rake run # Build Main
clean
.oや.exeは普通、rmコマンドで削除するが、rakeにはこれを補助する機能がある。
使い方は、‘rake/clean’ を requireで取り込んで、CLEAN、CLOBBERに削除したいファイルを登録する。すると、rake clean/rake clobberタスクが実行できるようになる。
rake clean # Remove any temporary products.
rake clobber # Remove any generated file.
cleanコマンドはCLEANに登録されたファイルを削除。clobberはCLEANとCLOBBERに登録されたファイルを削除する。以下の3行を追加。
require 'rake/clean'
CLEAN.include("src/*.o")
CLOBBER.include("*.exe")
実行結果
[tsu-nera]% rake clean
rm -r src/hogehoge.o
rm -r src/uhauha.o
[tsu-nera]% rake clobber
rm -r hogehoge.exe
ファイルリスト
ディレクトリ配下のファイルに対してまとめて同じ処理を実行するためには、ファイルリストを使うと便利。
こんな感じで宣言する。これは、src配下にあるすべてのソースをSRCSというファイルリストに入れている。考え方はArrayと同じ。
SRCS = FileList[“src/*.c”]
リストの拡張子を変更するために、extメゾッドが用意されている。これで、.cファイルが.oファイルに置き換えられる。
OBJS = SRCS.ext(‘o’)
ファイルリストにファイルを追加するためには、includeメゾッド、除外するためにはexcluleメゾッドを利用する。はじめに、srcs = FileList["**/*.c”]で全てのファイルをリストに加えてから、srcs.exclude(“src/hogehoge.c”)と書いて除外したりできる。
SRCS = FileList["src/*.c"]
OBJS = SRCS.ext('o')
file "hogehoge" => OBJS do |t|
puts "---- link ----"
sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end
まとめ
今までのをまとめると、こうなった。
C_COMPILER = "gcc"
TARGET = "hogehoge.exe"
INC_DIR ="include"
require 'rake/clean'
CLEAN.include(["src/*.o", "*.exe"])
#CLOBBER.include("*.exe")
desc "Clean and Run"
task :all => ["clean", "run"]
desc "Run build"
task :default => "run"
desc "Build Main"
task :run => "hogehoge" do
puts "---- run ----"
sh "./#{TARGET}"
end
SRCS = FileList["src/*.c"]
OBJS = SRCS.ext('o')
file "hogehoge" => OBJS do |t|
puts "---- link ----"
sh "#{C_COMPILER} -o #{t.name} #{t.prerequisites.join(' ')}"
end
rule '.o' => '.c' do |t|
puts "---- compile ----"
sh "#{C_COMPILER} -I#{INC_DIR} -c #{t.source} -o #{t.name}"
end
依存関係を美しく記述デキる点がとても気に入った。また、今まさにRubyを勉強中なので、makefileよりもモチベーションが上がる。ということで、もう少しいろいろ調べてみます。