FPGA開発日記

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

C++ビルド環境としてのBazel導入試行

f:id:msyksphinz:20160529232124p:plain

現在、自作ISSのビルド環境としてはCMakeを利用している。

CMakeの良いところは、

などがある。欠点といえば、CMakeは基本的にC++での開発プロジェクトを前提としており、それ以外のプログラミング言語では管理が少し難しい。 Verilogの開発環境などでも以前導入したことがあるが、かなり苦労させられた(というかほとんど依存関係を自分で記述する羽目になった)

msyksphinz.hatenablog.com

いろいろ調べていたのだが、Tensorflowをインストールするにあたり、Bazelというビルドツールをよく導入していた。 調査の結果、BazelはCMakeよりも多くの言語をサポートしており、マルチプラットフォームで依存関係も抽出できる。これは良さそうだ。 現状のサポート言語としてはVerilogは含まれていないが、将来性を見込んで試行してみよう。

サポートされている言語とコマンド

http://bazel.io/docs/be/overview.html より抜粋

f:id:msyksphinz:20160508122850p:plain

まずは簡単なプロジェクトでBazelの使い方を把握する

ググった結果、以下の記事がとても良さそうだった。

blog.matsuokah.jp

同じような環境を構築し、試行してみた。

github.com

BUILDの中身

cc_binary (
  name = "main",
  srcs = ["main.cpp"],
)

Makefileの中身

.PHONY: all
all: run

BAZEL=$(shell which bazel)

.PHONY: run
run:
        $(BAZEL) run //src:main

.PHONY: build
build :
        $(BAZEL) build //src:main

.PHONY: clean
clean :
        $(BAZEL) clean

実行結果

$ make
/usr/local/bin/bazel run //src:main
INFO: Found 1 target...
Target //src:main up-to-date:
  bazel-bin/src/main
INFO: Elapsed time: 6.113s, Critical Path: 0.47s

INFO: Running command line: bazel-bin/src/main
Hello World

確かに、シンプルなプロジェクト構成であれば、簡単にビルドおよび実行が可能だ。

欠点としては、異常に遅いことだと思う。一つのC++ソースをビルドして実行するのにおよそ5秒はかかっている。CMakeだと一瞬なのに...

実際のプロジェクトはここまで単純なものでは無い

シンプルなプロジェクトならこれで問題ないが、実際に僕のプロジェクトはここまで単純なものでは無い。

  • ライブラリを生成する
  • rubyによりC++ファイルを自動生成する

ライブラリの生成方法はBazelでもとても簡単だ。上記のBUILD記述ではcc_binaryとしていたが、cc_libraryとして同様の記述をするだけでライブラリを作成できる。

しかし問題は別の言語を用いて、ファイルを自動生成したいときにどのように記述するか、という事だ。まず、僕の環境では、アーキテクチャを記述したrubyのテーブルファイルから、複数C++コードとヘッダを生成する。

CMakeだと以下のように記述している。

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

カスタムコマンドの記述だ。カスタムコマンドgen_arch_table.rbに対して入力ファイル、出力ファイルを記述すれば自動的に依存関係を抽出してくれる。

一方でBazelで同様の記述をするためにはどのようにすれば良いのだろう?

Bazel でカスタムコマンドを使用する

残念ながら、Bazelは現在rubyでのビルドには対応していないらしい。

そこで、カスタムルールを生成するコマンドを使用してみたい。Bazelにもカスタムルールを生成するコマンドが存在する。genruleだ。

http://bazel.io/docs/be/general.html#genrule

genruleにより、C++ファイルを自動生成するコマンドを記述してみた。

genrule(
  name = "inst_decoder",
  outs = ["inst_decoder.cpp",
          "inst_decoder.hpp",
          "inst_operand.cpp",
          "inst_mnemonic.cpp",
          "inst_riscv_init.cpp",
          "hogelog",
  ],
  cmd = "./$(location gen_arch_table.rb) riscv",
  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",
          ],
)

依存関係を記述し、出力としてはさまざまなデコードファイルが生成される、という訳だ。これをBazelでビルドしてみたのだが、以下のようなエラーが出てしまった。

$ make
/usr/local/bin/bazel build :inst_decoder --verbose_failures
INFO: Found 1 target...
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'inst_decoder.cpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'inst_decoder.hpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'inst_operand.cpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'inst_mnemonic.cpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'inst_riscv_init.cpp' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: declared output 'hogelog' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely).
ERROR: /home/vagrant/swimmer_iss/src/BUILD:1:1: not all outputs were created.
Target //:inst_decoder failed to build
INFO: Elapsed time: 4.076s, Critical Path: 0.09s
make: *** [build] Error 1

うーん、実際にはスクリプトでファイルが生成されなかったって。

冷静に考えてみると、Bazelのビルドの仕組みがよくわかっていない。そもそもBazelは、ビルド時にビルド用のディレクトリを生成するようで、環境を移動してファイルを作成したらならば、生成したファイル群をどのように取り扱えばよいのか、そのあたりが全く理解できてない。

Bazel buildを実行すると、実際にはBazelにより生成された以下のディレクトリでビルド処理が行われるという訳だ。

:~/swimmer_iss/src$ ls -lt
total 1020
lrwxrwxrwx 1 vagrant vagrant    108 May  8 03:45 bazel-bin -> /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/bazel-out/local-fastbuild/bin
lrwxrwxrwx 1 vagrant vagrant    113 May  8 03:45 bazel-genfiles -> /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/bazel-out/local-fastbuild/genfiles
lrwxrwxrwx 1 vagrant vagrant     88 May  8 03:45 bazel-out -> /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/bazel-out
lrwxrwxrwx 1 vagrant vagrant     78 May  8 03:45 bazel-src -> /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src
lrwxrwxrwx 1 vagrant vagrant    113 May  8 03:45 bazel-testlogs -> /home/vagrant/.cache/bazel/_bazel_vagrant/b2de25cdaad74e7085e250210e26a5eb/src/bazel-out/local-fastbuild/testlogs
-rw-rw-r-- 1 vagrant vagrant    581 May  7 17:38 BUILD

そう考えると、どのようにして自動生成ファイルを処理すればよいのか。これを解決するためにはlocationという記述子を活用すれば良さそうなのだが、これもいまいちよく分からない。

以下はBazelのマニュアルに書いてある例なのだが、リダイレクトを使うのならば、カスタムルール一つにつき一つしかファイル作れないよね?

http://bazel.io/docs/be/general.html#genrule

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

このあたりはまだ勉強する必要がある。実際にBazelを導入する必要があるかどうかも含めて。