読者です 読者をやめる 読者になる 読者になる

FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://sites.google.com/site/fpgadevelopindex/

GoogleのビルドツールBazelの勉強(genruleによるファイル自動生成の仕組み)

f:id:msyksphinz:20160529232134p:plain

前回のBazelの試行で詰まったのは、例えば.cppファイルをrubyか何かのスクリプトで自動生成するとき、そのルールをどうやって記述すればいいの?ということだった。 そのあたりを突き詰めて考えてみる。

Bazelによるカスタムルールの作成方法

例えば、A.rbB.rbを使って、C.cpp, D.cppを自動生成するような環境があったとする。この時に、A.rbは内部でB.rbを呼び出し、また引数として```A.rb option````を付けなければならないとする。

まずは依存関係の記述だ。genruleの記法に則れば、以下のようになるはずだ。

genrule (
  name = "A"
  srcs = ["A.rb",
          "B.rb",
         ],
  outs = ["C.cpp",
          "D.cpp",
         ],
  cmd = "A.rb option"
)

CMakeのユーザならば、これだけでうまくいくと思うはずだ。だが、これだけではビルドに失敗する。

まず、ファイルが出力される場所についての明確な記載がない。何故か知らないが、bazelのgenruleの例では、常に出力ファイルの指定、出力ディレクトリの指定が登場する。

genrule(
    name = "foo",
    srcs = [],
    outs = ["foo.h"],
    cmd = "./$(location create_foo.pl) > \"$@\"",
    tools = ["create_foo.pl"],
)

とか、

genrule(
    name = "concat_all_files",
    srcs = [
        "//some:files",  # a filegroup with multiple files in it ==> $(locations)
        "//other:gen",   # a genrule with a single output ==> $(location)
    ],
    outs = ["concatenated.txt"],
    cmd = "cat $(locations //some:files) $(location //other:gen) > $@",
)

とかがそうだけど、違うんやで。リダイレクトだとファイル一個しか指定できない。

複数のファイルを指定できないんだけど、となると、別のMake識別子を使わなければならない。

Make Variables - Bazel

@D: The output directory. If there is only one filename in outs, this expands to the directory containing that file. If there are multiple filenames, this variable instead expands to the package's root directory in the genfiles tree, even if all the generated files belong to the same subdirectory! If the genrule needs to generate temporary intermediate files (perhaps as a result of using some other tool like a compiler) then it should attempt to write the temporary files to @D (although /tmp will also be writable), and to remove any such generated temporary files. Especially, avoid writing to directories containing inputs - they may be on read-only filesystems, and even if they aren't, doing so would trash the source tree.

なるほど、@Dを使ってrubyの引数に直接出力ディレクトリを指定してやるわけか。では、A.rbを改造して、以下のようにすれば良いか。

A.rb option @D

これで、Bazelの好む場所に出力ファイルを生成できる。そうすると、後はBazelが上手く取り計らって出力ファイルを制御してくれる。

genrule (
  name = "A"
  srcs = ["A.rb",
          "B.rb",
         ],
  outs = ["C.cpp",
          "D.cpp",
         ],
  cmd = "ruby $(location A.rb) option $(@D)"
)

スクリプトの場所を指定するときも、A.rbだけでなく、$(location A.rb)とすることもミソだ。

そもそも、Bazelはソースファイルとビルドファイルを混在させるため、一度ソースファイルを別の場所でリンクを張り、別の場所でビルドしてから元に戻す仕組みに見える。以下が証拠だ。

ERROR: /home/vagrant/swimmer_iss/src/BUILD:40:1: C++ compilation of rule '//:core_riscv' failed: namespace-sandbox failed: error executing command
  (cd /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src && \
  exec env - \
    PATH=/usr/local/bin:/home/vagrant/riscv/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games \
  /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/_bin/namespace-sandbox @/home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/bazel-sandbox/a688d402-e259-4842-919c-44976b1a68f0-7.params -- /usr/bin/gcc -U_FORTIFY_SOURCE '-D_FORTIFY_SOURCE=1' -fstack-protector -Wall -Wl,-z,-relro,-z,now -B/usr/bin -B/usr/bin -Wunused-but-set-parameter -Wno-free-nonheap-object -fno-omit-frame-pointer '-std=c++0x' -iquote . -iquote bazel-out/local-fastbuild/genfiles -iquote external/bazel_tools -iquote bazel-out/local-fastbuild/genfiles/external/bazel_tools -isystem external/bazel_tools/tools/cpp/gcc3 -no-canonical-prefixes -fno-canonical-system-headers -Wno-builtin-macro-redefined '-D__DATE__="redacted"' '-D__TIMESTAMP__="redacted"' '-D__TIME__="redacted"' '-frandom-seed=bazel-out/local-fastbuild/bin/_objs/core_riscv/inst_riscv_init.pic.o' -MD -MF bazel-out/local-fastbuild/bin/_objs/core_riscv/inst_riscv_init.pic.d -fPIC -c bazel-out/local-fastbuild/genfiles/inst_riscv_init.cpp -o bazel-out/local-fastbuild/bin/_objs/core_riscv/inst_riscv_init.pic.o).

一回 Change Directoryしとる!確かに、複数のリビジョンでビルドするときはこの方が便利なのかねえ、複雑すぎて利便性がいまいちわからん。

Bazelにより自作ISSの自動C++ファイル生成スクリプトを移植する

結局やりたかったことはこれなのだが、自作ISSでは、Rubyの命令テーブルから複数C++ファイルを自動生成している。

CMakeだとこんな感じ。

add_custom_command (OUTPUT ../src/inst_decoder.cpp ../src/inst_decoder.hpp ../src/inst_operand.cpp ../src/inst_operand.hpp ../src/inst_mnemonic.cpp ../src/inst_riscv_init.cpp ../src/inst_riscv.hpp ../src/inst_list.hpp
  COMMAND ruby ./../src/gen_arch_table.rb riscv .
  WORKING_DIRECTORY ./../src/
  DEPENDS ./../src/gen_arch_table.rb ../src/riscv_arch_table.rb
  )

これをBazelで食わせると以下のようになる。っていうか少しRubyの方も変えている。

github.com

genrule(
  name = "inst_decoder",
  outs = ["inst_list.hpp",
          "inst_decoder.cpp", "inst_decoder.hpp",
          "inst_operand.cpp", "inst_operand.hpp",
          "inst_riscv_init.cpp", "inst_riscv.hpp",
          "inst_mnemonic.cpp",
  ],
  cmd = "ruby $(location gen_arch_table.rb) riscv $(@D)",
  tools = ["gen_arch_table.rb"],
  visibility = ["//visibility:public"],
  srcs = ["gen_arch_table.rb",
          "gen_inst_list.rb",
          "gen_decode_table.rb",
          "gen_function_table.rb",
          "gen_inst_mnemonic.rb",
          "gen_operand_table.rb",
          "riscv_arch_table.rb",
          ],
)

gen_arch_table.rbの引数として出力ディレクトリを指定できるようにしている。

このように記述することで、とりあえず自動生成ファイル群の問題については解決した。 だが、まだ問題は山積だ。 まずは、外部のソースコードのビルドとどう連携する?前に言った通り、上位のディレクトリと連携するのが厳しい。 この辺についても調査してみなければ。

続く?