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.

Note that Bazel is currently in Beta - it is not yet covered by a deprecation policy, may be subject to backward-incompatible changes, and may be missing some features.

Why should I use Bazel?

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:

How familiar are you with Bazel?

I use it regularly I've used it once or twice I've heard of it but never tried it out I've never heard of Bazel

How will you use this codelab?

Only read through it Read it and complete the exercises

You'll need Bazel and Android Studio to complete this codelab. Skip ahead to "Get the sample project" if these are already installed.

Install Bazel

Follow the installation instructions to install Bazel and its dependencies.

Install Android Studio

Download and install Android Studio as described in Install Android Studio. Also install the Bazel plugin.

The installer does not automatically set the ANDROID_HOME variable. Set it to the location of the Android SDK, which defaults to $HOME/Android/Sdk/ .

For example:

export ANDROID_HOME=$HOME/Android/Sdk/

For convenience, add the above statement to your ~/.bashrc file

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.

A workspace is a directory that contains the source files for one or more software projects, as well as a WORKSPACE file 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 at its root. 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 SDK build tools to be configured. These are used to build your Android application.

Create your WORKSPACE file:

vi $WORKSPACE/WORKSPACE

And add the following lines:

android_sdk_repository(
    name = "androidsdk",
    path = "/Users/codelab/Library/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.

Alternatively, you can explicitly specify the API level and the version of build tools to use by including the api_level, and build_tools_version attributes. You can specify any subset of these attributes (but isn't required for this codelab):

android_sdk_repository(
    name = "androidsdk",
    path = "/path/to/Android/sdk",
    api_level = 27,
    build_tools_version = "26.0.1",
)

Optional: This is not required by this codelab, but if you want to compile native code into your Android app, you also need to download the Android NDK and tell Bazel where to find it by adding the following rule to your WORKSPACE file:

android_ndk_repository(
    name = "androidndk",
)

api_level is the version of the Android API the SDK and the NDK target (for example, 23 for Android 6.0 and 25 for Android 7.1). If not explicitly set, api_level will default to the highest available API level for android_sdk_repository and android_ndk_repository. It's not necessary to set the API levels to the same value for the SDK and NDK. This web page contains a map from Android releases to NDK-supported API levels.

Similar to android_sdk_repository, the path to the Android NDK is inferred from the ANDROID_NDK_HOME environment variable by default. The path can also be explicitly specified with a path attribute on android_ndk_repository.

Verify your workspace

Verify your workspace and SDK configuration by running the executing the following command:

bazel fetch @androidsdk//...

If your workspace configuration is correct you will see no console output. If there is an error with your SDK configuration you will see an error message.

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

mediarecorder/java/com/example/android/mediarecorder/AndroidManifest.xml

Activity source file

mediarecorder/java/com/example/android/mediarecorder/MainActivity.java

Resource file directory

mediarecorder/java/com/example/android/mediarecorder/res/

Common library

mediarecorder/java/com/example/android/common/media/

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.

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 Skylark, 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.

Bazel provides 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:

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:

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:

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:

android_binary(
    name = "mediarecorder",
    manifest = "AndroidManifest.xml",
    resource_files = glob(["res/**"]),
    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.

Now, save and close the file. You can compare both BUILD files to the completed examples in the solutions directory of the GitHub repo.

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:

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.

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 (29 packages loaded).
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: 12.299s, Critical Path: 10.59s
INFO: Build completed successfully, 85 total actions

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:

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.

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 can build your project directly from Android Studio by using the Bazel plugin, which you installed in the first step of this codelab.

Note that Bazel is not currently compatible with Android Studio 3.1. Expect to see this support soon, but in the meantime you can use Android Studio 3.0 to develop with Bazel.

Import your project

Complete the following steps to create a new Bazel project from a BUILD file:

  1. Launch Android Studio 3.0.
  2. Select Import Bazel Project from the Welcome Screen.
  3. Enter a WORKSPACE path by browsing to the directory containing the WORKSPACE file we created earlier.
  4. Select Generate from BUILD file.
  5. Enter a BUILD file path relative to the WORKSPACE directory: mediarecorder/java/com/example/android/mediarecorder/BUILD
  6. Preview the project definition that has been generated from the BUILD file:
directories:
  mediarecorder/java/com/example/android/mediarecorder

targets:
  //mediarecorder/java/com/example/android/mediarecorder/...:all

additional_languages:
  # Uncomment any additional languages you want supported
  # c
  # python


android_sdk_platform: android-27
  1. Click Finish and wait for Android Studio to import and sync your project.

This step only needs to be completed once. In the future, you can re-import this project definition and manually sync by clicking Sync Project with BUILD Files.

Build the app

Complete the following steps to build your app using Bazel:

  1. Change the left Project View from Android to Project.
  2. Expand the directory tree to find your BUILD file and double click to open it.
  3. Right-click the name of the android_binary target and click Run.
  4. Select your connected device (either physical or virtual) from the Device Selection window.
  5. Watch the Bazel Console as your app is built and deployed to the device.

Step 3 creates a Run Configuration for your target. This can be customised via the Run -> Edit Configurations menu for advanced settings.

Congratulations! You've completed the Building Android Apps with Bazel codelab.

What we've covered

Next steps

Useful links