この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の4日目の記事です。
4日目 ターゲットの依存関係を作ろう
Makefileでは、ルールに対する依存関係を記述することができます。あるルールを実行するためには、まずは依存する別のルールを実行する必要があり、そのルールを実行するためには、また別のルールを実行する必要があり、、、ということで、ルールに従ってMakefile内を渡り歩いていく仕組みになっています。
all: target1 # allはtarget1に依存している。まずはtarget1が呼び出される。 target1: target2 target3 # target1はtarget2とtarget3に依存している。 do target2 target3 # target2とtarget3が処理できたら、それらを用いてtarget1を実行。 target2: ... target3: ... # Target2と3はそれぞれまた別のターゲットに依存している...

これをrumy-makeでも実現したいのですが、単純に実装したいだけならば簡単です。Target
クラスには、依存する別のターゲットを記述するためのdepends
メンバ変数を用意していました。また、make_target
内でも、依存関係を記述するためのdepends
関数を指定できるようにしています。以下のように、make_target
でターゲットを作成時に依存するターゲットを指定できます。
make_target :run_c do depends [:compile_c] # ターゲット:run_cは:compile_cに依存している executes "../tests/simple_main" end
で、単純にexec_target
実行時に、そのターゲットのコマンドを実行する前に依存するターゲットを全部呼び出してしまえばよいわけです。という訳で、exec_target
を改造します。
src/rumy-exec.rb
class Target def initialize(name) @name = name @depend_targets = [] # コンストラクタでは、一応リストを初期化しておく。 end ... attr_reader :depend_targets # depends_targetsがread onlyで参照できるように設定。 end def exec_target (name) if @target_list.key?(name) then target = @target_list[name] # 依存するターゲットを繰り返して実行していく。 target.depend_targets.each{|dep| puts "[DEBUG] : Depends Target \"#{dep}\" execute." exec_target(dep) } # 最後に本来のターゲットを実行 result = `#{target.commands}` puts result else puts "Error: target \"#{name}\" not found." end end
target.depend_targets
の中身をeach
で繰り返しながら、exec_target(dep)
によって全部実行していきます。普通Makefileの場合はタイムスタンプなどを確認しながら実行しなくていいルールは呼び出しをスキップするわけですが、とりあえずこれは初期実装なのでそこまでしなくてもいいでしょう。まずは動けばよいのです。
というわけで、今回の実装はこれで終了です。テストを作ります。前回用意した:compile
ターゲットは、Cソースコードをコンパイルするだけのルールでしたが、コンパイルしたバイナリを実行するターゲットを作りたいです。これを:run
というターゲットとします。そして、:run
ターゲットは:compile
ターゲットに依存しており、実行前にコンパイルが走るようにします。
test/exec_test.rb
make_target :run_c do # simple_mainを実行するためのターゲット:run_cを作成 depends [:compile_c] # :run_cは:compile_cに依存している。 executes "../tests/simple_main" end exec_target :run_c # run_cを実行
実行します。
ruby ../tests/exec_test.rb
[DEBUG] : Target Created = run_c, Depends = , Commands = ../tests/simple_main [DEBUG] : Depends Target "compile_c" execute. Hello rumy-make!!
結果だけ見ると分かりにくいですが、simple_main.cpp
からsimple_main
がコンパイルされ、最終的に実行されていることが分かります。これで、依存関係を含むターゲットの記述ができるようになりました。
依存関係を多段にする
それでは、もう1段ルールの依存関係を増やして、多段のルールを呼び出してみます。上記のコンパイル→実行のルールと似ていますが、今回は「echoでソースコードを作成」→「コンパイル」→「実行」という3ステップにします。
tests/multiple_depends.rb
#!/usr/bin/ruby load "rumy-exec.rb" source_file = "./test.c" exec_file = source_file.sub(".c", "") make_target :make_ccode do executes "echo \"#include <stdio.h>\nint main () { printf(\\\"Hello Rumy-Make!!\\\"\); return 0; }\" > #{source_file}" end make_target :compile_c do depends [:make_ccode] executes "gcc #{source_file} -o #{exec_file}" end make_target :run_c do depends [:compile_c] executes "#{exec_file}" end exec_target :run_c
共通変数としてsource_file
とexec_file
を用意し、前回と同様にソースファイルの名前からバイナリファイル名が自動的に生成されるようにしています。:make_ccode
の実行コマンドを死ぬほど読みにくいですが、これはまあRubyの内部機能に依存している分仕方がありません。内部で括弧やダブルクォーテーションを含む文字列を生成すると、こういう内部DSLで記述する場合は複雑な表現になりがちです。
ともかく、ターゲットとしては完成です。依存関係しておきます。
:run_c
は:compile_c
に依存しています。:compile_c
は:mak_ccode
に依存しています。
では、exec_target :run_c
で:run_c
ターゲットを実行してみましょう。
ruby ../tests/multiple_depends.rb [DEBUG] : Target Created = make_ccode, Depends = , Commands = echo "#include <stdio.h> int main () { printf(\"Hello Rumy-Make!!\"); return 0; }" > ./test.c [DEBUG] : Target Created = compile_c, Depends = , Commands = gcc ./test.c -o ./test [DEBUG] : Target Created = run_c, Depends = , Commands = ./test [DEBUG] : Depends Target "compile_c" execute. [DEBUG] : Depends Target "make_ccode" execute. rumy-exec.rb:49: warning: Insecure world writable dir /home/msyksphinz/perl5/bin in PATH, mode 040777 Hello Rumy-Make!!
コンパイルから実行まで一気通貫で実行できましたね。このように、依存関係に従ってビルド処理を記述することができるようになりました。少しずつですが、Makeに近づいてきています。
しかし、これではまだタイムスタンプによるビルドルールの実行省略ができていません。次回は、それを考えていきます。