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

はじめに

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 番目のステージでは、 1 つのパッケージからバイナリとライブラリの両方をビルドします。3 番目の最後のステージでは、複数のパッケージを含むプロジェクトをビルドし、複数のターゲットでビルドします。

概要: はじめに

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

スタートガイド

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

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

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

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

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

BUILD ファイルについて

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

BUILD ファイルを cpp-tutorial/stage1/main ディレクトリで見てみましょう。

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 は変更された部分のみを再ビルドし、プロジェクトの複数の部分を同時にビルドすることでビルドを高速化します。このチュートリアルのこのステージではターゲットを追加し、次のステージではパッケージを追加します。

ステージ 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 バイナリをビルドします。deps 属性は、hello-world ターゲットで、hello-greet ライブラリが hello-world バイナリをビルドするために必要であることを 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 つのソースファイルをビルドし、2 つの追加のソースファイルをビルドする別のターゲット(//main:hello-greet)に依存します。次のセクションでは、さらに一歩進んで 別のパッケージを追加します。

ステージ 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 の学習を続けるためのリソースをいくつかご紹介します。

ビルドをお楽しみください。