Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use multirun #160

Merged
merged 3 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bazel_dep(name = "aspect_bazel_lib", version = "1.38.0")
bazel_dep(name = "aspect_rules_js", version = "1.33.1")
bazel_dep(name = "bazel_skylib", version = "1.4.2")
bazel_dep(name = "platforms", version = "0.0.7")
bazel_dep(name = "rules_multirun", version = "0.7.0")
bazel_dep(name = "rules_multitool", version = "0.4.0")

# Needed in the root because we dereference ProtoInfo in our aspect impl
Expand Down
63 changes: 18 additions & 45 deletions docs/format.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions docs/formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ A formatter is just an executable target.
Then register them on the `formatters` attribute, for example:

```starlark
load("@aspect_rules_lint//format:defs.bzl", "multi_formatter_binary")
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")

multi_formatter_binary(
format_multirun(
name = "format",
# register languages, e.g.
# python = "//:ruff",
Expand Down Expand Up @@ -67,12 +67,12 @@ If you don't use pre-commit, you can just wire directly into the git hook, howev
this option will always run the formatter over all files, not just changed files.

```bash
$ echo "bazel run //:format -- --mode check" >> .git/hooks/pre-commit
$ echo "bazel run //:format.check" >> .git/hooks/pre-commit
$ chmod u+x .git/hooks/pre-commit
```

### Check that files are already formatted

This will exit non-zero if formatting is needed. You would typically run the check mode on CI.

`bazel run //:format -- --mode check`
`bazel run //tools/format:format.check`
1 change: 1 addition & 0 deletions example/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package(default_visibility = ["//visibility:public"])

compile_pip_requirements(
name = "requirements",
requirements_in = "requirements.in",
)

npm_link_all_packages(name = "node_modules")
Expand Down
6 changes: 3 additions & 3 deletions example/tools/format/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This is in its own package because it has so many loading-time symbols,
we don't want to trigger eager fetches of these for builds that don't want to run format.
"""

load("@aspect_rules_lint//format:defs.bzl", "multi_formatter_binary")
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")
load("@npm//:prettier/package_json.bzl", prettier = "bin")
load("@rules_java//java:defs.bzl", "java_binary")

Expand Down Expand Up @@ -59,7 +59,7 @@ java_binary(
runtime_deps = ["@maven//:org_scalameta_scalafmt_cli_2_13"],
)

multi_formatter_binary(
format_multirun(
name = "format",
cc = "@llvm_toolchain_llvm//:bin/clang-format",
# You can use standard gofmt instead of stricter gofumpt:
Expand All @@ -68,7 +68,7 @@ multi_formatter_binary(
javascript = ":prettier",
kotlin = ":ktfmt",
markdown = ":prettier",
protobuf = "//tools/lint:buf",
protocol_buffer = "//tools/lint:buf",
python = "//tools/lint:ruff",
scala = ":scalafmt",
sql = ":prettier",
Expand Down
5 changes: 4 additions & 1 deletion format/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ bzl_library(
name = "defs",
srcs = ["defs.bzl"],
visibility = ["//visibility:public"],
deps = ["//format/private:formatter_binary"],
deps = [
"//format/private:formatter_binary",
"@rules_multirun//:defs",
],
)

bzl_library(
Expand Down
86 changes: 68 additions & 18 deletions format/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Some formatter tools are automatically provided by default in rules_lint.
These are listed as defaults in the API docs below.

Other formatter binaries may be declared in your repository, and you can test them by running
them with Bazel.
Other formatter binaries may be declared in your repository.
You can test that they work by running them directly with `bazel run`.

For example, to add prettier, your `BUILD.bazel` file should contain:

Expand All @@ -20,43 +20,93 @@ prettier.prettier_binary(

and you can test it with `bazel run //path/to:prettier -- --help`.

Then you can register it with `multi_formatter_binary`:
Then you can register it with `format_multirun`:

```
load("@aspect_rules_lint//format:defs.bzl", "multi_formatter_binary")
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")

multi_formatter_binary(
format_multirun(
name = "format",
javascript = ":prettier",
...
)
```
"""

load("//format/private:formatter_binary.bzl", _fmt = "multi_formatter_binary")
load("@rules_multirun//:defs.bzl", "command", "multirun")
load("//format/private:formatter_binary.bzl", "CHECK_FLAGS", "DEFAULT_TOOL_LABELS", "FIX_FLAGS", "TOOLS", "to_attribute_name")

multi_formatter_binary_rule = _fmt
def format_multirun(name, **kwargs):
"""Create a multirun binary for the given formatters.

def multi_formatter_binary(name, **kwargs):
"""Wrapper macro around multi_formatter_binary_rule that sets defaults for some languages.
Intended to be used with `bazel run` to update source files in-place.

Also produces a target `[name].check` which does not edit files, rather it exits non-zero
if any sources require formatting.

Tools are provided by default for some languages.
These come from the `@multitool` repo.
Under --enable_bzlmod, rules_lint creates this automatically.
WORKSPACE users will have to set this up manually. See the release install snippet for an example.

Set any attribute to `False` to turn off that language altogether, rather than use a default tool.

Note that `javascript` is a special case which also formats TypeScript, TSX, JSON, CSS, and HTML.

Args:
name: name of the resulting target, typically "format"
**kwargs: attributes named for each language, providing Label of a tool that formats it
"""
commands = []

for lang, toolname in TOOLS.items():
lang_attribute = to_attribute_name(lang)

_fmt(
name = name,
# Logic:
# - if there's no value for this key, the user omitted it, so use our default
# - if there is a value, and it's False, then pass None to the underlying rule
# - if there is a value, and it's False, then skip this language
# (and make sure we don't eagerly reference @multitool in case it isn't defined)
# - otherwise use the user-supplied value
jsonnet = kwargs.pop("jsonnet", Label("@multitool//tools/jsonnetfmt")) or None,
go = kwargs.pop("go", Label("@multitool//tools/gofumpt")) or None,
sh = kwargs.pop("sh", Label("@multitool//tools/shfmt")) or None,
yaml = kwargs.pop("yaml", Label("@multitool//tools/yamlfmt")) or None,
**kwargs
if lang_attribute in kwargs.keys():
tool_label = kwargs.pop(lang_attribute)
else:
tool_label = Label(DEFAULT_TOOL_LABELS[lang])
if not tool_label:
continue

target_name = "_".join([name, lang.replace(" ", "_"), "with", toolname])

for mode in ["check", "fix"]:
command(
name = target_name + (".check" if mode == "check" else ""),
command = "@aspect_rules_lint//format/private:format",
description = "Formatting {} with {}...".format(lang, toolname),
environment = {
# NB: can't use str(Label(target_name)) here because bzlmod makes it
# the apparent repository, starts with @@aspect_rules_lint~override
"FIX_TARGET": "//{}:{}".format(native.package_name(), target_name),
"tool": "$(rlocationpaths %s)" % tool_label,
"lang": lang,
"flags": FIX_FLAGS[toolname] if mode == "fix" else CHECK_FLAGS[toolname],
"mode": mode,
},
data = [tool_label],
)
commands.append(target_name)

# Error checking in case some user keys were unmatched and therefore not pop'ed
for attr in kwargs.keys():
fail("""Unknown language "{}". Valid values: {}""".format(attr, [to_attribute_name(lang) for lang in TOOLS.keys()]))

multirun(
name = name,
buffer_output = True,
commands = commands,
# Run up to 4 formatters at the same time. This is an arbitrary choice, based on some idea that 4-core machines are typical.
jobs = 4,
keep_going = True,
)

multirun(
name = name + ".check",
commands = [c + ".check" for c in commands],
)
7 changes: 6 additions & 1 deletion format/private/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")

exports_files(["format.sh"])
sh_binary(
name = "format",
srcs = ["format.sh"],
visibility = ["//visibility:public"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)

bzl_library(
name = "formatter_binary",
Expand Down
Loading
Loading