1. Introduction
What is Bazel?
Bazel is an open-source build and test tool similar to Make, Maven, and Gradle. The Google-internal version of Bazel, Blaze, is used to build major apps such as Google Maps and YouTube. Bazel uses a human-readable, high-level build language that supports multiple languages and platforms. There is virtually no limit on the size of the codebase or the number of uses that Bazel supports. See the product roadmap to learn about Bazel's future plans.
Why should I use Bazel?
- High-level build language. Bazel uses an abstract, human-readable language to describe the build properties of your project at a high semantical level. Unlike other tools, Bazel is declarative. Rather than defining individual build steps for your project, your tell Bazel about your project and it creates the build actions behind the scenes. This shields you from the complexity of writing individual calls to tools such as compilers and linkers.
- Bazel is fast and reliable. Bazel caches all previously done work and tracks changes to both file content and build commands. This way, Bazel knows when something needs to be rebuilt, and rebuilds only that. To further speed up your builds, you can set up your project to build in a highly parallel and incremental fashion.
- Bazel is reproducible. Running the same Bazel build twice will always give you the same result, bit-for-fit.
- Bazel is multi-platform. Bazel can build binaries and deployable packages for multiple platforms, including desktop, server, and mobile, from the same project.
- Bazel scales. Bazel maintains agility while handling builds with 100k+ source files. It works with multiple repositories and user bases in the tens of thousands.
What will I learn?
In this codelab you will learn how to use Bazel as a core tool in your Android development flow. You will learn:
- The core Bazel concepts and constructs
- How to structure your project
- How to build your app with Bazel
- How to rapidly iterate during development
How familiar are you with Bazel?
How will you use this codelab?
2. Get started
You'll need Bazel and the Android SDK to complete this codelab, along with our sample project.
Install Bazel
Follow the installation instructions to install Bazel and its dependencies.
Install the Android SDK via Android Studio
Download and install Android Studio as described in Install Android Studio.
The installer does not automatically set the ANDROID_HOME
variable. Set it to the location of the Android SDK, which defaults to $HOME/Library/Android/sdk/
on macOS.
For example:
export ANDROID_HOME=$HOME/Library/Android/sdk/
For convenience, add the above statement to your ~/.bashrc
file:
echo "export ANDROID_HOME=$HOME/Library/Android/sdk/" >> ~/.bashrc
Get the sample project
You'll need to get the sample project from GitHub. The repo has two directories: sample
and solution
. The sample
directory contains the base sample app code, while the solution
directory contains the finished project with the completed Bazel WORKSPACE
and BUILD
files. You can use this directory to check your work.
Enter the following at the command line:
cd $HOME git clone https://github.com/googlecodelabs/bazel-android-intro
The git clone
command creates directories named $HOME/bazel-android-intro/sample
and $HOME/bazel-android-intro/solution
. We'll be working in the sample
subdirectory.
3. Set up a workspace
A workspace is a directory that contains the source files for one or more software projects, as well as a WORKSPACE
file at its root and BUILD
files that contain the instructions that Bazel uses to build the software.
A workspace directory can be located anywhere on your filesystem and is denoted by the presence of the WORKSPACE
file. In this codelab, your workspace directory is $HOME/bazel-android-intro/sample/
, which contains the sample project files you cloned from the GitHub repo in the previous step.
Note that Bazel itself doesn't make any requirements about how you organize source files in your workspace. The sample source files in this codelab are organized according to Android conventions.
For your convenience, set the $WORKSPACE
environment variable now to refer to your workspace directory. At the command line, enter:
export WORKSPACE=$HOME/bazel-android-intro/sample cd $WORKSPACE
Create a WORKSPACE file
Every workspace must have a text file named WORKSPACE
located in the top-level workspace directory. For some languages the WORKSPACE
file can be empty, but for Android projects Bazel requires the Android Build Rules to be loaded from Github and the Android SDK tools to be configured. These are used to build your Android application.
Create your WORKSPACE
file:
vi $WORKSPACE/WORKSPACE
And add the following lines:
# Load the Android build rules
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "build_bazel_rules_android",
urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"],
sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
strip_prefix = "rules_android-0.1.1",
)
# Configure Android SDK Path
load("@build_bazel_rules_android//android:rules.bzl", "android_sdk_repository")
android_sdk_repository(
name = "androidsdk",
path = "/Users/codelab/Library/Android/sdk", # Path to Android SDK, optional if $ANDROID_HOME is set
)
The Android Build Rules are written in Starlark, Bazel's extension mechanism. The first section of the WORKSPACE
file configures the project to use the latest version of the rules from Github. As new versions of the rules are released the WORKSPACE
file must be updated. This allows you to update at your own pace and prevents unwanted breakages.
The second section of the WORKSPACE
file configures the Android SDK. This will use the Android SDK referenced by path
, and automatically detect the highest API level and the latest version of build tools installed within that location.
4. Review Source Files
Let's take a look at the source files for the app. These are located in $WORKSPACE/mediarecorder/
.
The key files and directories are:
Name | Location |
Manifest file |
|
Activity source file |
|
Resource file directory |
|
Common library |
|
Note that you're just looking at these files now to become familiar with the structure of the app. You don't have to edit any of the source files to complete this codelab.
5. Create the BUILD files
A BUILD
file is a text file that describes the relationship between a set of build outputs – for example, compiled software libraries or executables – and their dependencies. These dependencies may be source files in your workspace or other build outputs. BUILD
files are written in Starlark, a Python-inspired language.
In Bazel, adding a BUILD
file to a directory in your workspace turns it into a package. Adding BUILD
files to subdirectories of the package turns them into subpackages. This forms the package hierarchy. The package hierarchy is a logical structure that overlays the directory structure in your workspace. Each package is a directory (and its subdirectories) that contains a related set of source files and a BUILD
file. The package also includes any subdirectories, excluding those that contain their own BUILD
file. The package name is the name of the directory where the BUILD
file is located.
Note that this package hierarchy is distinct from, but coexists with, the Java package hierarchy for your Android app.
In most cases, each directory in your project will have its own BUILD
file. For this project we will create two BUILD
files: one for the common library and one for the main application.
Add an android_library rule for the common library
Let's start with the common library. At a command-line prompt, open a new BUILD
file for editing:
vi $WORKSPACE/mediarecorder/java/com/example/android/common/media/BUILD
A BUILD
file contains several different types of instructions for Bazel. The most important type is the build rule, which tells Bazel how to build an intermediate or final software output from a set of source files or other dependencies.
The Android Starlark Rules provide two primary build rules, android_library
and android_binary
, that you can use to build an Android app. For this codelab, you'll first use the android_library
rule to tell Bazel how to build an Android library module from the app source code and resource files. Then you'll use the android_binary
rule to tell it how to build the Android application package.
Add the following to your BUILD
file:
load("@build_bazel_rules_android//android:rules.bzl", "android_library")
android_library(
name = "media",
srcs = glob(["*.java"]),
visibility = ["//mediarecorder:__subpackages__"],
)
As you can see, the android_library
build rule contains a set of attributes that specify the information that Bazel needs to build a library module from the source files. It contains:
name
: unique name (within this BUILD file) for this targetsrcs
: list of source files (.java
or.srcjar
)visibility
: controls which other packages can depend on this rule
The name
attribute forms part of the target label
, which is a combination of the package name (mediarecorder/java/com/example/android/common/media
) and the name (media
). The full label for this target is thus //mediarecorder/java/com/example/android/common/media:media
. Labels are used to reference targets from other rules, as you'll see in the next section.
We set the visibility
attribute to allow all subpackages of our project, //mediarecorder
, to depend on this rule. There are a number of built-in visibility labels, such as //visibility:public
which allows anyone to depend on the target. In most cases you will want to specify which packages are allowed to depend on your targets, to provide proper encapsulation for modules in your project.
Save and close this file.
Add an android_library rule for the main activity
At a command-line prompt, open a new BUILD
file for editing:
vi $WORKSPACE/mediarecorder/java/com/example/android/mediarecorder/BUILD
Add the following to your BUILD
file:
load("@build_bazel_rules_android//android:rules.bzl", "android_library")
android_library(
name = "main",
srcs = ["MainActivity.java"],
manifest = "AndroidManifest.xml",
resource_files = glob(["res/**"]),
deps = [
"//mediarecorder/java/com/example/android/common/media:media",
],
)
Once again we create an android_library
rule, but this time we supply more information about the application by referencing the manifest and the resources. Here, the deps
attribute references the output of the media
rule you added to the BUILD
file above. This means that when Bazel builds the output of this rule, it first checks whether the output of the media
library rule has been built and is up-to-date. If not, it builds it and then uses that output to build the main library.
Save this file, but don't close. We'll be adding another rule in the next step.
Add an android_binary rule
The android_binary
rule builds the Android application package (.apk
file) for your app.
Add the following to the same BUILD
file from above:
load("@build_bazel_rules_android//android:rules.bzl", "android_binary")
android_binary(
name = "mediarecorder",
manifest = "AndroidManifest.xml",
deps = [":main"],
)
The android_binary
rule references one or multiple android_library
rules, and will package them all into a single APK file. In this case we just reference the main
library defined above.
You can also merge the load
statements, as both rules come from the same ruleset. Add the following to the beginning of the file, and remove the two other load
s:
load("@build_bazel_rules_android//android:rules.bzl", "android_binary", "android_library")
Now, save and close the file. You can compare both BUILD
files to the completed examples in the solutions
directory of the GitHub repo.
6. Using third-party libraries
So far we have created targets using the android_library
and android_binary
rules for our own source code. But what if you want to use an external library? At a high level there are two ways to depend on external libraries, and while we don't need any external libraries for this codelab, it's useful to be familiar with these rules.
Importing an .aar file
Bazel provides the aar_import
rule that allows an android_library
to directly depend on local .aar
files. For example:
load("@build_bazel_rules_android//android:rules.bzl", "aar_import", "android_library")
aar_import(
name = "some-sdk",
aar = "path/to/sdk-1.0.aar",
)
android_library(
name = "some_library",
srcs = glob(["*.java"]),
deps = [":some-sdk"],
)
External dependencies
Bazel can also depend on targets from other projects known as external dependencies. Both Bazel and non-Bazel projects are supported and Bazel is able to reference external dependencies on remote source repositories such as GitHub and Maven. For details, see the external dependencies topic on the Bazel site.
7. Build the app
You use the bazel
command-line tool to run builds, execute unit tests and perform other operations in Bazel. During installation you probably added this location to your path.
Before you build the app, make sure your current working directory is inside your Bazel workspace. Then execute a bazel build
command:
cd $WORKSPACE bazel build //mediarecorder/java/com/example/android/mediarecorder:mediarecorder
The build
subcommand instructs Bazel to build the target that follows. The target is specified as the name of a build rule inside a BUILD
file, with along with the package path relative to your workspace directory. Note that you can sometimes omit the package path or target name, depending on your current working directory at the command line and the name of the target.
You can run a bazel build
command on any target in your BUILD
file, not just an android_binary
target. This can be useful during development to isolate build errors and when you don't want to build your entire app to validate a change.
You will see terminal output during the build process, with the final output appearing similar to the following:
INFO: Analysed target //mediarecorder/java/com/example/android/mediarecorder:mediarecorder (35 packages loaded, 867 targets configured). INFO: Found 1 target... Target //mediarecorder/java/com/example/android/mediarecorder:mediarecorder up-to-date: bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder_deploy.jar bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder_unsigned.apk bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder.apk INFO: Elapsed time: 24.197s, Critical Path: 8.77s INFO: 41 processes: 26 darwin-sandbox, 15 worker. INFO: Build completed successfully, 72 total actions
8. Locate the build outputs
Bazel stores the outputs of both intermediate and final build operations in a set of per-user, per-workspace output directories. These directories are symlinked from the following locations:
$WORKSPACE/bazel-bin
, which stores binary executables and other runnable build outputs$WORKSPACE/bazel-genfiles
, which stores intermediary source files that are generated by Bazel rules$WORKSPACE/bazel-out
, which stores other intermediate build outputs
Most of the time you will only be interested in bazel-bin
as it contains the final build outputs.
The console output from bazel build
above includes paths to the key output artifacts. In this case, bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder.apk
is the file we are most interested in.
Once your device is properly configured for use with the Android Debug Bridge (adb
), you can install this output apk on your device (either physical or emulated) using adb install
.
9. Rapid development with mobile-install
The "standard" development flow of edit, build, push can be slow. Bazel's mobile-install
functionality introduces an incremental build-and-deploy flow to accelerate and simplify the process of iterating on your application. In a single command, you can build and sync just the changes you made instead of rebuilding and reinstalling the entire app.
To use this functionality, replace the bazel build
commands with bazel mobile-install
on your android_binary
target. For example:
bazel mobile-install //mediarecorder/java/com/example/android/mediarecorder:mediarecorder --start_app
This command performs an incremental build, taking advantage of a number of optimizations to the Android build process. Bazel mobile-install
will build and push a diffset of resources and .dex
files rather than building a single .apk
.
You will see terminal output during the build process, with the final output appearing similar to the following:
INFO: Analyzed target //mediarecorder/java/com/example/android/mediarecorder:mediarecorder (0 packages loaded, 0 targets configured). INFO: Found 1 target... INFO: From Installing //mediarecorder/java/com/example/android/mediarecorder:mediarecorder: I0327 09:01:02.186528 140734993477056 incremental_install.py:441] Updating 1 dex... I0327 09:01:02.234949 140734993477056 incremental_install.py:504] Updating application resources... I0327 09:01:02.322421 140734993477056 incremental_install.py:601] Updating 0 native libs... I0327 09:01:02.675275 140734993477056 incremental_install.py:790] Starting application com.example.android.mediarecorder Target //mediarecorder/java/com/example/android/mediarecorder:mediarecorder up-to-date: bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder_files/full_deploy_marker bazel-bin/mediarecorder/java/com/example/android/mediarecorder/mediarecorder_files/deploy_info_incremental.deployinfo.pb INFO: Elapsed time: 1.915s, Critical Path: 1.79s INFO: 1 process: 1 local. INFO: Build completed successfully, 3 total actions
10. Review
Congratulations! You've completed the Building Android Apps with Bazel codelab.
What we've covered
- How to configure your Bazel
WORKSPACE
- How to write a
BUILD
file - How to write an
android_library
target for each module in your application - How to write an
android_binary
target for your application - How to build your
.apk
- How to speedup your development cycle with
mobile-install
Next steps
- Try using Bazel for your own app: write some
BUILD
files!