| # Using rules_jvm_external with bzlmod |
| |
| Bzlmod is the package manager for Bazel modules and is required starting with Bazel 7. |
| |
| ## Installation |
| |
| Add the following to your `MODULE.bazel` file, setting the `version` to the latest one |
| available on https://registry.bazel.build/modules/rules_jvm_external: |
| |
| ```starlark |
| bazel_dep(name = "rules_jvm_external", version = "...") |
| maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") |
| maven.install( |
| artifacts = [ |
| # This line is an example coordinate, you'd copy-paste your actual dependencies here |
| # from your build.gradle or pom.xml file. |
| "org.seleniumhq.selenium:selenium-java:4.4.0", |
| ], |
| ) |
| |
| # You can split off individual artifacts to define artifact-specific options (this example sets `neverlink`). |
| # The `maven.install` and `maven.artifact` tags will be merged automatically. |
| maven.artifact( |
| artifact = "javapoet", |
| group = "com.squareup", |
| neverlink = True, |
| version = "1.11.1", |
| ) |
| |
| use_repo(maven, "maven") |
| ``` |
| |
| Now you can run the `@maven//:pin` program to create a JSON lockfile of the transitive dependencies, |
| in a format that rules_jvm_external can use later. You'll check this file into the repository. |
| |
| ```sh |
| $ bazel run @maven//:pin |
| ``` |
| |
| Ignore the instructions printed at the end of the output from this command, as they aren't updated |
| for bzlmod yet. See [#836](https://github.com/bazelbuild/rules_jvm_external/issues/836) |
| |
| Due to [#835](https://github.com/bazelbuild/rules_jvm_external/issues/835) this creates a file with |
| a longer name than it should, so we rename it: |
| |
| ```sh |
| $ mv rules_jvm_external~4.5~maven~maven_install.json maven_install.json |
| ``` |
| |
| Now that this file exists, we can update the `MODULE.bazel` to reflect that we pinned the |
| dependencies. |
| |
| Add a `lock_file` attribute to the `maven.install()` call like so: |
| |
| ```starlark |
| maven.install( |
| ... |
| lock_file = "//:maven_install.json", |
| ) |
| ``` |
| |
| Now you'll be able to use the same `REPIN=1 bazel run @maven//:pin` operation described in the |
| [README](/README.md#updating-maven_installjson) to update the dependencies. |
| |
| ## Extension and tag documentation |
| |
| The extension and tag documentation can be found [in this document](bzlmod-api.md). |
| |
| ## Declaring dependencies in files |
| |
| It is possible to use a |
| gradle [version catalog](https://docs.gradle.org/current/userguide/version_catalogs.html) |
| to declare dependencies. These should be declared in a `libs.versions.toml` file, and can be |
| imported to your bazel project by using the `from_toml` tag: |
| |
| ```starlark |
| maven.from_toml( |
| libs_versions_toml = "//gradle:libs.versions.toml", |
| ) |
| ``` |
| |
| An example `libs.versions.toml` file could look like: |
| |
| ```toml |
| [versions] |
| junitJupiter = "5.12.2" |
| |
| [libraries] |
| guava = { module = "com.google.guava:guava" } |
| guavaBom = { module = "com.google.guava:guava-bom", version = "33.4.8-jre" } |
| junitApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiter" } |
| ``` |
| |
| #### Extensions to the Gradle version catalog format |
| |
| `rules_jvm_external` supports several additional fields on library entries beyond the |
| standard Gradle version catalog format. These must be specified as quoted strings within |
| the inline table: |
| |
| | Field | Example | Description | |
| |-------|---------|-------------| |
| | `classifier` | `classifier = "all"` | Maven classifier for the artifact | |
| | `exclusions` | `exclusions = "['com.example:unwanted']"` | JSON-encoded list of `group:artifact` exclusions | |
| | `force_version` | `force_version = "true"` | Pins this version, ignoring higher versions from transitive deps | |
| | `is_bom` | `is_bom = "true"` | Treats this entry as a BOM instead of a regular artifact | |
| | `package` | `package = "aar"` | Packaging type (default is `jar`) | |
| |
| For example: |
| |
| ```toml |
| [libraries] |
| guavaBom = { module = "com.google.guava:guava-bom", version = "33.4.8-jre", is_bom = "true" } |
| guava = { module = "com.google.guava:guava" } |
| clickhouse = { module = "com.clickhouse:clickhouse-jdbc", version = "0.9.2", classifier = "all", force_version = "true" } |
| misk = { module = "com.squareup.misk:misk-core", version = "1.0.0", exclusions = "['*:*']" } |
| ``` |
| |
| ### Declaring BOMs from external files |
| |
| This can be done by using the `bom_modules` attribute of the `from_toml` tag. This is |
| a list of gradle modules, matching the `module` in the `libs.versions.toml` file. We |
| can change our module declaration like so to correctly use the guava bom: |
| |
| ```starlark |
| maven.from_toml( |
| libs_versions_toml = "//gradle:libs.versions.toml", |
| bom_modules = [ |
| "com.google.guava:guava-bom", |
| ], |
| ) |
| ``` |
| |
| ## Artifact exclusion |
| |
| The non-bzlmod instructions for how to configure |
| `exclusions` [from the README](../README.md#artifact-exclusion) |
| don't work as shown for bzlmod; it's not possible to "inline" them as shown (it will cause an `ERROR: in tag at |
| <root>/MODULE.bazel:22:14, error converting value for attribute artifacts: expected value of type 'string' for |
| element 9 of artifacts, but got None (NoneType)`). Split it like this instead: |
| |
| ```starlark |
| # https://github.com/grpc/grpc-java/issues/10576 |
| maven.artifact( |
| artifact = "grpc-core", |
| exclusions = ["io.grpc:grpc-util"], |
| group = "io.grpc", |
| version = "1.58.0", # Keep version in sync with below! |
| ) |
| maven.install( |
| artifacts = [ |
| "junit:junit:4.13.2", |
| ... |
| ``` |
| |
| Alternatively, you can use the mechanism outlined below to add exclusions. |
| |
| ## Modifying artifact declarations |
| |
| Because artifacts are not always declared in the module file, `rules_jvm_external` offers |
| a mechanism for modifying artifacts that are declared elsewhere (eg. in an `install` or a |
| `from_toml` tag). This is done using the `amend_artifact` tag: |
| |
| ```starlark |
| maven.amend_artifact( |
| coordinates = "io.grpc:grpc-core", |
| exclusions = ["io.grpc:grpc-util"], |
| ) |
| ``` |
| |
| When matching artifacts that have been declared, only the `group:artifact` tuple is used |
| for matching. |
| |
| ## Module dependency layering |
| |
| The extension collects declarations from all tags with the same `name` before resolving them. Each |
| name is an independent Maven repository namespace. Declarations in one namespace never affect |
| another namespace. |
| |
| The root module and its dependencies have different roles during layering. The root contributes |
| the declarations that belong to the current Bazel project. Every other module is a non-root |
| contributor. Coordinates are conceptually matched by `group:artifact:packaging:classifier`, |
| meaning that a classified JAR layers independently of its unclassified JAR. |
| |
| When performing [duplicate coordinate checks](#diagnostics), the declarations are keyed by |
| `group:artifact:classifier`, but not packaging. Packaging-distinct declarations at different |
| versions can therefore warn or fail even though the extension layers them independently. It also |
| continues to check multiple root declarations. Ordinary cross-module conflicts with the same |
| layering key no longer reach this check. |
| |
| ### Version precedence |
| |
| For conflicts between modules, layering selects one complete declaration for each coordinate. The |
| selected declaration supplies its exclusions, `neverlink`, `testonly`, `force_version`, packaging, |
| classifier, and other fields. Fields from discarded declarations are not merged into it. Layering |
| does not deduplicate within the root module, so repeated root declarations for one coordinate reach |
| the existing repository-level duplicate check, which warns or fails according to |
| `duplicate_version_warning`. Forcing is the exception: if any module, the root included, sets |
| `force_version` on the same coordinate at two different versions, layering will fail with an |
| error message. Non-root declarations marked `testonly` are dropped before this check. |
| |
| The surviving declaration is chosen by these rules: |
| |
| 1. A forced version in the root module always wins. |
| 2. Otherwise, a forced declaration beats any unforced one, whatever the versions. |
| 3. Otherwise, the highest version wins, regardless of which module declared it. |
| |
| On ties and conflicts: |
| |
| - Two non-root modules that force different versions is an error and fails before resolution. The root can settle it by forcing the version itself. |
| - On a tie (equal versions, or the same forced version from more than one module) the first module's declaration is kept; the root counts as first. |
| - A non-root artifact marked `testonly` is dropped. |
| |
| Be aware that non-default packaging and classifiers remain independent of each other and of the |
| plain versioned coordinate. This may lead to some surprises when resolution is complete. |
| |
| "Highest" uses the Maven `ComparableVersion` ordering implemented by |
| `private/rules/maven_version.bzl`, not lexical string ordering. |
| |
| `version_conflict_policy = "pinned"` changes this interaction. For the Gradle and Maven resolvers, |
| root artifacts are marked as `force_version` before layering. The duplicate-force check applies to |
| declared forces before this policy is applied. Maven then marks every versioned root declaration. |
| Gradle first selects one version for each root `group:artifact`: an unclassified declaration takes |
| precedence over classified declarations, and Maven `ComparableVersion` order selects among |
| declarations with the same classification status. Every root declaration for that module at the |
| selected version is then marked forced, including classified declarations. The root consequently |
| wins because it now forces the coordinate. For Coursier, layering is unchanged and the one |
| surviving direct version is later passed as a `--force-version` argument. A higher non-root |
| version can therefore displace the root under Coursier and then be pinned. |
| |
| The `force_version` flag can be set by an `artifact` tag, an `amend_artifact` tag, or a regular |
| artifact read by `from_toml`. Coordinates in `install.artifacts` cannot carry the flag. BOMs use the |
| same extension-layer precedence rules as artifacts. |
| |
| ### Contributors and configuration |
| |
| When the root and other modules contribute artifacts to the same namespace, the extension prints a |
| message such as: |
| |
| `The maven repository 'multiple_lock_files' has contributions from multiple bzlmod modules, and will be resolved together: ["bzlmod_lock_files", "rules_jvm_external"]` |
| |
| If those contributions are expected, set `known_contributing_modules` on the root `install` tag. |
| The warning includes the value to add. Once this attribute is non-empty, only listed modules may |
| contribute artifacts or BOMs to that namespace. A module that contributes only BOMs triggers the |
| same contribution warning and can be acknowledged through the same attribute. |
| |
| After dependencies are layered, scalar `install` attributes from the root module take precedence. |
| List attributes are combined root-first, while preserving their existing deduplication or |
| concatenation behaviour. |
| |
| The default namespace is `maven`. A module intended for use through `bazel_dep` should normally use |
| its own name, such as the `rules_jvm_external_deps` namespace used by this project. The default is |
| appropriate when a module deliberately contributes functionality that would otherwise be supplied |
| as a Maven dependency, or when the project is only used as the root module. |
| |
| ### <a id="diagnostics"></a>Diagnostics |
| |
| Layering keeps the following diagnostics so that unexpected versions can be traced to their |
| contributing module. Each entry shows the message a user sees and how to resolve it. Several are |
| governed by [`duplicate_version_warning`](bzlmod-api.md#maven.install-duplicate_version_warning), |
| which is `"error"` to fail, `"warn"` (the default) to print and continue, or `"none"` to stay |
| silent. |
| |
| #### Which modules are contributing to this repository? |
| |
| An unacknowledged non-root module contributing artifacts or BOMs always prints the contribution |
| warning: |
| |
| ``` |
| The maven repository 'my-project' has contributions from multiple bzlmod modules, and will be resolved together: ["my-project", "some-other-module"] |
| ``` |
| |
| **Remedy:** if the contributions are expected, set `known_contributing_modules` on the root |
| `install` tag to the module names in the message; otherwise remove the contributing module. When |
| `known_contributing_modules` instead excludes a contributor, an `INFO` message is printed when |
| `RJE_VERBOSE` is set. |
| |
| #### Why is my forced version rejected? |
| |
| One module forcing the same coordinate at two different versions fails: |
| |
| ``` |
| Module 'my_module' forces dependency 'com.google.guava:guava' at different versions: 31.1-jre and 33.0.0-jre. |
| ``` |
| |
| **Remedy:** keep a single version for the coordinate within that module. |
| |
| Non-root modules forcing different versions of a coordinate that the root does not force fails with: |
| |
| ``` |
| Conflicting forced versions for dependency 'com.google.guava:guava': module_a wants 31.1-jre, module_b wants 33.0.0-jre. Add an `artifact` tag to the root module at the version you want and set `force_version = True`. |
| ``` |
| |
| **Remedy:** add an `artifact` tag to the root module at the version you want and set |
| `force_version = True` on it. |
| |
| #### Which version will be selected? |
| |
| When layering selects a version different from the root version, the version-selection warning is: |
| |
| ``` |
| WARNING: For dependency 'com.google.protobuf:protobuf-java' the root @maven repo wants version 3.25.5, but got 4.27.2 from the bazel_worker_java bazel dep. Please update the version in your MODULE.bazel or set `force_version = True`. |
| ``` |
| |
| `duplicate_version_warning` controls whether this warns, fails, or stays silent. |
| |
| **Remedy:** update the version in the root module to the highest version, or set |
| `force_version = True` in the root module to ensure that version is the one used |
| in dependency resolution. |
| |
| You only see this when the version that ends up being used differs from the one declared in your |
| root module. For example, a `bazel_dep` may pull in a higher version of a dependency you also |
| declare in the root. If the resolved version already matches your root declaration, there is |
| nothing to act on and no warning is printed. Coordinates that only a `bazel_dep` declares (and |
| your root does not) do not produce this warning either; they are covered by the contribution |
| warning above instead. |
| |
| One equal-version case still reports itself. When a non-root module forces the same version that |
| your root module declares, its declaration displaces the root's, and an `INFO` message is printed |
| when `RJE_VERBOSE` is set: |
| |
| ``` |
| INFO: For dependency 'com.google.protobuf:protobuf-java' the bazel_worker_java bazel dep forces version 3.25.5; its declaration replaces the root module's declaration of the same version. |
| ``` |
| |
| **Remedy:** set `force_version = True` on the root declaration to keep the root module's |
| declaration, or drop the root declaration if the non-root module's is what you want. |
| |
| #### Which versions are reaching the repository? |
| |
| When more than one version of the same dependency makes it into the repository, whether declared |
| twice in one module or contributed by several modules, the message is: |
| |
| ``` |
| Found duplicate artifact versions |
| com.google.guava:guava has multiple versions 31.1-jre, 33.0.0-jre |
| Please remove duplicate artifacts from the artifact list so you do not get unexpected artifact versions |
| ``` |
| |
| `duplicate_version_warning` controls whether this warns, fails, or stays silent. **Remedy:** remove |
| duplicate artifacts from the artifact list. |
| |
| A non-root-only coordinate is reported as an `INFO` message when a repin variable and `RJE_VERBOSE` |
| are both set: |
| |
| ``` |
| INFO: The @maven repo is getting the additional artifact com.google.guava:guava:33.0.0-jre from the module_a bazel dep. |
| ``` |
| |
| ## Known issues |
| |
| - Some error messages print instructions that don't apply under bzlmod, |
| e.g. https://github.com/bazelbuild/rules_jvm_external/issues/827 |