この記事は「Qiita Advent Calendar 2019 DSLで自作ビルドツールを作ろう」の11日目の記事です。
11日目 Rumy-Makeを実際のプロジェクトに適用してみる
ここまで、Rumy-Makeの開発行ってきて基本的な機能は実装できたように思います。しかし、まだ足りないところがありそうな気がする。何となくこれでは物足りないような... モヤモヤするので、実際のプロジェクトに適用することによって問題を把握することにしたいと思います。
Rumy-Makeを適用するプロジェクト : RISC-V命令セットシミュレータSwimmer-RISCV
Rumy-Makeを適用するプロジェクトとしては、私が管理しているRISC-V命令セットシミュレータSwimmer-RISCVをターゲットにしたいと思います。Swimmer-RISCVは完全C++で書かれたRISC-Vシミュレータで、RISC-V向けにビルドされたLinuxを立ち上げることも可能なシミュレータです。ソースコードは数十個のファイルで構成されており、外部ライブラリも複数使用しています。
もともとSwimmer-RISCVはビルドツールとしてCMakeを使用しています。今回は、これをRumy-Makeで置き換えて、どの程度使い物になるのか見てみたいと思います。
- Swimmer-RISCVはsoftfloatをサブプロジェクトとして使用している。
Swimmer-RISCVはsoftfloatをサブプロジェクトとして含んでいます。こちらも、ビルドにはCMakeを使ってビルドしています。CMakeであればこのプロジェクトをサブプロジェクトとして含むことができるのですが、Rumy-Makeでは複数Makeファイルを使ったサブビルドという概念がまだ実装できていません。したがって、とりあえずはsoftfloatをCMakeでビルドして、ビルド済みのライブライを使ってリンクするところまでを考えます。
Swimmer-RISCVは2段階のビルド構成を取っており、まずはRISC-V命令セットシミュレータのコア部分をライブラリとしてビルドしています。CMakeLists.txtでは、以下のように記述しています。
add_library (riscv_cedar ../src/riscv_pe_thread.cpp ../src/riscv_syscall.cpp ../src/riscv_fds.cpp ... ../src/memory_block.cpp ../src/mem_body.cpp ../src/gdb_env.cpp )
これをそのままRumy-Makeに置き換えるのですが、Rumyにはadd-library
のようなC言語のライブラリコンパイルをまとめるだけの高解機能を持っていません。そこで、単純にC++ソースファイルをコンパイルしてオブジェクトファイルを生成し、それをライブラリとしてまとめ上げるとという手順を取ります。以下のようにRumyファイルを記述しました。
obj_lists = [ "riscv_pe_thread.o", "riscv_syscall.o", "riscv_fds.o", ... "memory_block.o", "mem_body.o", "gdb_env.o" ] c_options = compile_options.join(' ').to_s l_options = link_options.join(' ').to_s obj_lists.each {|obj| make_target obj do src = "../src/" + obj.sub(".o", ".cpp") depends [src] executes ["g++ #{c_options} -c #{src} -o #{obj}"] end } make_target "libriscv_cedar.a" do depends obj_lists executes ["ar qc libriscv_cedar.a #{obj_lists.join(' ').to_s}"] end
ソースファイルのオブジェクトをリストobj_lists
で管理し、これらにループを適用してmake_target
を適用していきます。Rubyのループを使えば簡単に複数のターゲットを定義することができます。さらに、ライブラリlibriscv_cedar.a
の生成には、依存ファイルとしてすべてのオブジェクトファイルをリストとして指定し、これを実行時にすべて渡します。obj_lists.join(' ').to_s
と記述することで、["riscv_pe_thread.o", "riscv_syscall.o", ... "gdb_env.o"]
という記述をriscv_pe_thread.o riscv_syscall.o ... gdb_env.o
という文字列に置き換えることができ、これをリンクファイルとして指定します。これで、libriscv_cedar.a
を作ることができます。
オプションの処理方法ですが、指定したいオプションを以下のようにリストとして管理し、これを文字列として展開してすべてのコンパイルコマンドに渡していきます。
compile_options = [] compile_options = compile_options + ["-DARCH_RISCV"] compile_options = compile_options + ["-I../vendor/cmdline/"] compile_options = compile_options + ["-I../vendor/softfloat/SoftFloat-3d/source/include/"] ... c_options = compile_options.join(' ').to_s
- バージョン情報表示のためのヘッダファイル生成
Swimmer-RISCVでは、--version
オプションを指定したときに、ビルド日時とgitのリビジョン番号について出力するようにしています。
$ swimmer_riscv --version // Swimmer-RISCV // Version 20190920 Revision acc20b3 // developed by msyksphinz <msyksphinz.dev@gmail.com>
これを実現しているのは、config.h.in
というオリジナルファイルからconfig.hpp
というファイルを生成し、このヘッダファイルにリビジョン番号とビルド日時を挿入していることによります。
config.h.in
#define VERSION "@VERSION@" #define REVISION "@REVISION@"
上記のヘッダテンプレートに対して、@VERSION@
と@REVISION@
の置き換えを行う訳ですが、CMakeの場合は特殊変数として認識されるのですがRumy-Makeはその機能は存在していないので、単純に外部コマンドを呼び出して対応します。
build_date = `date +%Y%m%d`.sub("\n", "") build_version = `git rev-parse --short`.sub("\n", "")
config.hpp
を生成するために以下の:config_hpp
ターゲットルールを作成しました。
make_target :config_hpp do executes ["sed 's/@VERSION@/#{build_date}/g' config.h.in | sed 's/@REVISION@/#{build_version}/g' > config.hpp"] end
これらをまとめ上げて、最後にswimmer_riscv
バイナリを生成するためのルールを生成します。swimmer_main.cpp
のみは上記のconfig.hpp
の生成が事前に必要なので、ルールを変更しました。
swimmer_obj_lists = [ "swimmer_util.o", "riscv_bfd_env.o", "python3_env.o" ] swimmer_obj_lists.each {|obj| make_target obj do src = "../src/" + obj.sub(".o", ".cpp") depends [src] executes ["g++ #{c_options} -c #{src} -o #{obj}"] end }
make_target "swimmer_riscv" do global depends swimmer_obj_lists + ["swimmer_main.o"] + ["libriscv_cedar.a"] link_libs = ["-lbfd", "-lpython3.6m", "-lgmp", "-lgmpxx"].join(' ').to_s executes ["g++ #{l_options} #{swimmer_obj_lists.join(' ').to_s} \ swimmer_main.o \ libriscv_cedar.a \ ../vendor/softfloat/build/libsoftfloat.a \ -o swimmer_riscv \ #{link_libs}"] end
最後のswimmer_riscv
ルールが、バイナリを生成するためのルールです。libriscv_cedera.a
と、`これまでに生成したオブジェクトをすべて結合してバイナリを作るルールにしています。
これを実行すると、時間がかかりますが無事にバイナリを生成することができました。とりあえず、ビルドツールとして最低限の仕事ができているようです。
ここまでの試行で、問題となったのは以下です。