FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

DSLでビルドツールを自作する (4日目 ターゲットの依存関係を作ろう)

この記事は「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はそれぞれまた別のターゲットに依存している...
f:id:msyksphinz:20191121222441p:plain

これを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_fileexec_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に近づいてきています。

しかし、これではまだタイムスタンプによるビルドルールの実行省略ができていません。次回は、それを考えていきます。