IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
Getting Started With the JetBrains Bazel Plugin
Large Bazel projects are becoming increasingly common in modern software development. Unfortunately, the IDE experience for these projects often leaves much to be desired. In this blog post, we’ll explore the new JetBrains Bazel plugin and how it can enhance your daily work with Bazel projects. We’ll begin with an overview of what Bazel users typically expect from IDEs and then dive into the features you can start using right away.
Bazel in your IDE
Bazel is a fast, scalable, and extensible build and test system that helps you manage complex codebases efficiently. It works with multi-language projects, offering support for C++, Java, Kotlin, Python, Rust, and other languages, and ensures consistent, reproducible builds across environments. With incremental build capabilities that recompile only what’s necessary and rich caching and parallel execution features, Bazel significantly enhances performance. Designed with extensibility in mind, it allows you to define custom build rules and scales seamlessly for monorepos and large-scale projects.
When it comes to Bazel support in IDEs, a variety of features are essential for streamlining workflows and boosting productivity. Seamless integration with Bazel configuration files (such as BUILD, MODULE.bazel, and WORKSPACE files) is a key requirement, with crucial support including syntax highlighting, code completion, and error checking to simplify the creation and maintenance of build rules. IDEs should also manage dependencies intelligently, automatically resolving them to ensure accurate project structures and robust code navigation, refactoring, and search capabilities.
Incremental build execution is another critical feature, enabling you to trigger and monitor Bazel builds directly from the IDE while leveraging performance-oriented features such as incremental compilation. Integrated test support is equally important, allowing developers to run and debug Bazel tests with tools like breakpoints and test filters. For multi-language projects, IDEs must handle cross-language dependencies effectively to ensure a smooth workflow.
Support for custom Bazel rules and plugins is extremely valuable, as it lets developers tailor the build process without sacrificing IDE functionality. Efficient indexing and caching are also essential for maintaining responsiveness, especially in large monorepos or with complex configurations. Finally, seamless integration with complementary tools like debuggers and profilers creates a cohesive and efficient development environment. These capabilities allow you to focus on writing code while Bazel manages the complexities of builds and dependencies.
Introducing our sample project
To demonstrate the features available in the JetBrains Bazel plugin and help you get started with it, we’ll take a look at the CLI implementation of an oversimplified calculator. Despite its simplicity, this calculator has evolved over years of development into a large monorepo with an advanced architecture, containing six packages implemented in Java and Kotlin – the languages the plugin currently supports:
From this diagram, you can see that some packages are nested while others are on the first level. The calc_core package serves as the foundation, with every other package in the project depending on it. Some packages, such as kotlin-div and kotlin-sub, can be developed independently of others, but they still require a dependency on calc_core. The developer of kotlin-div has also provided JUnit 5 tests that can be executed using Bazel. Tests for calc_add, on the other hand, are generated by Bazel.
For this project, we use the Bzlmod approach. The Bazel-related files are located within the project as follows:
. ├── .bazelrc ├── BUILD ├── MODULE.bazel ├── calc_cli │ ├── BUILD │ ├── rules.bzl │ └── src ├── calc_core │ ├── BUILD │ ├── operations │ │ ├── addition │ │ │ ├── BUILD │ │ │ ├── gen_tests.bzl │ │ │ └── src │ │ └── multiplication │ │ ├── BUILD │ │ └── src │ └── src ├── calc_div │ ├── BUILD │ └── src └── calc_sub ├── BUILD └── src
Note that we have MODULE.bazel (which defines the whole Bazel repository), a collection of BUILD files defining various targets, and files with rule definitions (*.bzl) here and there.
Working with a Bazel project in IntelliJ IDEA
Let’s install the JetBrains Bazel plugin and see how it can assist by exploring the most common workflows in Bazel projects.
Opening the project: Exploring the structure and project views
When we first open our Bazel project in IntelliJ IDEA, the Bazel plugin works with both Bazel and IDE to map Bazel packages onto IntelliJ IDEA’s workspace model. The JetBrains Bazel plugin aims to minimize the time required for the initial sync by waiting until a later stage to load all dependencies and metadata.
Here’s what we get as a result:
IntelliJ IDEA’s ability to properly recognize the project structure significantly enhances its performance for routine tasks like code completion and navigation. Several tools are available to help us examine the structure, including the Project tool window’s Packages view and the Bazel tool window:
The Bazel tool window gives us access to the project-wide operations (such as resyncing, building, and configuring) and lists all the Bazel targets discovered during the latest sync. At some point, we might be interested in a subset of targets, for example tests, or in filtering targets by name:
The filtered target view can be plain or hierarchical, and when searching through targets, we can use case-sensitive prompts or regular expressions.
Even with the improved mapping of Bazel projects onto the IntelliJ IDEA workspace, very large projects might be too big for IDE support. For those cases, we support project views, special configuration files that restrict the area for which the IDE is responsible.
When you open a Bazel project without .bazelproject files, the JetBrains Bazel plugin creates a default view in the .bazelbsp directory. Alternatively, you can create one yourself, for example, projectview.bazelproject:
derive_targets_from_directories: true directories: .
Here, we include all the project’s directories and derive all the targets from them.
For another example, with the following div-dev.bazelproject file, we can create a project view that provides support to only the directories needed for developers working on a Kotlin library that implements a division operation for our calculator:
derive_targets_from_directories: true directories: calc_div calc_core -calc_core/operations
These declarations mean that we include only two directories: calc_div and calc_core (excluding everything below calc_core/operations). Setting derive_targets_from_directories
to true
instructs the plugin also to include all the targets found in the specified directories.
Let’s put the div-dev.bazelproject file in the root folder of the project and execute the Load Project View action:
As a result, the following subset of targets becomes available in the project, while the previous subset becomes unavailable:
Reducing IDE support for packages outside the project view in this way should improve performance.
In general, you can have more than one project view file in the root folder of your project. The one that is currently in effect is highlighted in the Project tool window:
To get back to the full project, we can load the default .bazelbsp/.bazelproject file or the projectview.bazelproject file we created earlier. Switching between project views may take some time because it requires resyncing the whole project.
Editing, building, running, debugging, and testing
Once IntelliJ IDEA has loaded all the information from the Bazel configuration, we can use the IDE as usual. Among other things, project configuration makes it possible to provide a smoother code editing experience, as the IDE is aware of available libraries, provides meaningful completion suggestions, and supports navigation through the codebase:
We can also use Bazel to run or debug an application or test:
Another option is to run a test from the Bazel tool window:
From the Bazel tool window, we can also initiate a build for a particular target or jump to the corresponding BUILD file to tweak any configuration options:
Reconfiguring or extending the project
Changes in Bazel configuration files might require the project to be resynced with IntelliJ IDEA. The plugin detects such changes and suggests syncing with the IDE:
Adding files or packages to our repository is also detected and requires syncing for IntelliJ IDEA to work correctly.
Working with Starlark code
The JetBrains Bazel plugin also provides code completion, navigation, and debugging support for Starlark code.
In the following two screenshots, we change the name of tests generated for the addition operation, set a breakpoint, and launch a Starlark debugging session. Once we’re suspended on the breakpoint, we can inspect the available variables, and then we can continue the debugging session by using Step Into or Step Over or resuming execution.
When debugging Starlark code, it’s always helpful to remember that Bazel checks if there are any changes since the last time the code was executed. Before we can start debugging, we need to have changed something in the corresponding files.
Conclusion
In this blog post, we’ve reviewed how the JetBrains Bazel plugin integrates Bazel projects into IntelliJ IDEA, covering everything from project setup to debugging Starlark code. The plugin provides tools to help manage large and complex codebases, such as project views, target filtering, and incremental builds.
These features aim to simplify working with Bazel projects by improving navigation, build management, and test execution within the IDE. While it may require some initial setup, the plugin can make handling Bazel configurations and workflows more straightforward over time. If you’re working with Bazel, check out the plugin to see how it can complement your development process.