You can find the source code of tutorial in bazel-101 branch.
What will you learn?
- Introduction to bazel build system
- How to build and run Java package?
- How to add maven dependency to bazel build files?
- How to add protobuf compiler to bazel build?
Introduction
Bazel is imperative build system that can build packages for Java, C++, Python, Ruby, Go, etc … The two main advantages of bazel,
- One build tool can build packages for variety of languages and easier for platform teams to build packages across variety of languages. Consider learning many different build systems - Pip, bundle, maven, etc…
- Bazel build system can cache already built packages in a remote or local environment and can reuse it without compiling be it for binary, library, or tests.
The main difference between bazel and other build/dependency management systems is imperative vs declarative approach.
Consider a Java package sample
with the following structure with one file Sample.java
$ls_custom
.
BUILD
src/main/java/com/example/Sample.java
WORKSPACE
Every project contains one WORKSPACE
file and contains one or many BUILD
files.
One BUILD
file for a package. In the example project, BUILD
WORKSPACE: A directory containing a WORKSPACE file and source code for the software you want to build. Labels that start with // are relative to the workspace directory.
WORKSPACE FILE: Defines a directory to be a workspace. The file can be empty, although it usually contains external repository declarations to fetch additional dependencies from the network or local filesystem.
A BUILD file is the main configuration file that tells Bazel what software outputs to build, what their dependencies are, and how to build them. Bazel takes a BUILD file as input and uses the file to create a graph of dependencies and to derive the actions that must be completed to build intermediate and final software outputs. A BUILD file marks a directory and any sub-directories not containing a BUILD file as a package, and can contain targets created by rules. The file can also be named BUILD.bazel.
The Sample.java
file looks like
package com.example;
public class Sample{
public static void main(String[] args) {
String label = "Krace";
System.out.println(String.format("Hello: %s", label));
}
}
Build the target
Now let’s build the java binary and execute it. The Sample.java file has no external dependency.
Assuming the bazel is installed, let’s write imperative code to build the binary package(BUILD file).
java_binary(
name = "Sample",
srcs = glob(["src/main/java/com/example/*.java"]),
)
In BUILD file mention, it’s a Java binary by invoking java_binary
function, name the package as Sample
and source files as srcs=glob(["src/main/java/com/example/*.java"])
.
All the java files inside src/main/java/com/example
directory is part of the package Sample
.
Now build the package using bazel build <target>
syntax.
$bazel build Sample
INFO: Analyzed target //:Sample (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:Sample up-to-date:
bazel-bin/Sample.jar
bazel-bin/Sample
INFO: Elapsed time: 0.046s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
The command built the package without any error and the target is generated in bazel-bin
directory.
Run the target
Now run the target using bazel run <target>
or ./bazel-bin/Sample
./bazel-bin/Sample
Hello: Krace
When you invoke bazel run <target>
, bazel build the package and executes it(uses the cache, if there is no change).
$ bazel run Sample
INFO: Analyzed target //:Sample (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:Sample up-to-date:
bazel-bin/Sample.jar
bazel-bin/Sample
INFO: Elapsed time: 0.046s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
Hello: Krace
Similar to java_binary
, notable functions are java_library
, java_test
.
Add a dependency from the maven repository
Bazel has rules and definition for how to download and build the packages that are distributed to the repositories like maven or zip files.
To download file from maven repository, bazel needs to some information about the repository and it’s structure.
In WORKSPACE
file, you can details about the maven bazel rules and what to packages are required.
Let’s add okhttp3
from maven as dependency to Sample Project
.
Update WORKSPACE file
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "4.2"
RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca"
http_archive(
name = "rules_jvm_external",
sha256 = RULES_JVM_EXTERNAL_SHA,
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
rules_jvm_external_deps()
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
rules_jvm_external_setup()
load("@rules_jvm_external//:defs.bzl", "maven_install")
maven_install(
artifacts = [
# https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
"com.squareup.okhttp3:okhttp:jar:4.10.0",
],
repositories = [
"https://repo1.maven.org/maven2",
],
)
That’s a lot of copy-paste code!
- First the bazel workspace loads
http
build file located inbazeltools
repo. - Set some rules for JVM, external dependencies and external setup.
- Then workspace loads
maven_install
function. maven_install
function specifies the dependency and repository location for installation.
Update the BUILD file
Now the workspace knows what to load for the project, now let’s update the build file.
Most of the heavy lifting happens in the WORKSPACE file.
In build file, mention the dependency to load using deps
parameter to the function java_binary
.
java_binary(
name = "Sample",
srcs = glob(["src/main/java/com/example/*.java"]),
deps = [
"@maven//:com_squareup_okhttp3_okhttp",
],
)
@maven
indicates the dependency is a maven install. And in the name, .
in artifcat becomes _
.
bazel build Sample
INFO: Analyzed target //:Sample (41 packages loaded, 753 targets configured).
INFO: Found 1 target...
Target //:Sample up-to-date:
bazel-bin/Sample.jar
bazel-bin/Sample
INFO: Elapsed time: 5.997s, Critical Path: 2.27s
INFO: 20 processes: 7 internal, 10 linux-sandbox, 3 worker.
INFO: Build completed successfully, 20 total actions
Add protobuf as dependency
Similar to maven rules, it’s possible to download any dependency from the internet and add it as dependency.
Protobuf is a binary data serialization format for communicating with services. Since it’s a binary format, the proto buffer compiler generates the java class to encode and decode.
Let’s add a proto buf definition to the project and use it.
Create a new directory protos
in example directory and add label.proto
.
A simple Label with list of names. Extra option configuration is to generate java class from proto definition.
syntax = "proto3";
package example;
option java_multiple_files = true;
option java_package = "com.example.protos";
option java_outer_classname = "LabelProtos";
message Label {
repeated string names = 1;
}
Add following lines to WORKSPACE
file
# proto
# rules_cc defines rules for generating C++ code from Protocol Buffers.
http_archive(
name = "rules_cc",
sha256 = "35f2fb4ea0b3e61ad64a369de284e4fbbdcdba71836a5555abb5e194cf119509",
strip_prefix = "rules_cc-624b5d59dfb45672d4239422fa1e3de1822ee110",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_cc/archive/624b5d59dfb45672d4239422fa1e3de1822ee110.tar.gz",
"https://github.com/bazelbuild/rules_cc/archive/624b5d59dfb45672d4239422fa1e3de1822ee110.tar.gz",
],
)
http_archive(
name = "rules_java",
sha256 = "ccf00372878d141f7d5568cedc4c42ad4811ba367ea3e26bc7c43445bbc52895",
strip_prefix = "rules_java-d7bf804c8731edd232cb061cb2a9fe003a85d8ee",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_java/archive/d7bf804c8731edd232cb061cb2a9fe003a85d8ee.tar.gz",
"https://github.com/bazelbuild/rules_java/archive/d7bf804c8731edd232cb061cb2a9fe003a85d8ee.tar.gz",
],
)
# rules_proto defines abstract rules for building Protocol Buffers.
http_archive(
name = "rules_proto",
sha256 = "2490dca4f249b8a9a3ab07bd1ba6eca085aaf8e45a734af92aad0c42d9dc7aaf",
strip_prefix = "rules_proto-218ffa7dfa5408492dc86c01ee637614f8695c45",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/218ffa7dfa5408492dc86c01ee637614f8695c45.tar.gz",
"https://github.com/bazelbuild/rules_proto/archive/218ffa7dfa5408492dc86c01ee637614f8695c45.tar.gz",
],
)
load("@rules_cc//cc:repositories.bzl", "rules_cc_dependencies")
rules_cc_dependencies()
load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
rules_java_dependencies()
rules_java_toolchains()
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
rules_proto_dependencies()
rules_proto_toolchains()
So many rules and setup for proto conversion and java specific instructions!
protobuf BUILD instructions and example
Now add the proto build instructions in BUILD
file
- Load the bazel definition for proto library and java proto library.
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_java//java:defs.bzl", "java_proto_library")
- Convert proto definition and generate java code
proto_library(
name = "label_proto",
srcs = ["src/main/java/com/example/protos/label.proto"],
)
java_proto_library(
name = "label_java_proto",
deps = [":label_proto"],
)
- Update the
deps
injava_binary
function call to include the generate java code.
deps = [
":label_java_proto",
"@maven//:com_squareup_okhttp3_okhttp",
],
- Modify the
Sample.java
code to use generated Java class
package com.example;
import com.example.protos.Label;
import java.util.ArrayList;
public class Sample{
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<String>();
names.add("Adult!");
names.add("Programmer");
Label.Builder builder = Label.newBuilder();
builder.addAllNames(names);
Label label = builder.build();
System.out.println(String.format("Hello: %s", label));
}
}
- Run the target.
bazel run Sample
INFO: Analyzed target //:Sample (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:Sample up-to-date:
bazel-bin/Sample.jar
bazel-bin/Sample
INFO: Elapsed time: 0.501s, Critical Path: 0.45s
INFO: 5 processes: 1 internal, 2 linux-sandbox, 2 worker.
INFO: Build completed successfully, 5 total actions
INFO: Build completed successfully, 5 total actions
Hello: names: "Adult!"
names: "Programmer"
Common beginner mistakes
- Using wrong function in BUILD and WORKSPACE.
- Not loading relevant load functions or rules.
- Missing out dependency in deps.
Conclusion
There are a lot of more important concepts like visibility, local dependency
that’s skipped.
Another tutorial for another day.
Bazel is definitely confusing and powerful build system that can make you hate building the package. In my opinion, learning bazel is like learning new programming language with step-learning curves.
References
- Bazel build for java
- Bazel http_archive
- Bazel Maven integration
- OkHTTP Maven repository
- Bazel JVM rules
- Protobuf in Bazel
- Bazel Proto library
- Bazel proto rules
- Repo with source code
- Proto buf
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.