A linting framework for Bazel
You must load and configure the linting framework before anything else. This is because later rulesets that depend on the linting framework will attempt to ensure that linters are configured by registering no-op implementations of lint configs. You can do this by:
# WORKSPACE
load("@apple_rules_lint//lint:repositories.bzl", "lint_deps")
lint_deps()
load("@apple_rules_lint//lint:setup.bzl", "lint_setup")
lint_setup({
"java-checkstyle": "//your:checkstyle-config",
})
Alternatively, using Bzlmod:
# MODULE.bazel
bazel_dep(name = "apple_rules_lint", version = "0.4.0")
linter = use_extension("@apple_rules_lint//lint:extensions.bzl", "linter")
linter.configure(name = "java-checkstyle", config = "//your:checkstyle-config")
use_repo(linter, "apple_linters")
You may override specific lint configurations on a per-package basis by:
# BUILD.bazel
load("@apple_rules_lint//lint:defs.bzl", "package_lint_config")
package_lint_config({
"java-checkstyle": ":alternative-checkstyle-config",
})
Bazel may report an error like this:
ERROR: Failed to load Starlark extension '@@apple_linters//:defs.bzl'.
Cycle in the workspace file detected. This indicates that a repository is used prior to being defined.
The following chain of repository dependencies lead to the missing definition.
- @@apple_linters
This could either mean you have to add the '@@apple_linters' repository with a statement like `http_archive` in your WORKSPACE file (note that transitive dependencies are not added automatically), or move an existing definition earlier in your WORKSPACE file.
The @apple_linters
repository is defined when lint_setup
is called. You'll need to figure out where load("@apple_linters//:defs.bzl, ...)
is getting called, and modify your build to ensure that lint_setup
is called before linting is loaded.
Can be found in the api docs
To add linter support to your repo, add this to...
# repositories.bzl
load("@apple_rules_lint//lint:repositories.bzl", "lint_deps")
lint_deps()
Then add this to...
# setup.bzl
load("@apple_rules_lint//lint:setup.bzl", "ruleset_lint_setup")
ruleset_lint_setup()
Add:
# MODULE.bazel
bazel_dep(name = "apple_rules_lint", version = "0.1.1")
linter = use_extension("@apple_rules_lint//lint:extensions.bzl", "linter")
linter.register(name = "java-checkstyle")
To obtain the currently configured config for a ruleset, use:
# your_rules.bzl
load("@apple_rules_lint//lint:defs.bzl", "get_lint_config")
config = get_lint_config("java-checkstyle", tags)
if config != None:
# set up lint targets
pass
Where tags
are the tags of the rule to check.
For the sake of this example, we'll show how apple_rules_lint
is
integrated with the Selenium project, but the same process can be
followed for any linter:
-
Wrap the linter with a
_test
rule, so you can run them with bazel test. In Selenium, this is the spotbugs_test -
It is recommended, but not required, that your test return a
LinterInfo
so that other tooling can detect whether this is a lint test. -
Create a config rule or a marker rule of some sort. For example, spotbugs_config
-
Pick a "well known" name:
lang-tool
seems to work well (such asjava-spotbugs
, but you might havego-gofmt
orpy-black
) -
Create a macro that uses get_lint_config to look up the config for you. If that's present, create a new instance of your test rule. You can see this in action here.
-
As you write code, make sure your macro is called. If you're a ruleset author, this can be as lightweight as exporting the macro created above as the default way to call your rules.
-
...
-
Profit!
Users can then use the "well known" name to point to an instance of
the config rule in their WORKSPACE
files:
lint_setup({
"java-spotbugs": "//java:spotbugs-config",
})