このチュートリアルでは、Bazel を使用した Java アプリケーションのビルドの基本について説明します。ワークスペースを設定し、Bazel の主なコンセプト(ターゲットや BUILD
ファイルなど)を示すシンプルな Java プロジェクトを構築します。
推定完了時間: 30 分。
学習内容
このチュートリアルでは、次の方法を学習します。
- ターゲットを作成する
- プロジェクトの依存関係を可視化する
- プロジェクトを複数のターゲットとパッケージに分割する
- パッケージ全体でターゲットの公開設定を制御する
- ラベルを使用してターゲットを参照する
- ターゲットをデプロイする
始める前に
Bazel をインストールする
チュートリアルを準備するには、まず Bazel をインストールします(まだインストールしていない場合)。
JDK をインストールする
Java JDK をインストールします(推奨バージョンは 11 ですが、8 ~ 15 のバージョンがサポートされています)。
JDK を参照するように JAVA_HOME 環境変数を設定します。
Linux/macOS の場合:
export JAVA_HOME="$(dirname $(dirname $(realpath $(which javac))))"
Windows の場合:
- コントロール パネルを開きます。
- [システムとセキュリティ] > [システム] > [システムの詳細設定] > [詳細設定] タブ > [環境変数...] に移動します。.
- [ユーザー変数] のリスト(最上部)で [新規...] をクリックします。
- [変数名] に「
JAVA_HOME
」と入力します。 - [Browse Directory...] をクリックします。
- JDK ディレクトリに移動します(例:
C:\Program Files\Java\jdk1.8.0_152
)。 - すべてのダイアログ ウィンドウで [OK] をクリックします。
サンプル プロジェクトを取得する
Bazel の GitHub リポジトリからサンプル プロジェクトを取得します。
git clone https://github.com/bazelbuild/examples
このチュートリアルのサンプル プロジェクトは examples/java-tutorial
ディレクトリにあり、次のように構成されています。
java-tutorial
├── BUILD
├── src
│ └── main
│ └── java
│ └── com
│ └── example
│ ├── cmdline
│ │ ├── BUILD
│ │ └── Runner.java
│ ├── Greeting.java
│ └── ProjectRunner.java
└── WORKSPACE
Bazel を使用したビルド
ワークスペースをセットアップする
プロジェクトをビルドするには、その前にワークスペースを設定する必要があります。ワークスペースは、プロジェクトのソースファイルと Bazel のビルド出力を格納するディレクトリです。また、Bazel が特殊なファイルとして認識するファイルも含まれています。
WORKSPACE
ファイル。ディレクトリとその内容を Bazel ワークスペースとして識別し、プロジェクトのディレクトリ構造のルートにあります。1 つ以上の
BUILD
ファイル。プロジェクトのさまざまな部分をビルドする方法を Bazel に指示します。(BUILD
ファイルを含むワークスペース内のディレクトリはパッケージです。パッケージについては、このチュートリアルの後半で説明します)。
ディレクトリを Bazel ワークスペースとして指定するには、そのディレクトリに WORKSPACE
という名前の空のファイルを作成します。
Bazel がプロジェクトをビルドする際は、すべての入力と依存関係を同じワークスペースにする必要があります。異なるワークスペースに存在するファイルは、リンクされていない限り互いに独立しているため、このチュートリアルでは扱いません。
BUILD ファイルを理解する
BUILD
ファイルには、Bazel に関するさまざまな種類の手順が含まれています。最も重要なタイプはビルドルールです。ビルドルールにより、実行可能なバイナリやライブラリなどの目的の出力のビルド方法を Bazel に指示されます。BUILD
ファイル内のビルドルールの各インスタンスはターゲットと呼ばれ、ソースファイルと依存関係の特定のセットを指しています。ターゲットは、他のターゲットを指すこともできます。
java-tutorial/BUILD
ファイルを確認します。
java_binary(
name = "ProjectRunner",
srcs = glob(["src/main/java/com/example/*.java"]),
)
この例では、ProjectRunner
ターゲットが Bazel の組み込み java_binary
ルールをインスタンス化します。このルールは、.jar
ファイルとラッパーシェル スクリプト(どちらもターゲットにちなんだ名前)をビルドするよう Bazel に指示します。
ターゲットの属性には、その依存関係とオプションが明示的に記述されています。name
属性は必須ですが、多くは省略可能です。たとえば、ProjectRunner
ルール ターゲットでは、name
はターゲットの名前、srcs
は Bazel がターゲットのビルドに使用するソースファイルを指定し、main_class
は main メソッドを含むクラスを指定します。(この例では、ソースファイルを 1 つずつリストするのではなく、glob を使用してソースファイルのセットを Bazel に渡します)。
プロジェクトをビルドする
サンプル プロジェクトをビルドするには、java-tutorial
ディレクトリに移動して次のコマンドを実行します。
bazel build //:ProjectRunner
ターゲット ラベルの //
の部分は、ワークスペースのルート(この場合はルート自体)からの BUILD
ファイルの場所です。ProjectRunner
は BUILD
ファイル内のターゲット名です。(ターゲット ラベルについては、このチュートリアルの最後に詳しく説明します)。
Bazel では、次のような出力が生成されます。
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 1.021s, Critical Path: 0.83s
これで、最初の Bazel ターゲットをビルドできました。Bazel は、ワークスペースのルートにある bazel-bin
ディレクトリにビルド出力を配置します。Bazel の出力構造については、その内容をご覧ください。
新たにビルドしたバイナリをテストします。
bazel-bin/ProjectRunner
依存関係グラフを確認する
Bazel では、ビルドの依存関係を BUILD ファイルで明示的に宣言する必要があります。 Bazel はこれらのステートメントを使用してプロジェクトの依存関係グラフを作成します。これにより、正確な増分ビルドが可能になります。
サンプル プロジェクトの依存関係を可視化するには、ワークスペースのルートで次のコマンドを実行して、依存関係グラフのテキスト表現を生成します。
bazel query --notool_deps --noimplicit_deps "deps(//:ProjectRunner)" --output graph
上記のコマンドは、ターゲット //:ProjectRunner
のすべての依存関係(ホストと暗黙的な依存関係を除く)を検索し、出力をグラフとしてフォーマットするよう Bazel に指示します。
コピーしたテキストを GraphViz に貼り付けます。
ご覧のとおり、プロジェクトには 1 つのターゲットがあり、追加の依存関係なしで 2 つのソースファイルをビルドします。
ワークスペースを設定し、プロジェクトをビルドし、その依存関係を確認したら、複雑さを増すことができます。
Bazel ビルドを改善する
小規模なプロジェクトには単一のターゲットで十分ですが、大規模なプロジェクトを複数のターゲットとパッケージに分割して、増分ビルド(変更された部分のみを再ビルド)を高速化し、プロジェクトの複数の部分を一度にビルドすることでビルドを高速化することもできます。
複数のビルド ターゲットを指定する
サンプル プロジェクトのビルドは、2 つのターゲットに分割できます。java-tutorial/BUILD
ファイルの内容を次のように置き換えます。
java_binary(
name = "ProjectRunner",
srcs = ["src/main/java/com/example/ProjectRunner.java"],
main_class = "com.example.ProjectRunner",
deps = [":greeter"],
)
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
)
この構成では、Bazel は最初に greeter
ライブラリをビルドし、次に ProjectRunner
バイナリをビルドします。java_binary
の deps
属性は、ProjectRunner
バイナリのビルドに greeter
ライブラリが必要であることを Bazel に伝えます。
この新しいバージョンのプロジェクトをビルドするには、次のコマンドを実行します。
bazel build //:ProjectRunner
Bazel では、次のような出力が生成されます。
INFO: Found 1 target...
Target //:ProjectRunner up-to-date:
bazel-bin/ProjectRunner.jar
bazel-bin/ProjectRunner
INFO: Elapsed time: 2.454s, Critical Path: 1.58s
新たにビルドしたバイナリをテストします。
bazel-bin/ProjectRunner
ここで ProjectRunner.java
を変更してプロジェクトを再ビルドすると、Bazel はそのファイルのみを再コンパイルします。
依存関係グラフを見ると、ProjectRunner
が以前と同じ入力に依存していますが、ビルドの構造が異なります。
これで、2 つのターゲットを持つプロジェクトがビルドされました。ProjectRunner
ターゲットは 2 つのソースファイルをビルドし、もう 1 つのターゲット(:greeter
)に依存しています。ターゲットは 1 つの追加のソースファイルをビルドします。
複数のパッケージを使用する
次に、プロジェクトを複数のパッケージに分割します。src/main/java/com/example/cmdline
ディレクトリを見ると、BUILD
ファイルと、いくつかのソースファイルが含まれていることがわかります。そのため、Bazel ではワークスペースに //src/main/java/com/example/cmdline
と //
の 2 つのパッケージが含まれるようになりました(ワークスペースのルートに BUILD
ファイルがあるため)。
src/main/java/com/example/cmdline/BUILD
ファイルを確認します。
java_binary(
name = "runner",
srcs = ["Runner.java"],
main_class = "com.example.cmdline.Runner",
deps = ["//:greeter"],
)
runner
ターゲットは //
パッケージ内の greeter
ターゲット(したがってターゲット ラベル //:greeter
)に依存します。Bazel は deps
属性を通じてこのことを認識します。依存関係グラフを見てみましょう。
ただし、ビルドを成功させるには、visibility
属性を使用して、//src/main/java/com/example/cmdline/BUILD
の runner
ターゲットの公開設定を //BUILD
のターゲットに明示的に付与する必要があります。これは、デフォルトでは、ターゲットは同じ BUILD
ファイル内の他のターゲットにのみ表示されるためです。(Bazel は、ターゲットの可視性を使用して、実装の詳細を含むライブラリが公開 API に漏洩するなどの問題を防ぎます)。
これを行うには、以下に示すように visibility
属性を java-tutorial/BUILD
の greeter
ターゲットに追加します。
java_library(
name = "greeter",
srcs = ["src/main/java/com/example/Greeting.java"],
visibility = ["//src/main/java/com/example/cmdline:__pkg__"],
)
これで、ワークスペースのルートで次のコマンドを実行して、新しいパッケージをビルドできます。
bazel build //src/main/java/com/example/cmdline:runner
Bazel では、次のような出力が生成されます。
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner.jar
bazel-bin/src/main/java/com/example/cmdline/runner
INFO: Elapsed time: 1.576s, Critical Path: 0.81s
新たにビルドしたバイナリをテストします。
./bazel-bin/src/main/java/com/example/cmdline/runner
これで、それぞれが 1 つのターゲットを含む 2 つのパッケージとしてビルドするようにプロジェクトを変更し、それらの依存関係を把握できるようになりました。
ラベルを使用してターゲットを参照する
BUILD
ファイルとコマンドラインで、Bazel はターゲット ラベルを使用してターゲットを参照します(例: //:ProjectRunner
、//src/main/java/com/example/cmdline:runner
)。それらの構文は次のとおりです。
//path/to/package:target-name
ターゲットがルール ターゲットの場合、path/to/package
は BUILD
ファイルを含むディレクトリのパスです。target-name
は、BUILD
ファイルでターゲットに指定した名前(name
属性)です。ターゲットがファイル ターゲットの場合、path/to/package
はパッケージのルートのパス、target-name
はターゲット ファイルの名前(フルパスを含む)です。
リポジトリのルートでターゲットを参照する場合、パッケージパスは空です。//:target-name
を使用してください。同じ BUILD
ファイル内のターゲットを参照する場合は、//
ワークスペースのルート識別子をスキップして、:target-name
のみを使用することもできます。
たとえば、java-tutorial/BUILD
ファイル内のターゲットの場合、ワークスペースのルート自体がパッケージ(//
)であり、2 つのターゲット ラベルは単に //:ProjectRunner
と //:greeter
であるため、パッケージ パスを指定する必要はありません。
ただし、//src/main/java/com/example/cmdline/BUILD
ファイル内のターゲットの場合、//src/main/java/com/example/cmdline
のフルパッケージ パスを指定する必要があり、ターゲット ラベルは //src/main/java/com/example/cmdline:runner
でした。
デプロイ用の Java ターゲットをパッケージ化する
次に、すべてのランタイム依存関係を含むバイナリをビルドして、Java ターゲットをデプロイ用にパッケージ化します。これにより、開発環境外でバイナリを実行できます。
すでに説明したように、java_binary ビルドルールは .jar
とラッパーシェル スクリプトを生成します。次のコマンドを使用して、runner.jar
の内容を確認します。
jar tf bazel-bin/src/main/java/com/example/cmdline/runner.jar
内容は次のとおりです。
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
ご覧のとおり、runner.jar
には Runner.class
が含まれていますが、依存関係 Greeting.class
は含まれていません。Bazel が生成した runner
スクリプトは、クラスパスに greeter.jar
を追加するので、そのままにするとローカルでは実行されますが、別のマシンではスタンドアロンでは実行されません。幸いなことに、java_binary
ルールを使用すると、自己完結型のデプロイ可能なバイナリをビルドできます。これをビルドするには、ターゲット名に _deploy.jar
を追加します。
bazel build //src/main/java/com/example/cmdline:runner_deploy.jar
Bazel では、次のような出力が生成されます。
INFO: Found 1 target...
Target //src/main/java/com/example/cmdline:runner_deploy.jar up-to-date:
bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
INFO: Elapsed time: 1.700s, Critical Path: 0.23s
これで runner_deploy.jar
がビルドされました。必要なランタイム依存関係が含まれているため、開発環境からスタンドアロンで実行できます。前と同じコマンドを使用して、このスタンドアロン JAR の内容を確認します。
jar tf bazel-bin/src/main/java/com/example/cmdline/runner_deploy.jar
コンテンツには、実行に必要なすべてのクラスが含まれています。
META-INF/
META-INF/MANIFEST.MF
build-data.properties
com/
com/example/
com/example/cmdline/
com/example/cmdline/Runner.class
com/example/Greeting.class
関連情報
詳しくは以下をご覧ください。
rules_jvm_external: 推移的な Maven 依存関係を管理するルール用
外部依存関係: ローカル リポジトリとリモート リポジトリの操作の詳細を確認する。
Bazel の詳細に関するその他のルール。
Bazel で C++ プロジェクトのビルドを開始するための C++ ビルド チュートリアル。
Bazel を使用した Android / iOS 向けモバイルアプリの構築を始めるための Android アプリのチュートリアルと iOS アプリのチュートリアル。
ご利用をお待ちしております。