Mabel
A Maven dependency graph generator for Bazel
Install / Use
/learn @menny/MabelREADME
Mabel
Yet another Maven dependency graph generator for Bazel.
Mabel provides the mabel_rule rule and artifact macro, which automatically generate a set of targets that can be used as dependencies based on a given list of Maven coordinates. The rule outputs the dependency graph to a lockfile (similar to Yarn's lock file or npm's package-lock.json).
Features
- Transitively resolves all dependencies from a given list of Maven dependencies and manages version conflicts, ensuring that only one version of each artifact is available in the dependency graph.
- Generates repository rules for all remote artifacts.
- Generates required Java rules with transitive dependencies.
- Allows marking dependencies as
test_only. - Automatically detects which rule type to create for a given dependency:
aar_importfor Android artifacts.java_plugin+java_libraryfor annotation processors. More about this here.jvm_importfor anything else.
- Allows implementation replacement for
jvm_importandaar_import. These can be replaced with another rule or macro. - Supports custom Maven repository URLs and locking dependencies to a specific Maven repository.
- Adds
licensesdata tojvm_importrules if a license is declared in the artifact's POM file. Also adds license metadata to the targets'tagsattribute:mabel_license_name- The name of the license as it appears in thepom.xmlfile.mabel_license_url- The URL to the license file as it appears in thepom.xmlfile.mabel_license_detected_type- The type of license (Apache,MIT,GPL, etc.) as detected by Mabel.
- Adds
srcjarif sources are available in the Maven repository. - Handles POM options:
- Profiles and placeholders.
- Version specifications.
- Dependencies that do not have a POM.
- Exports the Maven coordinate as a tag in the
jvm_importrule. This can help with Bazel's pom_file rule.
- Calculates
sha256for each remote artifact. - Produces a lockfile that describes the dependency graph. This file should be checked into your repository.
Why
Unlike other build systems, Bazel does not provide a dependency management service as part of the build and does not provide a way to specify a Maven dependency (which will be resolved transitively) and be available during compilation.
There are several attempts to solve this problem (such as sync-deps, gmaven, rules_jvm_external, migration-tooling, maven-rules, and bazel-deps), but some do not support Kotlin or Android, and some do not support customized Maven repositories.
Usage
Mabel uses a two-phase approach for dependency management:
- Phase 1: Lockfile Generation - Run the
mabel_ruletarget to resolve dependencies and generate a JSON lockfile - Phase 2: Module Extension - Configure the
mavenextension to read the lockfile and create repository rules
This approach ensures reproducible builds and allows you to review dependency changes before committing them.
Step 1: Add Mabel to your MODULE.bazel
Add mabel as dependencies:
bazel_dep(name = "mabel", version = "0.31.0") # Check latest release
Step 2: Define Dependencies in BUILD.bazel
In your module's BUILD.bazel file (e.g., //third_party:BUILD.bazel), load the mabel_rule and artifact symbols, and define a mabel_rule target with the list of your dependencies:
load("@mabel//rules:mabel.bzl", "mabel_rule", "artifact")
mabel_rule(
name = "maven_deps",
lockfile_path = "third_party/maven_install.json",
maven_deps = [
artifact("com.google.guava:guava:33.0.0-jre"),
artifact("org.apache.commons:commons-lang3:3.14.0"),
artifact("com.google.code.findbugs:jsr305:3.0.2"),
],
)
Attributes:
-
name- A unique name for this target. -
lockfile_path- Path to the output JSON lockfile (relative to workspace root). This file will be created/updated when you run the target. -
maven_deps- List ofartifact()macro invocations representing Maven coordinates to resolve. -
version_conflict_resolver- (Optional) Defaultlatest_version. Strategy for resolving version conflicts. Can belatest_versionorbreadth_first. -
calculate_sha- (Optional) DefaultTrue. Calculates thesha256value of each remote artifact. -
fetch_srcjar- (Optional) DefaultFalse. Also tries to fetch sources jar for each dependency.
The artifact() macro accepts:
coordinate- Maven coordinate in the formgroup-id:artifact-id:version(e.g.,"com.google.guava:guava:33.0.0-jre").type- (Optional) Target type to create. Default isinherit. Can bejar,aar,naive,processor,inherit, orauto.test_only- (Optional) Marks this dependency as test-only.maven_exclude_deps- (Optional) List of Maven dependencies to exclude from resolution.repositories- (Optional) List of URLs pointing to Maven servers. Defaults to Maven Central.exports_generation_type- (Optional) Defaultinherit. Override exports generation for this artifact. Can beinherit,all,requested_deps,none.debug_logs- (Optional) DefaultFalse. Prints debug logs for this artifact.
Step 3: Generate the Lockfile
Run the mabel_rule target to resolve all transitive dependencies and generate the lockfile:
bazel run //third_party:maven_deps
This will:
- Resolve all transitive dependencies from the specified Maven coordinates
- Apply version conflict resolution (using the configured strategy)
- Download and calculate SHA256 hashes for all artifacts
- Generate
third_party/maven_install.jsoncontaining all resolved dependency metadata
The lockfile is a JSON file with the following structure:
{
"version": "1.0",
"artifacts": {
"com.google.guava:guava:33.0.0-jre": {
"repo_name": "com_google_guava__guava__33_0_0_jre",
"url": "https://repo1.maven.org/maven2/...",
"sha256": "...",
"dependencies": ["com_google_code_findbugs__jsr305__3_0_2", ...],
"exports": [...],
"runtime_deps": [...],
"test_only": false,
"target_type": "jar",
"licenses": [...]
}
}
}
Important: Commit this lockfile to your version control system. It should be treated like package-lock.json in npm or Cargo.lock in Rust.
Step 4: Configure the Module Extension
In your MODULE.bazel, configure the mabel extension to read the lockfile:
mabel = use_extension("@mabel//rules:extensions.bzl", "mabel")
mabel.install(
lockfile = "//third_party:maven_install.json",
aliases_repo = "maven",
)
# Import the maven alias repository for convenient access
use_repo(mabel, "maven")
# Optionally (but discouraged) import specific versioned repositories if needed
use_repo(mabel,
"com_google_guava__guava__33_0_0_jre",
"org_apache_commons__commons_lang3__3_14_0",
"com_google_code_findbugs__jsr305__3_0_2",
)
The mabel.install() tag accepts:
lockfile- Label pointing to the JSON lockfile generated in Step 3.aliases_repo- Name of the alias repository to create (e.g.,"maven"). This repository provides clean, version-free paths to your dependencies.
The use_repo() call imports the repository rules created by the extension. The "maven" repository is an alias repository that provides convenient access to all dependencies. Repository names follow the {group_id}__{artifact_id}__{version} pattern, where dots, hyphens, and other special characters are replaced with underscores.
How to find repository names:
You can find the exact repository names in the lockfile under the repo_name field for each artifact, or use this command:
cat third_party/maven_install.json | grep -o '"repo_name": "[^"]*"' | cut -d'"' -f4 | sort
Step 5: Use in Your Targets
Reference the dependencies in your Bazel targets using the @maven alias repository:
java_library(
name = "mylib",
srcs = ["MyLib.java"],
deps = [
"@maven//com/google/guava/guava",
"@maven//org/apache/commons/commons-lang3",
],
)
The @maven repository provides clean, version-free paths to your dependencies using the pattern @maven//group/id/artifact-id.
Updating Dependencies
When you need to update dependencies:
- Modify the
maven_depslist in yourmabel_rule(add, remove, or change versions) - Run
bazel run //third_party:maven_depsto regenerate the lockfile - Review the changes to
maven_install.json(usegit diff) - Update the
use_repo()call inMODULE.bazelif you added or removed dependencies - Commit the updated lockfile
Working with Multiple Dependency Sets
You can define multiple mabel_rule targets for different dependency sets (e.g., separate sets for main code, tests, or different modules):
mabel_rule(
name = "main_deps",
lockfile_path = "third_party/main_install.json",
maven_deps = [
artifact("com.google.guava:guava:33.0.0-jre"),
],
)
mabel_rule(
name = "test_deps",
lockfile_path = "third_party/test_install.json",
maven_deps = [
artifact("junit:junit:4.13.2", test_only = True),
artifact("org.mockito:mockito-core:5.0.0", test_
