Scalio

Bazel

Bazel with Android + Kotlin

Mikhail Gurevich

Mikhail Gurevich

Principal Android Engineer

Bazel with Android + Kotlin
Bazel is a flexible build tool from Google used for automating both building and testing software across a variety of languages and platforms. In this article, we will explore how to create a simple Android project with Kotlin 1.3 support.

TL;DR

The final project can be found in Scalio’s Android Kotlin Starter repository.

Let’s get started

Choose the Empty Activity project template in Android Studio:

It’s time to replace the default Gradle configuration with some sweet Bazel!


Bazel configuration files

Bazel will require a slight adjustment if you’re coming from Gradle, which has a convention-based notion of a project where all required files reside. Bazel, on the other hand, specifies all relevant build files explicitly by defining a single WORKSPACE file with each package/target defined by a separate BUILD file.


WORKSPACE file

The main Bazel configuration is held in a single WORKSPACE file located at the project root. This is where we specify each external project dependency that will be used in the build targets.

We have a couple of options when defining the Android SDK within Bazel.

Assuming you already have your local ANDROID_HOME variable set as described in the documentation, you can import the Android SDK by simply using:

android_sdk_repository(
    name = "androidsdk",
    api_level = 29,
    build_tools_version = "29.0.2"
)
Android SDK Declaration (Implicit)

You can also import the Android SDK manually by specifying the path:

android_sdk_repository(
    name = "androidsdk",
    path = "/path/to/Android/sdk",
    api_level = 29,
    build_tools_version = "29.0.2"
)
Android SDK Declaration (Explicit)

Since every Bazel target requires its own rules, we need to specify Android related rules as well. We can use the official Android rules directly from Google:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "rules_android",
    urls = ["https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"],
    sha256 = "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
    strip_prefix = "rules_android-0.1.1",
)
Base Android integration rule


WORKSPACE file — Kotlin 1.3

One issue we dealt with when we started using Bazel with Android is that there are no official Android rules that support Kotlin version 1.3+. Google’s latest official version only supports Kotlin 1.2 and has a number of unresolved issues. Since we want to use coroutines and all the latest Kotlin goodies, we at Scalio decided to write our own rules. Thanks to Google and Christian Edward, Kotlin 1.3 is supported now by default!

kotlin 1-2 to 1-3.jpg 51.09 KB

Include Kotlin 1.3.31 support in your projects with the following rule:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

rules_kotlin_version = "legacy-1.3.0-rc1"
rules_kotlin_sha = "9de078258235ea48021830b1669bbbb678d7c3bdffd3435f4c0817c921a88e42"
http_archive(
    name = "io_bazel_rules_kotlin",
    urls = ["https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % rules_kotlin_version],
    type = "zip",
    strip_prefix = "rules_kotlin-%s" % rules_kotlin_version,
    sha256 = rules_kotlin_sha
)

load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains")
kotlin_repositories()
kt_register_toolchains()

Kotlin integration rules

If you require any Kotlin 1.3.* version, you can specify the compiler explicitly by modifying the rule:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

kotlin_version = "1.3.61"
kotlin_release_sha = "3901151ad5d94798a268d1771c6c0b7e305a608c2889fc98a674802500597b1c"
rules_kotlin_compiler_release = {
    "urls": [
    "https://github.com/JetBrains/kotlin/releases/download/v{v}/kotlin-compiler-{v}.zip".format(v = kotlin_version),
    ],
    "sha256": kotlin_release_sha,
}

rules_kotlin_version = "legacy-1.3.0-rc1"
rules_kotlin_sha = "9de078258235ea48021830b1669bbbb678d7c3bdffd3435f4c0817c921a88e42"
http_archive(
    name = "io_bazel_rules_kotlin",
    urls = ["https://github.com/bazelbuild/rules_kotlin/archive/%s.zip" % rules_kotlin_version],
    type = "zip",
    strip_prefix = "rules_kotlin-%s" % rules_kotlin_version,
    sha256 = rules_kotlin_sha
)

load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains")
kotlin_repositories(compiler_release = rules_kotlin_compiler_release)
kt_register_toolchains()
Kotlin integration rules with custom compiler

The only thing left to do is to include other external dependencies. Android Studio uses AppCompat and ConstraintLayout in its samples, so we need to tell Bazel where to find those libraries:

RULES_JVM_EXTERNAL_TAG = "2.2"
RULES_JVM_EXTERNAL_SHA = "f1203ce04e232ab6fdd81897cf0ff76f2c04c0741424d192f28e65ae752ce2d6"

http_archive(
    name = "rules_jvm_external",
    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
    sha256 = RULES_JVM_EXTERNAL_SHA,
    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
)

load("@rules_jvm_external//:defs.bzl", "maven_install")

maven_install(
    artifacts = [
        "androidx.appcompat:appcompat:1.0.2",
        "androidx.fragment:fragment:1.0.0",
        "androidx.core:core:1.0.1",
        "androidx.lifecycle:lifecycle-runtime:2.0.0",
        "androidx.lifecycle:lifecycle-viewmodel:2.0.0",
        "androidx.lifecycle:lifecycle-common:2.0.0",
        "androidx.drawerlayout:drawerlayout:1.0.0",
        "androidx.constraintlayout:constraintlayout:1.1.3",
        "com.google.android.material:material:1.0.0",
        "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2",
        "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"

    ],
    repositories = [
        "https://maven.google.com",
    ],
    fetch_sources = True,
)
External project dependencies

BUILD file

There is a single package in our sample, so we only need to create one BUILD file for Bazel located at the package root.

We first create the initial imports from the WORKSPACE file, set the resource target bazel_res, set the Kotlin target bazel_kt and finally compile it all together into the binary target bazel.

load("@rules_jvm_external//:defs.bzl", "artifact")
load("@rules_android//android:rules.bzl", "android_library")

package(default_visibility = ["//visibility:private"])
load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")


PACKAGE = "io.scal.bazel"
MANIFEST = "AndroidManifest.xml"

android_library(
    name = "bazel_res",
    custom_package = PACKAGE,
    manifest = MANIFEST,
    resource_files = glob(["res/**"]),
    enable_data_binding = False,
    deps = [
        artifact("androidx.constraintlayout:constraintlayout"),
        artifact("com.google.android.material:material"),
    ],
)

kt_android_library(
    name = "bazel_kt",
    srcs = glob(["java/**/*.kt"]),
    deps = [
        ":bazel_res",
        artifact("androidx.appcompat:appcompat"),
        artifact("androidx.fragment:fragment"),
        artifact("androidx.core:core"),
        artifact("androidx.lifecycle:lifecycle-runtime"),
        artifact("androidx.lifecycle:lifecycle-viewmodel"),
        artifact("androidx.lifecycle:lifecycle-common"),
        artifact("androidx.drawerlayout:drawerlayout"),
    ]
)

android_binary(
    name = "bazel",
    manifest = MANIFEST,
    custom_package = PACKAGE,
    manifest_values = {
        "minSdkVersion": "21",
        "versionCode" : "2",
        "versionName" : "0.2",
        "targetSdkVersion": "29",
    },
    deps = [
        ":bazel_res",
        ":bazel_kt",
        artifact("androidx.appcompat:appcompat"),
    ],
)
Final Build File

That’s it! The only thing left to do is build our shiny, new project with Bazel by running:

bazel build //app/src/main:bazel
Bazel build command


Conclusion

Note that Android Studio has great integration with Bazel via its plugin if you would rather work outside the command line.

The final project can be found in Scalio’s Android Kotlin Starter repository. The Bazel Kotlin plugin is open source and can be found on Github.

A special thanks to
Micah Lucas for edits and revisions.

How can we help you?