Bazel チュートリアル: C++ プロジェクトをビルドする

7.3 · 7.2 · 7.1 · 7.0 · 6.5

はじめに

Bazel は初めてですか?お問い合わせいただきありがとうございます。Bazel の使用方法を簡単に説明した最初のビルドのチュートリアルをご覧ください。このチュートリアルでは、Bazel のコンテキストで使用される重要な用語を定義し、Bazel ワークフローの基本について説明します。必要なツールから始めて、複雑さを増す 3 つのプロジェクトを構築して実行し、複雑になる仕組みと理由を学びます。

Bazel はマルチリンガル ビルドをサポートするビルドシステムですが、このチュートリアルでは C++ プロジェクトを例として使用し、ほとんどの言語に適用される一般的なガイドラインとフローを説明します。

所要時間: 30 分

前提条件

まだインストールしていない場合は、まず Bazel をインストールします。このチュートリアルでは Git をソース管理に使用します。最適な結果を得るには、Git をインストールしてください。

次に、任意のコマンドライン ツールで次のコマンドを実行して、Bazel の GitHub リポジトリからサンプル プロジェクトを取得します。

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

このチュートリアルのサンプル プロジェクトは、examples/cpp-tutorial ディレクトリにあります。

以下に、その構造を示します。

examples
└── cpp-tutorial
    ├──stage1
      ├── main
         ├── BUILD
         └── hello-world.cc
      └── WORKSPACE
    ├──stage2
      ├── main
         ├── BUILD
         ├── hello-world.cc
         ├── hello-greet.cc
         └── hello-greet.h
      └── WORKSPACE
    └──stage3
       ├── main
          ├── BUILD
          ├── hello-world.cc
          ├── hello-greet.cc
          └── hello-greet.h
       ├── lib
          ├── BUILD
          ├── hello-time.cc
          └── hello-time.h
       └── WORKSPACE

3 つのファイルセットがあり、このチュートリアルでは、各セットがステージを表しています。最初のステージでは、1 つのパッケージに存在する 1 つのターゲットを構築します。2 番目のステージでは、単一のパッケージからバイナリとライブラリの両方をビルドします。3 つ目のステージ(最後のステージ)では、複数のパッケージを含むプロジェクトをビルドし、複数のターゲットでビルドします。

概要: 概要

Bazel(および Git)をインストールし、このチュートリアル用のリポジトリのクローンを作成することで、Bazel を使用した最初のビルドの基盤ができました。次のセクションに進んで用語を定義し、ワークスペースを設定します。

スタートガイド

ワークスペースを設定する

プロジェクトを作成する前に、ワークスペースを設定する必要があります。ワークスペースは、プロジェクトのソースファイルと Bazel のビルド出力を保持するディレクトリです。また、次の重要なファイルも含まれています。

  • WORKSPACE file 。ディレクトリとその内容を Bazel ワークスペースとして識別し、プロジェクトのディレクトリ構造のルートに存在します。
  • 1 つ以上の BUILD files 。プロジェクトのさまざまな部分をビルドする方法を Bazel に指示します。ワークスペース内の BUILD ファイルを含むディレクトリはパッケージです。(パッケージの詳細については、このチュートリアルの後半で説明します)。

今後のプロジェクトでディレクトリを Bazel ワークスペースとして指定するには、そのディレクトリに WORKSPACE という名前の空のファイルを作成します。このチュートリアルでは、各ステージに WORKSPACE ファイルがすでに存在します。

: Bazel がプロジェクトをビルドする場合、すべての入力が同じワークスペースに存在している必要があります。異なるワークスペースに存在するファイルは、リンクされていない限り、互いに独立しています。ワークスペース ルールの詳細については、こちらのガイドをご覧ください。

BUILD ファイルを理解する

BUILD ファイルには、Bazel 用のさまざまなタイプの指示が含まれています。各 BUILD ファイルには、一連の手順として少なくとも 1 つのルールが必要です。このルールは、実行可能バイナリやライブラリなどの目的の出力のビルド方法を Bazel に指示します。BUILD ファイル内のビルドルールの各インスタンスはターゲットと呼ばれ、特定のソースファイルと依存関係のセットを参照します。ターゲットは他のターゲットを参照することもできます。

cpp-tutorial/stage1/main ディレクトリの BUILD ファイルを確認します。

cc_binary(
    name = "hello-world",
    srcs = ["hello-world.cc"],
)

この例では、hello-world ターゲットが Bazel の組み込み cc_binary rule をインスタンス化しています。このルールは、依存関係のない hello-world.cc ソースファイルから自己完結型の実行可能バイナリをビルドするよう Bazel に指示します。

概要: スタートガイド

これで、いくつかの重要な用語と、このプロジェクトと Bazel 全般のコンテキストでの意味を理解できました。次のセクションでは、プロジェクトのステージ 1 をビルドしてテストします。

ステージ 1: 単一ターゲット、単一パッケージ

いよいよプロジェクトの第 1 部を完成します。プロジェクトの Stage 1 セクションの構造は次のとおりです。

examples
└── cpp-tutorial
    └──stage1
       ├── main
          ├── BUILD
          └── hello-world.cc
       └── WORKSPACE

次のコマンドを実行して cpp-tutorial/stage1 ディレクトリに移動します。

cd cpp-tutorial/stage1

続いて、次のコマンドを実行します。

bazel build //main:hello-world

ターゲット ラベルの //main: 部分は、ワークスペースのルートからの BUILD ファイルの場所であり、hello-worldBUILD ファイル内のターゲット名です。

Bazel の出力は次のようになります。

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.267s, Critical Path: 0.25s

これで、最初の Bazel ターゲットがビルドされました。Bazel は、ビルド出力をワークスペースのルートにある bazel-bin ディレクトリに配置します。

新しくビルドしたバイナリをテストします。バイナリは次のとおりです。

bazel-bin/main/hello-world

これにより、「Hello world」というメッセージが出力されます。

ステージ 1 の依存関係グラフは次のとおりです。

hello-world の依存関係グラフには、単一のソースファイルを持つ単一のターゲットが表示されます。

概要: ステージ 1

最初のビルドが完了したので、ビルドの構造について基本的な理解ができました。次のステージでは、別のターゲットを追加して複雑さを増やします。

ステージ 2: 複数のビルド ターゲット

小規模なプロジェクトでは 1 つのターゲットで十分ですが、大規模なプロジェクトは複数のターゲットとパッケージに分割することをおすすめします。これにより、増分ビルドを高速化できます。つまり、Bazel は変更された部分のみを再ビルドします。また、プロジェクトの複数の部分を一度にビルドすることで、ビルドを高速化できます。このチュートリアルのこのステージではターゲットを追加し、次のステージではパッケージを追加します。

これはステージ 2 で使用するディレクトリです。

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

cpp-tutorial/stage2/main ディレクトリの BUILD ファイルは次のようになります。

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",
    ],
)

この BUILD ファイルを使用すると、Bazel はまず hello-greet ライブラリをビルドし(Bazel の組み込み cc_library rule を使用して)、次に hello-world バイナリをビルドします。hello-world ターゲットの deps 属性は、hello-world バイナリのビルドに hello-greet ライブラリが必要であることを Bazel に伝えます。

この新しいバージョンのプロジェクトをビルドする前に、ディレクトリを変更して cpp-tutorial/stage2 ディレクトリに切り替える必要があります。次のコマンドを実行します。

cd ../stage2

これで、以下の使い慣れたコマンドを使用して新しいバイナリをビルドできるようになりました。

bazel build //main:hello-world

再度、Bazel は次のような出力を生成します。

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 2.399s, Critical Path: 0.30s

これで、新たにビルドされたバイナリをテストできるようになりました。これにより、別の「Hello world」が返されます。

bazel-bin/main/hello-world

hello-greet.cc を変更してプロジェクトを再ビルドすると、Bazel はそのファイルのみを再コンパイルします。

依存関係グラフを見ると、hello-world は以前と同じ入力に依存していますが、ビルドの構造は異なります。

「hello-world」の依存関係グラフには、ファイルの変更後の構造の変更が表示されます。

概要: ステージ 2

これで、2 つのターゲットを含むプロジェクトが作成されました。hello-world ターゲットは 1 つのソースファイルをビルドし、他の 1 つのターゲット(//main:hello-greet)に依存します。このターゲットは 2 つの追加のソースファイルをビルドします。次のセクションでは、さらに進んで別のパッケージを追加します。

ステージ 3: 複数のパッケージ

この次のステージでは、複雑さがさらに増し、複数のパッケージを含むプロジェクトをビルドします。cpp-tutorial/stage3 ディレクトリの構造と内容は次のとおりです。

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

2 つのサブディレクトリがあり、それぞれに BUILD ファイルが含まれていることがわかります。したがって、Bazel から見ると、ワークスペースには libmain の 2 つのパッケージが含まれています。

lib/BUILD ファイルを確認します。

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

main/BUILD ファイルで次のようにします。

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",
    ],
)

メイン パッケージの hello-world ターゲットは、lib パッケージの hello-time ターゲットに依存しています(ターゲットラベル //lib:hello-time がそのことを示しています)。Bazel は deps 属性を通じてこれを認識します。これは依存関係グラフに反映されています。

「hello-world」の依存関係グラフは、メイン パッケージ内のターゲットが「lib」パッケージ内のターゲットに依存する仕組みを示しています。

ビルドを成功させるには、visibility 属性を使用して、lib/BUILD//lib:hello-time ターゲットを main/BUILD のターゲットに明示的に表示します。これは、デフォルトではターゲットが同じ BUILD ファイル内の他のターゲットにのみ表示されるためです。Bazel はターゲットの可視性を使用して、実装の詳細を含むライブラリが公開 API に漏れるなどの問題を防ぎます。

プロジェクトのこの最終バージョンをビルドします。次のコマンドを実行して、cpp-tutorial/stage3 ディレクトリに切り替えます。

cd  ../stage3

再度、次のコマンドを実行します。

bazel build //main:hello-world

Bazel の出力は次のようになります。

INFO: Found 1 target...
Target //main:hello-world up-to-date:
  bazel-bin/main/hello-world
INFO: Elapsed time: 0.167s, Critical Path: 0.00s

次に、このチュートリアルの最後のバイナリで、最終的な Hello world メッセージをテストします。

bazel-bin/main/hello-world

概要: ステージ 3

これで、3 つのターゲットを含む 2 つのパッケージとしてプロジェクトがビルドされ、それらの間の依存関係が理解されました。これにより、今後 Bazel でプロジェクトをビルドする準備が整いました。次のセクションでは、Bazel の学習を継続する方法について説明します。

次のステップ

これで、Bazel を使用した最初の基本的なビルドが完了しました。しかし、これはほんの始まりにすぎません。Bazel についてさらに学習するためのリソースを以下にご紹介します。

ぜひ活用してみてください。