始めに
仕事で、ゼロからEdge 向けのAIの推論を行うアプリケーションを作ることになったので、Googleの社内でも使われているというBazelというビルドツールを使って、プロジェクトを作成したので、その時のノウハウをまとめておきます。
テストフレームワークにgtestを採用して、Bazelでビルドとテストの実行を行いました。
最初は、使い慣れていてたくさんサンプルがあるmakeの方が使いやすいなぁと思っていたが、一度慣れると、bazelの方が簡単に書けるし、makeに詳しくない新しい開発メンバーもすぐに使えるようになっているので、bazelにしてよかったなぁという感じ。
Bazelとは
Googleの社内でも使われているビルドとテストを行うツールで、Tensorflowやmediapipeなどでも使われています。
https://bazel.build/
コードを修正したときに、関係のあるファイルのみをビルドし直すので、ビルド時間も早く、複数言語にも対応、実行binaryやlibrary、テストコードでBUILDファイルのルールが異なり、ソースコードの目的も分かりやすい。
Bazelのインストール(Linux X86_64向け)
$ sudo apt install apt-transport-https curl gnupg
$ curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
$ sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
$ echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
$ sudo apt update && sudo apt install bazel
用意するファイル
ソースコード以外にも、WORKSPACEというプロジェクトで一つのファイルと、BUILDというモジュールごとに用意するファイルの2種類がある。
フォルダとファイルの構成
.
|-- BUILD
|-- README.md
|-- WORKSPACE
|-- gmock.BUILD
|-- opencv.BUILD
|-- sample_app
| |-- BUILD
| |-- lib
| | |-- BUILD
| | |-- sample_lib.cpp
| | `-- sample_lib.h
| |-- sample_main.cpp
| `-- test
| |-- BUILD
| `-- test_sample_app.cpp
`-- test_data
|-- BUILD
`-- sample_app.png
WORKSPACEファイルの作成
WORKSPACEというファイルは、プロジェクトのルートディレクトリの直下に置いておく必要がある。
ここには、基本的に外部のプロジェクト等を呼び出す際の記述を書く。
今回は、opencvやgtestの外部ライブラリを使用したので、以下のように記述しておく。
workspace(name = "sample_app")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
new_git_repository(
name = "googletest",
build_file = "@//:gmock.BUILD",
remote = "https://github.com/google/googletest",
tag = "release-1.10.0",
)
new_local_repository(
name = "opencv",
path = "/usr",
build_file = "opencv.BUILD",
)
~~~
WORKSPACEで、モジュール対してbuild_fileを指定すると、指定したBUILDファイルが呼び出されて、そちらでビルドされる。
今回、googletest では WORKSPACEでgit のレポ名やブランチ名、タグ名などを指定して、build_fileにgmock.BUILDを指定している。
gmock.BUILDは以下のように記述する。
```bash:gmock.BUILD
cc_library(
name = "gtest",
srcs = [
"googletest/src/gtest-all.cc",
"googlemock/src/gmock-all.cc",
],
hdrs = glob([
"**/*.h",
"googletest/src/*.cc",
"googlemock/src/*.cc",
]),
includes = [
"googlemock",
"googletest",
"googletest/include",
"googlemock/include",
],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
)
cc_library(
name = "gtest_main",
srcs = ["googlemock/src/gmock_main.cc"],
linkopts = ["-pthread"],
visibility = ["//visibility:public"],
deps = [":gtest"],
また、今回は、opencvはhost上にbuild済み(apt-get で取ってきた)のものを使うので、
opencv.BUILDにhost上の/usr/lib/x86_64-linux-gnu以下にある、opencvのlibraryを取り込む記述をしておく。
WORKSPACEの方で、path = "/usr" と書いているのでopencv.BUILDの方は/usr以下のパスで記述しておくこと。
cc_library(
name = "opencv",
srcs = glob(["lib/x86_64-linux-gnu/libopencv_core.so*",
"lib/x86_64-linux-gnu/libopencv_highgui.so*",
"lib/x86_64-linux-gnu/libopencv_imgcodecs.so*",
"lib/x86_64-linux-gnu/libopencv_imgproc.so*",
"lib/x86_64-linux-gnu/libopencv_video.so*",
"lib/x86_64-linux-gnu/libopencv_videoio.so*",
"lib/x86_64-linux-gnu/blas/libblas.so*",
"lib/x86_64-linux-gnu/lapack/liblapack.so*",
]),
hdrs = glob(["include/opencv2/**/*.h*"]),
includes = ["include/opencv2"],
visibility = ["//visibility:public"],
linkstatic = 1,
)
BUILDファイルの作成
では、実際にソースコードの方のBUILDファイルには、以下のような感じで記載する。
以下を見て、binaryをビルドするならcc_binary、 libraryを作りたいならcc_library、テストコードを書くならcc_test という構文を使う。
https://docs.bazel.build/versions/main/be/c-cpp.html
cc_binaryであれば、
- binaryの名前(name = "sample_app.bin")
- ビルドしたいファイル(srcs = "sample_main.cpp")
- linkするlibrary(dep = "@opencv//:opencv")
- -D で定義したいマクロ(deps = "ENABLE_DEBUG")
- makeに渡すオプション(copts = "-g")
- ソースコード以外のファイル(data = "test_data:sample_app.png")
という感じ書いていく。
cc_binary(
name = "sample_app.bin",
srcs = ["sample_main.cpp"],
deps = ["//sample_app/lib:lib_sample_app",
"@opencv//:opencv"],
defines = ["ENABLE_DEBUG"],
copts = ["-Isample_app/lib/",
"-fPIC", "-g"],
data = ["//test_data:sample_app.png"],
)
~~~
Libraryを作るときは、以下のようにcc_libraryを使って記述する。
cc_binaryをと異なるところは、visibility を追加する必要がある。
```bash:sample_app/lib/BUILD
cc_library(
name = "lib_sample_app",
srcs = ["sample_lib.cpp"],
hdrs = ["sample_lib.h"],
defines = ["ENABLE_DEBUG"],
visibility = ["//visibility:public"],
copts = ["-fPIC", "-g"],
)
ソースコードとは別に、テスト用に使うData もあると思うので、そういった物は、exports_filesというものを使って定義する。
exports_files("sample_app.png")
以下は、gtestで書いたテストコードをビルドするときのBUILDファイル。
cc_test(
name = "test_sample_app",
srcs = ["test_sample_app.cpp"],
deps = ["//sample_app/lib:lib_sample_app",
"@googletest//:gtest_main",
copts = ["-Isample_app/lib/"]
)
ソースコードのビルドと実行
実際にビルドとテストを行うときは、bazel build というコマンドを使ってBUILDファイルの置いてある場所とname を指定する。
ビルド後は、bazel-bin以下の実行ファイルを実行すればよい。
bazel build sample_app:sample_app.bin
./bazel-bin/sample_app/sample_app.bin
テストコードのビルドと実行
テストコードの実行はビルドと実行を一緒に行ってくれるbazel run を使う
bazel run sample_app/test:test_sample_app
それ以外のTips
-
BAZEL_BUILD_FLAGSには--config debugを追加すると、debug buildになる。
-
--config にパラメータを指定して、.bazelrc に設定した内容も呼び出せる。
以下のようにbuild:XXXX と.bazelrc に書いておいて、bazel build するときに--config xxx を付けると、.bazelrc の内容で反映される。
tensorflow や mediapip のgithub を見れば、.bazelrc のサンプルを見ることができる。
build:debug -c dbg
build:debug --copt="-g"
build:debug --strip="never"
build:lsan --copt -fsanitize=leak
build:lsan --copt -O0
build:lsan --linkopt -fsanitize=leak
build:msan --copt -fsanitize=memory
build:msan --copt -O0
build:msan --linkopt -fsanitize=memory
サンプルコード置き場
上で説明してきたサンプルコードは以下に置いてある。
https://github.com/daiki0321/bazel_sample_app