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

問題を報告する ソースを表示

はじめに

Bazel を初めて使用する場合ここで紹介する情報が役に立つでしょう。Bazel の使用に関する簡単な概要については、この最初のビルド チュートリアルをご覧ください。このチュートリアルでは、Bazel のコンテキストで使用されている主な用語を定義し、Bazel ワークフローの基本について説明します。まずは必要なツールから始め、複雑さを増しながら 3 つのプロジェクトをビルドして実行し、より複雑になる仕組みと理由について学習します。

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

所要時間: 30 分

Prerequisites

まだ行っていない場合は、まず 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 つのファイルセットがあり、各セットはこのチュートリアルのステージを表します。最初のステージでは、単一のパッケージ内に存在する単一のターゲットを作成します。第 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 の構成は次のようになります。

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 は迅速なビルド、つまり 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 はまず Bazel の組み込み cc_library rule を使用して hello-greet ライブラリをビルドし、次に 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-worldhello-greet という追加入力に依存していることがわかります。

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

まとめ: ステージ 2

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

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

次のステージでは、ウォッチフェイスの追加機能レイヤがもう 1 つ追加され、複数のパッケージを持つプロジェクトが作成されます。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 にはワークスペースが 2 つのパッケージ(libmain)が含まれています。

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 を使用した学習を継続するためのその他のリソース:

ぜひご活用ください。