FPGA開発日記

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

DSLでビルドツールを自作する (12日目 C++プロジェクトビルド用のラッパーを作ろう)

この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の12日目の記事です。

12日目 C++プロジェクトビルド用のラッパーを作ろう

前回、実際に実プロジェクトにRumy-Makeを適用して問題が把握できてきました。まず、C++ファイルをコンパイルするために記述しなければならないルールが多すぎます。いくらRubyの構文が使えるとはいえ、数10個のファイルをコンパイルするためにわざわざfor文を記述しなければならないのは面倒です。そう考えると、CMakeのようなうまく内部をラップしたビルドツールというのは上手くできていると実感できます。

まあ人のツールをほめたたえてもしょうがないので、自作のビルドツールにも同じようなラッパーを導入しましょう。そこで、rumy-cpp.rbというラッパーを作成し、このファイルを呼べばC++用のビルドはある程度形式化できるようにしておきましょう。このC++用ラッパーでは、以下の機能を追加します。

  • C++のファイルからライブラリを作るためのルール。C++のソースファイルを指定し、あとは外部の別のライブラリを指定すれば自動的にすべてのソースファイルをコンパイルしてライブラリを生成します。
  • C++のファイルから実行ファイルを作るためのルール。C++のソースファイルを指定し、外部の依存する別のライブラリを指定すれば自動的にすべてのソースファイルをコンパイルして実行ファイルを生成します。

それぞれ、ライブラリを生成するルールをmake_library、実行ファイルを生成するルールをmake_executeとして実装します。

実装としては全く難しくなくて、前回自力で書いたソースファイルのコンパイルのループと、最後の実行ファイル・ライブラリの作成部分を追加するだけです。

  • src/rumy-cpp.rb
def make_library(lib_name, cpp_file_list, compile_options, link_options, additional_depends = [])

  # 依存するターゲットをすべて実行するループ
  additional_depends.each {|dep|
    do_target dep
  }

  obj_file_list = cpp_file_list.map {|cpp| cpp + ".o"}
  # ソースファイルから生成したオブジェクトを使ってライブラリを生成する。
  make_target lib_name do
    global
    depends obj_file_list
    executes ["ar qc #{link_options.join(' ').to_s} #{lib_name} #{obj_file_list.join(' ').to_s}"]
  end

  # 指定されたソースファイルをすべてコンパイルしていくループ。
  cpp_file_list.zip(obj_file_list).each {|pair|
    cpp = pair[0]
    obj = pair[1]
    make_target obj do
      depends [cpp]
      executes ["g++ #{compile_options.join(' ').to_s} -c #{cpp} -o #{obj}"]
    end
  }
end

このラッパーを使えば、前回一生懸命書いたオブジェクトをコンパイルするためのループは、以下のように表現できます。とても楽になりました。

cedar_cpp_lists = [
  "../src/riscv_pe_thread.cpp",
  "../src/riscv_syscall.cpp",
  "../src/riscv_fds.cpp",
...
  "../src/mem_body.cpp",
  "../src/gdb_env.cpp"
]

c_options = compile_options.join(' ').to_s
l_options = link_options.join(' ').to_s

make_library "libriscv_cedar.a", cedar_cpp_lists, compile_options, [], [:gen_riscv_arch_info, :gen_riscv_csr_info]

make_libraryを使えば、ソースファイルのリストを追加するだけの1行でライブラリを生成できます。

実行ファイルの生成も同様です。ここではmake_executeの実装は殆どmake_libraryと一緒なので、ここでは省略します。ここでは、make_executeの使用例だけを示します。

make_execute("swimmer_riscv", swimmer_cpp_lists, ["libriscv_cedar.a", "../vendor/softfloat/build/libsoftfloat.a"],
             compile_options, link_options,
             link_libs,
             [:config_hpp])

使い方はmake_libraryと似ており、ソースファイル、依存するライブラリ、リンクオプション、外部リンクなどを指定すれば完成です。

これにより、前回のRumy-Makeファイルに対してどれだけ行数を節約できたかというと、

  • make_library ,make_execute 適用前 : 116行
  • make_library ,make_execute 適用後 : 92行

多少は行数を削減することができました。まあ行数削減というか、目的としてはルールを見やすくするためだったので行数が減らなくても問題ないと思います。

また、make_library, make_executemake_targetの単純なラッパーなので、draw_targetも問題なく表示できます。