bazel-contrib / rules_jvm

Contributed Bazel rules that make working with java projects more pleasant
Apache License 2.0
46 stars 60 forks source link

contrib_rules_jvm

Handy rules for working with JVM-based projects in Bazel.

This ruleset is designed to complement rules_java (and Bazel's built-in Java rules), not replace them.

The intended way of working is that the standard rules_java rules are used, and this ruleset adds extra functionality which is compatible with rules_java.

Using these rules

In order to use these in your own projects, in your WORKSPACE once you've used an http_archive, you can load all the necessary dependencies by:

load("@contrib_rules_jvm//:repositories.bzl", "contrib_rules_jvm_deps")

contrib_rules_jvm_deps()

load("@contrib_rules_jvm//:setup.bzl", "contrib_rules_jvm_setup")

contrib_rules_jvm_setup()

If you're looking to get started quickly, then take a look at java_test_suite (a macro for generating a test suite from a glob of java test sources) and java_junit5_test (a drop-in replacement for java_test that can run JUnit5 tests)

Linting

Many of the features in this repo are designed to be exposed via apple_rules_lint, which provides a framework for integrating linting checks into your builds. To take advantage of this perform the following steps:

# In your WORKSPACE, after loading `apple_rules_lint`

load("@apple_rules_lint//lint:setup.bzl", "lint_setup")

lint_setup({
  # Note: this is an example config!
  "java-checkstyle": "@contrib_rules_jvm//java:checkstyle-default-config",
  "java-pmd": "@contrib_rules_jvm//java:pmd-config",
  "java-spotbugs": "@contrib_rules_jvm//java:spotbugs-default-config",
})

You are welcome to include all (or none!) of these rules, and linting is "opt-in": if there's no lint_setup call in your repo's WORKSPACE then everything will continue working just fine and no additional lint tests will be generated.

The linters are configured using specific rules. The mappings are:

Well known name Lint config rule
java-checkstyle checkstyle_config
java-pmd pmd_ruleset
java-spotbugs spotbugs_config

Requirements

These rules require Java 11 or above.

The gazelle plugin requires Go 1.18 or above.

Java Rules

checkstyle_config

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "checkstyle_config")

checkstyle_config(name, data, checkstyle_binary, config_file, output_format)

Rule allowing checkstyle to be configured. This is typically used with the linting rules from @apple_rules_lint to configure how checkstyle should run.

ATTRIBUTES

Name Description Type Mandatory Default
name A unique name for this target. Name required
data Additional files to make available to Checkstyle such as any included XML files List of labels optional []
checkstyle_binary Checkstyle binary to use. Label optional "@contrib_rules_jvm//java:checkstyle_cli"
config_file The config file to use for all checkstyle tests Label required
output_format Output format to use. Defaults to plain String optional "plain"

pmd_ruleset

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "pmd_ruleset")

pmd_ruleset(name, format, pmd_binary, rulesets, shallow)

Select a rule set for PMD tests.

ATTRIBUTES

Name Description Type Mandatory Default
name A unique name for this target. Name required
format Generate report in the given format. One of html, text, or xml (default is xml) String optional "xml"
pmd_binary PMD binary to use. Label optional "@contrib_rules_jvm//java:pmd"
rulesets Use these rulesets. List of labels optional []
shallow Use the targetted output to increase PMD's depth of processing Boolean optional True

pmd_test

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "pmd_test")

pmd_test(name, srcs, ruleset, target)

Use PMD to lint the srcs.

ATTRIBUTES

Name Description Type Mandatory Default
name A unique name for this target. Name required
srcs - List of labels optional []
ruleset - Label required
target - Label optional None

spotbugs_config

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "spotbugs_config")

spotbugs_config(name, effort, exclude_filter, fail_on_warning, plugin_list, spotbugs_binary)

Configuration used for spotbugs, typically by the //lint rules.

ATTRIBUTES

Name Description Type Mandatory Default
name A unique name for this target. Name required
effort Effort can be min, less, default, more or max. Defaults to default String optional "default"
exclude_filter Report all bug instances except those matching the filter specified by this filter file Label optional None
fail_on_warning Whether to fail on warning, or just create a report. Defaults to True Boolean optional True
plugin_list Specify a list of plugin Jar files to load List of labels optional []
spotbugs_binary The spotbugs binary to run. Label optional "@contrib_rules_jvm//java:spotbugs_cli"

spotbugs_test

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "spotbugs_test")

spotbugs_test(name, deps, config, only_output_jars)

Use spotbugs to lint the srcs.

ATTRIBUTES

Name Description Type Mandatory Default
name A unique name for this target. Name required
deps - List of labels required
config - Label optional "@contrib_rules_jvm//java:spotbugs-default-config"
only_output_jars If set to true, only the output jar of the target will be analyzed. Otherwise all transitive runtime dependencies will be analyzed Boolean optional True

checkstyle_binary

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "checkstyle_binary")

checkstyle_binary(name, main_class, deps, runtime_deps, srcs, visibility, kwargs)

Macro for quickly generating a java_binary target for use with checkstyle_config.

By default, this will set the main_class to point to the default one used by checkstyle but it's ultimately a drop-replacement for straight java_binary target.

At least one of runtime_deps, deps, and srcs must be specified so that the java_binary target will be valid.

An example would be:

checkstyle_binary(
    name = "checkstyle_cli",
    runtime_deps = [
        artifact("com.puppycrawl.tools:checkstyle"),
    ]
)

PARAMETERS

Name Description Default Value
name The name of the target none
main_class The main class to use for checkstyle. "com.puppycrawl.tools.checkstyle.Main"
deps The deps required for compiling this binary. May be omitted. None
runtime_deps The deps required by checkstyle at runtime. May be omitted. None
srcs If you're compiling your own checkstyle binary, the sources to use. None
visibility

-

["//visibility:public"]
kwargs

-

none

checkstyle_test

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "checkstyle_test")

checkstyle_test(name, size, timeout, kwargs)

PARAMETERS

Name Description Default Value
name

-

none
size

-

"medium"
timeout

-

"short"
kwargs

-

none

java_binary

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_binary")

java_binary(name, kwargs)

Adds linting tests to Bazel's own java_binary

PARAMETERS

Name Description Default Value
name

-

none
kwargs

-

none

java_export

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_export")

java_export(name, maven_coordinates, pom_template, deploy_env, visibility, kwargs)

Adds linting tests to rules_jvm_external's java_export

PARAMETERS

Name Description Default Value
name

-

none
maven_coordinates

-

none
pom_template

-

None
deploy_env

-

None
visibility

-

None
kwargs

-

none

java_junit5_test

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_junit5_test")

java_junit5_test(name, test_class, runtime_deps, package_prefixes, jvm_flags, include_tags,
                 exclude_tags, include_engines, exclude_engines, kwargs)

Run junit5 tests using Bazel.

This is designed to be a drop-in replacement for java_test, but rather than using a JUnit4 runner it provides support for using JUnit5 directly. The arguments are the same as used by java_test.

By default Bazel, and by extension this rule, assumes you want to always run all of the tests in a class file. The include_tags and exclude_tags allows for selectively running specific tests within a single class file based on your use of the @Tag Junit5 annotations. Please see the JUnit 5 docs for more information about using JUnit5 tag annotation to control test execution.

The generated target does not include any JUnit5 dependencies. If you are using the standard @maven namespace for your maven_install you can add these to your deps using JUNIT5_DEPS or JUNIT5_VINTAGE_DEPS loaded from //java:defs.bzl

Note: The junit5 runner prevents System.exit being called using a SecurityManager, which means that one test can't prematurely cause an entire test run to finish unexpectedly. This security measure prohibits tests from setting their own SecurityManager. To override this, set the bazel.junit5runner.allowSettingSecurityManager system property.

While the SecurityManager has been deprecated in recent Java releases, there's no replacement yet. JEP 411 has this as one of its goals, but this is not complete or available yet.

PARAMETERS

Name Description Default Value
name The name of the test. none
test_class The Java class to be loaded by the test runner. If not specified, the class name will be inferred from a combination of the current bazel package and the name attribute. None
runtime_deps

-

[]
package_prefixes

-

[]
jvm_flags

-

[]
include_tags Junit5 tag expressions to include execution of tagged tests. []
exclude_tags Junit tag expressions to exclude execution of tagged tests. []
include_engines A list of JUnit Platform test engine IDs to include. []
exclude_engines A list of JUnit Platform test engine IDs to exclude. []
kwargs

-

none

java_library

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_library")

java_library(name, kwargs)

Adds linting tests to Bazel's own java_library

PARAMETERS

Name Description Default Value
name

-

none
kwargs

-

none

java_test

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_test")

java_test(name, kwargs)

Adds linting tests to Bazel's own java_test

PARAMETERS

Name Description Default Value
name

-

none
kwargs

-

none

java_test_suite

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "java_test_suite")

java_test_suite(name, srcs, runner, test_suffixes, package, deps, runtime_deps, size, kwargs)

Create a suite of java tests from *Test.java files.

This rule will create a java_test for each file which matches any of the test_suffixes that are passed to this rule as srcs. If any non-test sources are added these will first of all be compiled into a java_library which will be added as a dependency for each test target, allowing common utility functions to be shared between tests.

The generated java_test targets will be named after the test file: FooTest.java will create a :FooTest target.

In addition, a test_suite will be created, named using the name attribute to allow all the tests to be run in one go.

PARAMETERS

Name Description Default Value
name A unique name for this rule. Will be used to generate a test_suite none
srcs Source files to create test rules for. none
runner One of junit4 or junit5. "junit4"
test_suffixes The file name suffix used to identify if a file contains a test class. ["Test.java"]
package The package name used by the tests. If not set, this is inferred from the current bazel package name. None
deps A list of java_* dependencies. None
runtime_deps A list of java_* dependencies needed at runtime. []
size The size of the test, passed to java_test None
kwargs

-

none

pmd_binary

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "pmd_binary")

pmd_binary(name, main_class, deps, runtime_deps, srcs, visibility, kwargs)

Macro for quickly generating a java_binary target for use with pmd_ruleset.

By default, this will set the main_class to point to the default one used by PMD but it's ultimately a drop-replacement for a regular java_binary target.

At least one of runtime_deps, deps, and srcs must be specified so that the java_binary target will be valid.

An example would be:

pmd_binary(
    name = "pmd",
    runtime_deps = [
        artifact("net.sourceforge.pmd:pmd-dist"),
    ],
)

PARAMETERS

Name Description Default Value
name The name of the target none
main_class The main class to use for PMD. "net.sourceforge.pmd.cli.PmdCli"
deps The deps required for compiling this binary. May be omitted. None
runtime_deps The deps required by PMD at runtime. May be omitted. None
srcs If you're compiling your own PMD binary, the sources to use. None
visibility

-

["//visibility:public"]
kwargs

-

none

spotbugs_binary

load("@contrib_rules_jvm//docs:stardoc-input.bzl", "spotbugs_binary")

spotbugs_binary(name, main_class, deps, runtime_deps, srcs, visibility, kwargs)

Macro for quickly generating a java_binary target for use with spotbugs_config.

By default, this will set the main_class to point to the default one used by spotbugs but it's ultimately a drop-replacement for a regular java_binary target.

At least one of runtime_deps, deps, and srcs must be specified so that the java_binary target will be valid.

An example would be:

spotbugs_binary(
    name = "spotbugs_cli",
    runtime_deps = [
        artifact("com.github.spotbugs:spotbugs"),
        artifact("org.slf4j:slf4j-jdk14"),
    ],
)

PARAMETERS

Name Description Default Value
name The name of the target none
main_class The main class to use for spotbugs. "edu.umd.cs.findbugs.LaunchAppropriateUI"
deps The deps required for compiling this binary. May be omitted. None
runtime_deps The deps required by spotbugs at runtime. May be omitted. None
srcs If you're compiling your own spotbugs binary, the sources to use. None
visibility

-

["//visibility:public"]
kwargs

-

none

Freezing Dependencies

At runtime, a handful of dependencies are required by helper classes in this project. Rather than pollute the default @maven workspace, these are loaded into a @contrib_rules_jvm_deps workspace. These dependencies are loaded using a call to maven_install, but we don't want to force users to remember to load our own dependencies for us. Instead, to add a new dependency to the project:

  1. Update contrib_rules_jvm_deps in the MODULE.bazel file
  2. Run ./tools/update-dependencies.sh
  3. Commit the updated files.

Freezing your dependencies

As noted above, if you are building Bazel rules which require Java parts, and hence Java dependencies, it can be useful to freeze these dependencies. This process captures the list of dependencies into a zip file. This zip file is distributed with your Bazel rules. This makes your rule more hermetic, your rule no longer relies on the user to supply the correct dependencies, because they get resolved under their own repository namespace rather than being intermingled with the user's, and your dependencies no longer conflict with the users selections. If you would like to create your dependencies as a frozen file you need to do the following:

  1. Create a maven install rule with your dependencies and with a unique name, this should be in or referenced by your WORKSPACE file.

    maven_install(
        name = "frozen_deps",
        artifacts = [...],
        fail_if_repin_required = True,
        fetch_sources = True,
        maven_install_json = "@workspace//:frozen_deps_install.json",
    )
    
    load("@frozen_deps//:defs.bzl", "pinned_maven_install")
    
    pinned_maven_install()
  2. Run bazel run //tools:freeze-deps -- --repo <repo name> --zip <path/to/dependency.zip>. The <repo name> matches the name used for the maven_install() rule above. This will pin the dependencies then collect them into the zip file.
  3. Commit the zip file into your repo.
  4. Add a zip_repository() rule to your WORKSPACE to configure the frozen dependencies:
    maybe(
       zip_repository,
       name = "workspace_deps",
       path = "@workspace//path/to:dependency.zip",
    )
  5. Make sure to pin the maven install from the repostitory:

    load("@workspace_deps//:defs.bzl", "pinned_maven_install")
    
    pinned_maven_install()
  6. Use the dependencies for your java_library rules from the frozen deps:
    java_library(
        name = "my_library",
        srcs = glob(["*.java"]),
        deps = [
             "@workspace_deps//:my_frozen_dep",
        ],
    )

NOTE: If you need to generate the compat_repositories for the dependencies, usually because your rule depends on another rule which is still using the older compat repositories, you need to make the following changes and abide by the following restrictions.

  1. Add the generate_compat_repositories = True, attribute to the original maven_install() rule.
  2. In step 2, add the parameter --zip-repo workspace_deps to match the name used in the zip_repository() rule (step 4). If you don't supply this, it uses the base name of the zip file, which may not be what you want.
  3. In step 5, add the call to generate the compat_repositories:

    load("@workspace_deps//:compat.bzl", "compat_repositories")
    
    compat_repositories()