Bazelお試し

Bazelとはオープンソースのビルドツールです。

類似する他の有名なツールとしてはGNU MakeやMavenが挙げられます。公式ドキュメントを読むと、Bazelは以下の5点で優れたツールだと述べられています。

High-level build language. Bazel uses an abstract, human-readable language to describe the build properties of your project at a high semantical level. Unlike other tools, Bazel operates on the concepts of libraries, binaries, scripts, and data sets, shielding you from the complexity of writing individual calls to tools such as compilers and linkers.

Bazel is fast and reliable. Bazel caches all previously done work and tracks changes to both file content and build commands. This way, Bazel knows when something needs to be rebuilt, and rebuilds only that. To further speed up your builds, you can set up your project to build in a highly parallel and incremental fashion.

Bazel is multi-platform. Bazel runs on Linux, macOS, and Windows. Bazel can build binaries and deployable packages for multiple platforms, including desktop, server, and mobile, from the same project.

Bazel scales. Bazel maintains agility while handling builds with 100k+ source files. It works with multiple repositories and user bases in the tens of thousands.

Bazel is extensible. Many languages are supported, and you can extend Bazel to support any other language or framework.

Why should I use Bazel?

要約すると、

  • 可読性の高いセマンティックな記述が可能
  • 高速で、かつ、キャッシュを用いたインクリメンタルなビルドが可能
  • 複数プラットフォームをターゲットとしたビルドが可能
  • 100,000以上の膨大なソースファイルに対しても使用可能
  • さまざまな言語で使用可能

なツールだと述べられています。

インストール

Ubuntu 18.04 LTSにおいてBazelをインストールする方法は以下の通りです。

$ sudo apt install curl gnupg
$ curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add -
$ 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

他のOSにおけるインストール方法は公式ドキュメントを参照下さい。

試してみる

以下のC++向けチュートリアルを試してみます。

リポジトリが用意されているようです。ありがたい。

$ git clone https://github.com/bazelbuild/examples

stage1からstage3まで3種類のチュートリアルが用意されています。

Bazelによるビルドには以下の2種類のファイルを使用します。これらのファイルにビルドに関する情報を記述します。

  • WORKSPACE: ワークスペースの情報を記述する。プロジェクトのルートディレクトリに配置する。
  • BUILD: パッケージのビルド情報を記述する。各パッケージのルートディレクトリに配置する。

stage1

1つ目のチュートリアルはとても単純で、単一のC++ファイルをコンパイルします。ディレクトリ構成は以下の通りです。

stage1
  ├── main
  │   ├── BUILD
  │   └── hello-world.cc
  └── WORKSPACE

WORKSPACEファイルは空ファイルです。

BUILDファイルには以下のような記述がされています。

load("@rules_cc//cc:defs.bzl", "cc_binary")
 
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

ここで、cc_binaryはBazelで定義されているC/C++向けのルールであり、指定された引数の情報を元にバイナリを出力します。詳しくは以下に書かれています。

ここでは、hello-world.ccからhello-worldという名前の実行バイナリを生成することを指定しています。hello-world.ccはご想像通り”hello world”を出力するだけのmain関数…と思わせて、”hello world”と同時に実行時刻を出力します。さて、ビルドしてみます。

$ bazel build //main:hello-world

上記のコマンドをプロジェクトのルートディレクトリで実行すると、そのディレクトリ直下にビルド成果物であるbazel-*ディレクトリが生成されます。

stage1
   ├── bazel-bin -> $HOME/.cache/bazel/...
   ├── bazel-out -> $HOME/.cache/bazel/...
   ├── bazel-stage1 -> $HOME/.cache/bazel/...
   ├── bazel-testlogs -> $HOME/.cache/bazel/...
   ├── main
   │   ├── BUILD
   │   └── hello-world.cc
   └── WORKSPACE

ここで、各bazel-*ディレクトリはシンボリックリンクであることに注意してください。Bazelはキャッシュをそれぞれのプロジェクト下ではなくホームディレクトリ下に保持するようです。

実行バイナリはbazel-bin以下に生成されています。

$ ./bazel-bin/main/hello-world
Hello world
Mon Jun  8 23:05:53 2020

runコマンドを利用すればビルドと同時に実行することも出来ます。

$ bazel run //main:hello-world
Loading: 
Loading: 0 packages loaded
Analyzing: target //main:hello-world (0 packages loaded, 0 targets configured)
INFO: Analyzed target //main:hello-world (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
[0 / 1] [Prepa] BazelWorkspaceStatusAction stable-status.txt
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.114s, Critical Path: 0.01s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
INFO: Running command line: bazel-bin/main/hello-world
INFO: Build completed successfully, 1 total action
Hello world
Mon Jun  8 23:20:33 2020

stage2

2つ目のチュートリアルでは、ソースとヘッダの対が存在する一般的なC++実装のビルドを行います。ディレクトリ構成は以下の通りです。

stage2
  ├── main
  │   ├── BUILD
  │   ├── hello-world.cc
  │   ├── hello-greet.cc
  │   └── hello-greet.h
  └── WORKSPACE

相変わらずWORKSPACEファイルは空ファイルです。

hello-greet.hに関数が定義されており、その実装がhello-greet.ccにあります。これをhello-world.cc内のmain関数が呼び出しています。

BUILDファイルには以下のような記述がされています。

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)
 
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
    ],
)

ビルドしてみます。

$ bazel build //main:hello-greet
Loading: 
Loading: 0 packages loaded
Analyzing: target //main:hello-greet (1 packages loaded, 0 targets configured)
INFO: Analyzed target //main:hello-greet (1 packages loaded, 3 targets configured).
INFO: Found 1 target...
[0 / 4] [Prepa] BazelWorkspaceStatusAction stable-status.txt
Target //main:hello-greet up-to-date:
  bazel-bin/main/libhello-greet.a
  bazel-bin/main/libhello-greet.so
INFO: Elapsed time: 0.154s, Critical Path: 0.01s
INFO: 0 processes.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action

BUILDファイルの記述は上から順に実行されます。はじめにcc_libraryルールによってhello-greetがライブラリ化されます。コンソールに出力されている通り、静的ライブラリ(bazel-bin/main/libhello-greet.a)と共有ライブラリ(bazel-bin/main/libhello-greet.so)の両方が生成されます。cc_binaryルールのdeps:hello-greetを指定することで、静的ライブラリがリンクされます。

stage3

3つ目のチュートリアルでは複数のパッケージを扱います。ディレクトリ構成は以下の通りです。

stage3
  ├── main
  │   ├── BUILD
  │   ├── hello-world.cc
  │   ├── hello-greet.cc
  │   └── hello-greet.h
  ├── lib
  │   ├── BUILD
  │   ├── hello-time.cc
  │   └── hello-time.h
  └── WORKSPACE

最後までWORKSPACEファイルは空ファイルでした(嘘だよな…?)。

main/BUILDファイルには以下のような記述がされています。

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
 
cc_library(
    name = "hello-greet",
    srcs = ["hello-greet.cc"],
    hdrs = ["hello-greet.h"],
)
 
cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
    deps = [
        ":hello-greet",
        "//lib:hello-time",
    ],
)

また、lib/BUILDファイルには以下のような記述がされています。

load("@rules_cc//cc:defs.bzl", "cc_library")
 
cc_library(
    name = "hello-time",
    srcs = ["hello-time.cc"],
    hdrs = ["hello-time.h"],
    visibility = ["//main:__pkg__"],
)

Bazelでは別のパッケージのライブラリをリンクする場合、リンクされる側のパッケージで明示的にリンク先を指定することが推奨されています。これはルールの引数visibilityで指定することが可能であり、今回の場合、"//main:__pkg__"の記述が、mainディレクトリ直下に存在するパッケージにリンクを許可する(mainディレクトリ直下に存在するパッケージから見えるようになる)ことを意味しています。

visibilityの詳しい説明は以下に載っています。

例えば、"//visibility:public"を指定するとすべてのパッケージから見えるようになるようです。

dependency graph

なんとBazelを使うとdependency graphを簡単に出力することが出来ます。まず、必要なパッケージをインストールします。

$ sudo apt update && sudo apt install graphviz xdot

以下のコマンドを実行します。これは3つ目のチュートリアル(stage3)で出力したときの例です。

$ xdot <(bazel query --notool_deps --noimplicit_deps "deps(//main:hello-world)"  --output graph)
すごい!

これは嬉しい。

参考

Bazelお試し」への2件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です