初心者でも使えたビルドツールのBazel(C/C++編)

## 始めに
　仕事で、ゼロから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の外部ライブラリを使用したので、以下のように記述しておく。

```bash:WORKSPACE
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以下のパスで記述しておくこと。

```bash:opencv.BUILD
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")   

という感じ書いていく。

```bash:sample_app/BUILD
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というものを使って定義する。

```bash:test_data/BUILD
exports_files("sample_app.png")
```

　以下は、gtestで書いたテストコードをビルドするときのBUILDファイル。

```bash:sample_app/test/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 のサンプルを見ることができる。

```:.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