Posted by: Pavlo Stavytskyi
Bazel for Android is a series of blog posts that shows the basics of building Android projects with the 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 Android Studio, IntelliJ IDEA, VSCode, or any other favorite text editor.
Step 0. Setting up Bazel
While working with Bazel, it is recommended to use Bazelisk, a wrapper for Bazel that can manage its version and automatically download the right binary if required.
Since Bazelisk is just a wrapper around Bazel, their command-line interfaces are the same, so you can use bazelisk
the same way as you would use bazel
.
Install with brew on MacOS:
$ brew install bazelisk
Install with npm :
$ npm install -g @bazel/bazelisk
Install with go (make sure you’re using Go version 1.11 or higher):
go get github.com/bazelbuild/bazelisk
If you are using Go version, add it to your PATH:
export PATH=$PATH:$(go env GOPATH)/bin
Install from binaries:
Releases · bazelbuild/bazelisk |
Step 1. Creating Bazel WORKSPACE
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.
android_sdk_repository( | |
name = "androidsdk" | |
) |
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:
android_sdk_repository( | |
name = "androidsdk", | |
path = "/path/to/Android/sdk", | |
) |
Here is the project structure so far:
bazel-android-hello-world └── WORKSPACE
Step 2. Creating folder structure
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 thejava
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 thejava
directory. - Under the
app
folder also create theres
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 thelayout
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
Step 3. Writing source code
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:
package com.morfly.helloworld; | |
import android.app.Activity; | |
import android.os.Bundle; | |
public class MainActivity extends Activity { | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
} | |
} |
Next, under theapp/res/layout
directory create an activity_main.xml
layout file. Populate it with the following code:
<?xml version="1.0" encoding="utf-8"?> | |
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<TextView | |
android:id="@+id/text" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_gravity="center" | |
android:text="Hello World!" /> | |
</FrameLayout> |
Finally, under the app
directory create an AndroidManifest.xml
file with the following code:
<?xml version="1.0" encoding="utf-8"?> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
package="com.morfly.helloworld"> | |
<application android:label="Bazel Android Hello World"> | |
<activity android:name=".MainActivity"> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN" /> | |
<category android:name="android.intent.category.LAUNCHER" /> | |
</intent-filter> | |
</activity> | |
</application> | |
<uses-sdk | |
android:minSdkVersion="21" | |
android:targetSdkVersion="21" /> | |
</manifest> |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,
Job Offers
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
Step 4. Building and running the project
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:
android_binary( | |
name = "app", | |
custom_package = "com.morfly.helloworld", | |
manifest = "AndroidManifest.xml", | |
srcs = ["java/com/morfly/helloworld/MainActivity.java"], | |
resource_files = glob(["res/**"]), | |
) |
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:
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.name
— specifies the unique name of the target.custom_package
— specifies a Java package for which Java sources will be generated.manifest
— specifies a path to theAndroidManifest.xml
file.srcs
— specifies the list of source files used by the target.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 thesrcs
argument, or as an alternative, include all files that match the given pattern usingglob
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 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
Step 5. Understanding Bazel labels
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 ofapp
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 ofapp
is the name of the target defined in theBUILD
file. In our case, it refers to theandroid_binary
target with the argumentname = "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.
The source code can be found here:
Morfly/bazel-examplesContribute to Morfly/bazel-examples development by creating an account on GitHub. |