Gazelle Java extension
This provides an experimental Gazelle extension to generate build files for
Java projects.
Usage
In the WORKSPACE
file set up the rules_jvm correctly:
load("@contrib_rules_jvm//:repositories.bzl", "contrib_rules_jvm_deps", "contrib_rules_jvm_gazelle_deps")
contrib_rules_jvm_deps()
contrib_rules_jvm_gazelle_deps()
load("@contrib_rules_jvm//:setup.bzl", "contrib_rules_jvm_setup")
contrib_rules_jvm_setup()
load("@contrib_rules_jvm//:gazelle_setup.bzl", "contrib_rules_jvm_gazelle_setup")
contrib_rules_jvm_gazelle_setup()
In the top level BUILD.bazel
file, setup Gazelle to use gazelle-languages binary:
load("@bazel_gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle", "gazelle_binary")
# gazelle:prefix github.com/your/project
gazelle(
name = "gazelle",
gazelle = ":gazelle_bin",
)
gazelle_binary(
name = "gazelle_bin",
languages = DEFAULT_LANGUAGES + [
"@contrib_rules_jvm//java/gazelle",
],
)
Make sure you have everything setup properly by building the gazelle binary:
bazel build //:gazelle_bin
To generate BUILD files:
# Run Gazelle with the java extension
bazel run //:gazelle
Requirements
This gazelle plugin requires Go 1.18 or above.
Configuration options
This Gazelle extension supports some configuration options, which are enabled by
adding comments to your root BUILD.bazel
file. For example, to set
java_maven_install_file
, you would add the following to your root
BUILD.bazel
file:
# gazelle:java_maven_install_file project/main_maven_install.json
See javaconfig/config.go for a list of configuration
options and their documentation.
Additionally, some configuration can only be done by flag. See the
RegisterFlags
function in configure.go for a list of these
options.
Source code restrictions and limitations
Currently, the gazelle plugin makes the following assumptions about the code it's generating BUILD files for:
-
All code lives in a non-empty package. Source files must have a package
declaration, and classes depended on all themselves have a package
declaration.
-
Packages only exist in one place. Two different directories or dependencies may not contain classes which belong in the same package. The exception to this is that for each package, there may be a single test directory which uses the same package as that package's non-test directory.
-
There are no circular dependencies that extend beyond a single package. If these are present, and can't easily be removed, you may want to set # gazelle:java_module_granularity module
in the BUILD file containing the parent-most class in the dependency cycle, which may fix the problem, but will slow down your builds. Ideally, remove dependency cycles.
-
Non-test code doesn't depend on test code.
-
Non-test code used by one package of tests either lives in the same directory as those tests, or lives in a non-test-code directory. We also detect non-test code used from another test package, if that other package doesn't have a corresponding non-test code directory, but require you to manually set the visibility on the depended-on target, because this is an unexpected set-up.
-
Package names and class/interface names follow standard java conventions; that is: package names are all lower-case, and class and interface names start with Upper Case letters.
-
Code doesn't use types which it doesn't name only through unnamed means, across multiple calls. For example, if some code calls x.foo().bar()
where the return type of foo
is defined in another target, and the calling code explicitly uses a type from that target somewhere else. In the case of x.foo()
, we add exports so that the caller will have access to the return type of foo()
, but do not track dependencies on the return types across multiple calls.
This limitation could be lifted, but would require us to export all transitively used symbols from every function. This would serve to add direct dependencies between lots of targets, which can slow down compilation and reduce cache hits.
In our experience, this kind of code is rare in Java - most code tends to either introduce intermediate variables (at which point the type gets used and we detect that a dependency needs to be added), or tends to already use the targets containing the intermediate types somewhere else (at which point the dependency will already exist), but we're open to discussion about this heuristic if it poses problems for a real-world codebase.
If these assumptions are violated, the rest of the generation should still function properly, but the specific files which violate the assumptions (or depend on files which violate the assumptions) will not get complete results. We strive to emit warnings when this happens.
We are also aware of the following limitations. This list is not exhaustive, and is not intentional (i.e. if we can fix these limitations, we would like to):
- Runtime dependencies are not detected (e.g. loading classes by reflection).
Troubleshooting
If one forgets to run bazel fetch @maven//...
, the code will complain and tell
you to run this command.
If one forgets to "Update the Maven mapping", they use out of date data for the
rules resolution, and the hash check will fail. An error is printed and the
resolution does not happen.
Contibutors documentation
The following are the targets of interest:
//java/gazelle
implements a Gazelle extension
//java/gazelle/private/javaparser/cmd/javaparser-wrapper
wraps the java
parser with an activity tracker (to stop the parser) and an adapter to prevent
self imports.
//java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators:Main
is the java parser side process
The maven integration relies on using rules_jvm_external
at least as new as
https://github.com/bazelbuild/rules_jvm_external/pull/716