In this post, I will show you how to build Micronaut applications with Bazel. This is the first step in creating a mono repository for a complete web application (Backend and Frontend) that leverages incremental builds with Bazel. This tutorial is intended for Mac users, but It should be straightforward to get it working on other platforms.
Bazel
Bazel is a versatile build tool developed at Google used to speed up the build process of their mono-repository. As described here, Google uses a mono-repository and Bazel to host and build billions of lines of code. One key aspect about using Bazel is that it allows you to leverage mono-repository without compromising the build performance by leveraging incremental builds (It avoids building modules that have not changed).
Pre-requisites
Before you dive in, make sure you have the following dependencies installed. You can use the links provided below.
Build the project
In this section, I will show a step by step how to create and build a Micronaut project using Bazel.
First of all, we need to create the project structure to host our code.
mkdir bazel-micronaut && cd bazel-micronaut
mkdir src
touch WORKSPACE.bazel
touch BUILD
The WORKSPACE
file lives at the root of the project and is used to define the BAZEL workspace.
The BUILD
file is generally used to define how a package is built.
The next step is to create the Micronaut project.
cd src
mn create-app --build=maven --jdk=8 --lang=java --test=junit com.example.backend
Now that we have all the resources created, we are going to configure Bazel to build our application. The first step to do is to configure Bazel workspace and define the list of maven dependencies we need for this project.
Open the file WORKSPACE.bazel
and paste the following:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "4.0"
RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169"
MICRONAUT_VERSION = "2.5.7"
MICRONAUT_TEST_VERSION = "2.3.7"
JUNIT_VERSION = "5.7.1"
LOGBACK_VERSION = "1.2.3"
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 = [
"io.micronaut:micronaut-aop:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-inject:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-inject-java:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-validation:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-http-client:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-http-server-netty:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-runtime:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-context:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-core:%s" % MICRONAUT_VERSION,
"ch.qos.logback:logback-core:%s" % LOGBACK_VERSION,
"ch.qos.logback:logback-classic:%s" % LOGBACK_VERSION,
"javax.annotation:javax.annotation-api:1.3.2",
"org.slf4j:slf4j-api:1.7.30",
"io.micronaut.test:micronaut-test-junit5:%s" % MICRONAUT_TEST_VERSION,
"org.junit.jupiter:junit-jupiter-api:%s" % JUNIT_VERSION,
"org.junit.jupiter:junit-jupiter-engine:%s" % JUNIT_VERSION
],
repositories = [
"https://repo.maven.apache.org/maven2"
],
)
After defining the dependencies, and in order to improve build time, and artifacts resolution, it’s advised to in maven dependencies. This step also enables you to run builds in offline mode.
bazel run @maven//:pin
After pinning maven dependencies, we need to update the file WORKSPACE.bazel
to instruct Bazel to use pinned dependencies. The updated version of this file should look like the following:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
RULES_JVM_EXTERNAL_TAG = "4.0"
RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169"
MICRONAUT_VERSION = "2.5.7"
MICRONAUT_TEST_VERSION = "2.3.7"
JUNIT_VERSION = "5.7.1"
LOGBACK_VERSION = "1.2.3"
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 = [
"io.micronaut:micronaut-aop:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-inject:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-inject-java:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-validation:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-http-client:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-http-server-netty:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-runtime:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-context:%s" % MICRONAUT_VERSION,
"io.micronaut:micronaut-core:%s" % MICRONAUT_VERSION,
"ch.qos.logback:logback-core:%s" % LOGBACK_VERSION,
"ch.qos.logback:logback-classic:%s" % LOGBACK_VERSION,
"javax.annotation:javax.annotation-api:1.3.2",
"org.slf4j:slf4j-api:1.7.30",
"io.micronaut.test:micronaut-test-junit5:%s" % MICRONAUT_TEST_VERSION,
"org.junit.jupiter:junit-jupiter-api:%s" % JUNIT_VERSION,
"org.junit.jupiter:junit-jupiter-engine:%s" % JUNIT_VERSION
],
repositories = [
"https://repo.maven.apache.org/maven2"
],
maven_install_json = "//:maven_install.json"
)
load("@maven//:defs.bzl", "pinned_maven_install")
pinned_maven_install()
To pin new dependencies, you can the following command:
bazel run @unpinned_maven//:pin
The next step is to configure the build file to instruct Bazel
how to build our application. It’s important to mention that Micronaut
uses annotation processor, and we need to configure them appropriately. The content of the file BUILD
should look like the following:
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin", "java_test")
java_plugin(
name = "type_element_annotation_processor",
processor_class = "io.micronaut.annotation.processing.TypeElementVisitorProcessor",
deps = [
"@maven//:io_micronaut_micronaut_inject_java",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_validation"
]
)
java_plugin(
name = "aggregating_type_element_annotation_processor",
processor_class = "io.micronaut.annotation.processing.AggregatingTypeElementVisitorProcessor",
deps = [
"@maven//:io_micronaut_micronaut_inject_java",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_validation"
]
)
java_plugin(
name = "bean_definition_annotation_processor",
processor_class = "io.micronaut.annotation.processing.BeanDefinitionInjectProcessor",
deps = [
"@maven//:io_micronaut_micronaut_inject_java",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_validation"
]
)
java_plugin(
name = "service_descriptor_annotation_processor",
processor_class = "io.micronaut.annotation.processing.ServiceDescriptionProcessor",
deps = [
"@maven//:io_micronaut_micronaut_inject_java",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_validation"
]
)
java_library(
name = "micronaut_libs",
exported_plugins = ["service_descriptor_annotation_processor","type_element_annotation_processor","aggregating_type_element_annotation_processor", "bean_definition_annotation_processor"],
exports = [
"@maven//:io_micronaut_micronaut_inject_java",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_validation"
]
)
java_library(
name = "backend_deps",
resources = glob(["src/backend/src/main/resources/**/*.xml", "src/backend/src/main/resources/**/*.yml"]),
exports = [
"@maven//:ch_qos_logback_logback_classic",
"@maven//:io_micronaut_micronaut_runtime",
"@maven//:io_micronaut_micronaut_http_server_netty",
"@maven//:io_micronaut_micronaut_http_client",
"@maven//:io_micronaut_micronaut_core",
"@maven//:io_micronaut_micronaut_context",
"@maven//:io_micronaut_micronaut_validation",
"@maven//:io_micronaut_micronaut_aop",
"@maven//:io_micronaut_micronaut_inject",
"@maven//:io_micronaut_micronaut_inject_java"
]
)
java_library(
name = "backend_libs",
srcs = glob(["src/backend/src/main/java/**/*.java"]),
deps = [
":micronaut_libs",
":backend_deps",
],
)
java_binary(
name = "backend-services",
main_class = "com.example.Application",
runtime_deps = [":backend_libs"]
)
After we have configured all the required files, now we can build the project by running the following command:
bazel build //:backend-services
To run the application, you can run the following command:
bazel run //:backend-services
If everything works as expected, you should see an output like this:
Summary
In this tutorial, we learnt how to configure Bazel
to build and run Micronaut
applications. The most important part of this tutorial is configuring the annotation processors that Micronaut
relies on to assemble our application at compile time. In the next tutorial, we will look at how to configure Bazel
to run tests.
You can find all the source code of this tutorial here.