Photo by Alex Milkis

Part 1 — Creating Bazel Android app from scratch

Bazel for Android is a series of blog posts that shows the basics of building Android projects with Bazel build system.

  • Part 1: Getting started you are here
  • Part 2: Adding external dependencies
  • Part 3: Building multi-module projects
  • Part 4: Building Kotlin code

In this chapter, you will see how to create a “Hello World!” Android application built with Bazel completely from scratch. No automatic wizard or project template will be used to getting started. We will create an empty project directory and populate it with source files one by one until the project is built and launched with Bazel.

There are no specific requirements for IDEs in this chapter. You can use IntelliJ IDEA, VSCode, or any other favorite text editor.

First, we need to create an empty folder which will be the root of the project. Let’s call it bazel-android-hello-world.

Inside of this folder create a file called WORKSPACE . Open terminal at the bazel-android-hello-world directory, and run the following command:

$ bazelisk info workspace

If everything is done correctly you will see the path to the root project directory on your machine as an output.

Next, we need to specify an Android SDK in the project. To do this, add the following code to theWORKSPACE file.

By default, Bazel will reference Android SDK by using ANDROID_HOME environmental variable, so the code above will be enough in this case. Alternatively, you can explicitly specify path to Android SDK:

Here is the project structure so far:

bazel-android-hello-world
└── WORKSPACE

Before writing the source code we need to set up a folder layout for our project:

  • Create app folder under the project root directory.
  • Under the app folder create the java directory. It will contain the Java code of the application.
  • The Java package for our application will be com.morfly.helloworld . Create corresponding folders under the java directory.
  • Under the app folder also create the res directory. It will contain the resource files of the application.
  • Bazel requires a strict directory structure inside resource directories. Therefore, under the res folder create the layout directory. It will contain the layout files of the application.

Note. Bazel is more flexible in terms of project structure, so you don’t necessarily need to follow the structure used in Gradle Android projects.

Here is the project structure so far:

bazel-android-hello-world
├── app
│ ├── java
│ │ └── com
│ │ └── morfly
│ │ └── helloworld
│ └── res
│ └── layout

└── WORKSPACE

Now, we are ready to add the source code for our Android application.

Under the app/java/com/morfly/helloworld directory create a MainActivity.java file and add the following code:

Next, under theapp/res/layout directory create an activity_main.xmllayout file. Populate it with the following code:

Finally, under the app directory create an AndroidManifest.xml file with the following code:

That’s all we need to have a working Android application.

Here is the project structure so far:

bazel-android-app 
├── app
├── java
└── com/morfly/helloworld
│ │ └── MainActivity.java <- new
│ ├── res
│ │ └── activity_main.xml <- new
│ │
│ └── AndroidManifest.xml <- new

└── WORKSPACE

Now we’ve reached the most interesting part. We are going to build and run our Android project with Bazel.

In order to build the project, we need to create a buildable target. To do this, under the app directory create the file called BUILD . Add the following code:

All Bazel files use Starlark language which is a simplified version of Python. This means that can consider the contents of a BUILD file to be just a Python code. Let’s go line-by-line and see what does this code do:

  1. android_binary — rule that allows creating buildable Android target. By the convention, all rules that end with _binary , create targets that generate executable files for applications. In our case, this rule is used to define a target that generates an .apk file that can be launched on a mobile device.
  2. name — specifies the unique name of the target.
  3. custom_package — specifies a Java package for which Java sources will be generated.
  4. manifest — specifies a path to the AndroidManifest.xml file.
  5. srcs — specifies the list of source files used by the target.
  6. resource_files — specifies the list of resource files used by the target. It is possible to list all the files 1-by-1 as it is done with the srcs argument, or as an alternative, include all files that match the given pattern using glob function.

Rule — is the function that creates buildable targets. Each rule defines specific build configuration for the particular use case. E.g. Targets created with java_binary rule generate executable .jar files, while android_binary targets create .apk files. To do this, they take different arguments and perform different job under the hood.

Target — is the single instance created by the rule function that can be referred by the unique name. E.g. Your project can contain multiple targets namedapp1 , app2 , … appN created with using android_binary rule.

Now, open your terminal at the root directory of your workspace and run the following command to start building the project:

$ bazelisk build //app:app

Now, launch the emulator or connect the real mobile device to your computer and run the following command:

$ bazelisk mobile-install //app:app --start_app

If the build was successful, you will see the Android application launched on your device showing Hello World! on the screen.

Note. By default mobile-install only installs the application to the device without launching. By using --start_app flag we can tell Bazel to launch the application right after installation.

Here is the project structure so far:

bazel-android-hello-world 
├── app
├── java
└── com/morfly/helloworld
│ │ └── MainActivity.java
│ ├── res
│ │ └── activity_main.xml
│ │
│ ├── BUILD <- new
│ └── AndroidManifest.xml

└── WORKSPACE

In the example above we're using //app:app label in order to refer to the android_binary target that we have defined earlier. Let’s deconstruct this label and understand its structure

  • // — means that we start the reference from the root of the workspace (root of the project).
  • app — first occurrence of app is the name of Bazel package that contains the target we want to build.

Note: Bazel package is the directory that holds BUILD file. Therefore, Bazel package name is defined by the relative path from the project root to the directory that holds BUILD file.

Do not confuse Bazel package and Java package. They are not the same.

  • :app — second occurrence of app is the name of the target defined in the BUILD file. In our case, it refers to the android_binary target with the argument name = "app" .

Shortening labels

In some cases, it is possible to shorten labels for convenience. For example, if the package name and target name are the same, we can avoid explicitly specifying the latter in labels. So, instead of //app:app we can refer to our target as //app :

$ bazelisk build //app

Moreover, if the target is inside the package that is next to the project root directory, we can also omit // . So we can refer to our target as app .

$ bazelisk build app

Try this command and you will see that it builds successfully with no changes.

This is the bare minimum required for building Android applications with Bazel. In the next chapter, you will see how to add external maven dependencies to the project.

All the source code can be found on GitHub.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store