Skip to content

Commit

Permalink
feat: Add rust_lint_group and cargo_lints rule to define lint gro…
Browse files Browse the repository at this point in the history
…ups (bazelbuild#2993)

Chatted about this previously in
[Slack](https://bazelbuild.slack.com/archives/CSV56UT0F/p1718030356910019).
The goal with this PR is to make it easier to define and share lint
configurations for your build.

### Description

The PR adds two new rules, `rust_lint_group` and `cargo_lints`. It also
adds a "lints" attr to `rust_library` and `rust_binary` rules that
expects a new `LintsInfo` provider.

* `rust_lint_group` allows you to define Rust, Clippy, and Rustdoc lints
in a `BUILD` file.
* `cargo_lints` automatically generates a set of lints by reading a
crate's `Cargo.toml`, and optionally the workspace's Cargo.toml for
workspace inheritance.

Then the rustc, clippy, and rustdoc actions are all updated to check for
the `LintsInfo` provider and appends the correct arguments so the lints
take effect.


### Design

Honestly this is my first large change to a set of Bazel rules, so I
definitely could have done something wrong here!

The change is split into two commits, the first introduces
`rust_lint_group` which IMO is relatively straight forward. Some
attributes are defined on a rule which are then formatted into command
line flags and passed around with a provider.

The second commit adds `cargo_lints` and is much larger, a few things
worth considering:

* I was back and forth a bit on using a `repository_rule` or a regular
`rule`. While the `repository_rule` maps well to a Cargo Workspace and
you'd only need to define it once, not everyone uses workspaces and so I
figured a regular `rule` was more general.
* Parsing a `Cargo.toml` is done via a new Rust binary called
`cargo_toml_info`. I tried to keep the external dependencies on this
binary to a minimum, it only has one at the moment which is
[`cargo_toml`](https://docs.rs/cargo_toml/latest/cargo_toml/) and I
based this change largely off of
bazelbuild#2772. I tried to make the
tool general though so other Cargo metadata rules could use it in the
future.

### Tests

There aren't any! I wasn't sure where the best place to start was, any
guidance here is appreciated!
  • Loading branch information
ParkMyCar authored Jan 8, 2025
1 parent b9d8426 commit 3d63631
Show file tree
Hide file tree
Showing 53 changed files with 3,374 additions and 25 deletions.
23 changes: 19 additions & 4 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ single_rust_channel_targets: &single_rust_channel_targets
# These tests are expected to fail as they require both a nightly and stable toolchain.
- "-//test/unit/channel_transitions/..."
- "-//test/unpretty/..."
single_rust_channel_min_version_targets: &single_rust_channel_min_version_targets
# START - Copied from 'single_rust_channel_targets'.
# TODO: Figure out how to do proper list inheritence.
- "--"
- "//..."
# TODO: Remove this and replace `cargo_bootstrap_repository` with a
# http_archive for a release: https://github.com/cross-rs/cross/issues/1356
- "-//crate_universe/tools/cross_installer/..."
# These tests are expected to fail as they require both a nightly and stable toolchain.
- "-//test/unit/channel_transitions/..."
- "-//test/unpretty/..."
# END - Copied from 'single_rust_channel_targets'.
#
# These tests exercise behavior only available versions of Rust >1.80
- "-//test/unit/lint_flags/..."
default_linux_targets: &default_linux_targets
- "--"
- "//..."
Expand Down Expand Up @@ -327,16 +342,16 @@ tasks:
name: "Min Rust Version"
platform: ubuntu2004
shell_commands: *min_rust_version_shell_commands
build_targets: *single_rust_channel_targets
test_targets: *single_rust_channel_targets
build_targets: *single_rust_channel_min_version_targets
test_targets: *single_rust_channel_min_version_targets
ubuntu2004_min_rust_version_with_aspects:
name: "Min Rust Version With Aspects"
platform: ubuntu2004
shell_commands: *min_rust_version_shell_commands
build_flags: *aspects_flags
build_targets: *single_rust_channel_targets
build_targets: *single_rust_channel_min_version_targets
test_flags: *aspects_flags
test_targets: *single_rust_channel_targets
test_targets: *single_rust_channel_min_version_targets
ubuntu2004_stable_toolchain:
name: "Only Stable Toolchain"
platform: ubuntu2004
Expand Down
6 changes: 6 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ use_repo(
"rules_rust_tinyjson",
)

cargo_internal_deps = use_extension("//cargo/private:internal_extensions.bzl", "i")
use_repo(
cargo_internal_deps,
"rrcti__cargo_toml-0.20.5",
)

rust = use_extension("//rust:extensions.bzl", "rust")
rust.toolchain(edition = "2021")
use_repo(rust, "rust_toolchains")
Expand Down
4 changes: 4 additions & 0 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ load("//crate_universe/tools/cross_installer:cross_installer_deps.bzl", "cross_i

cross_installer_deps()

load("@rules_rust//cargo:deps.bzl", "cargo_dependencies")

cargo_dependencies()

load("@rules_rust//tools/rust_analyzer:deps.bzl", "rust_analyzer_dependencies")

rust_analyzer_dependencies()
Expand Down
6 changes: 6 additions & 0 deletions cargo/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ load(
"//cargo/private:cargo_dep_env.bzl",
_cargo_dep_env = "cargo_dep_env",
)
load(
"//cargo/private:cargo_lints.bzl",
_extract_cargo_lints = "extract_cargo_lints",
)

cargo_bootstrap_repository = _cargo_bootstrap_repository
cargo_env = _cargo_env

cargo_build_script = _cargo_build_script
cargo_dep_env = _cargo_dep_env

extract_cargo_lints = _extract_cargo_lints
9 changes: 9 additions & 0 deletions cargo/deps.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
The dependencies for running the cargo_toml_info binary.
"""

load("//cargo/private/cargo_toml_info/3rdparty/crates:defs.bzl", "crate_repositories")

def cargo_dependencies():
"""Define dependencies of the `cargo` Bazel tools"""
return crate_repositories()
72 changes: 72 additions & 0 deletions cargo/private/cargo_lints.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""
Rule used to retrieve the lints specified in the [lints section] of a `Cargo.toml`.
[lints section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section)
"""

# buildifier: disable=bzl-visibility
load("//rust/private:providers.bzl", "LintsInfo")

def _extract_cargo_lints(ctx):
# Cargo.toml's to read from.
inputs = [ctx.file.manifest]
args = ctx.actions.args()

args.add("--manifest_toml={0}".format(ctx.file.manifest.path))
if ctx.attr.workspace:
inputs.append(ctx.file.workspace)
args.add("--workspace_toml={0}".format(ctx.file.workspace.path))
args.add("lints")

# Files to output our formatted arguments into.
rustc_lints_out = ctx.actions.declare_file(ctx.label.name + ".rustc" + ".lints")
clippy_lints_out = ctx.actions.declare_file(ctx.label.name + ".clippy" + ".lints")
rustdoc_lints_out = ctx.actions.declare_file(ctx.label.name + ".rustdoc" + ".lints")

args.add(rustc_lints_out)
args.add(clippy_lints_out)
args.add(rustdoc_lints_out)

outputs = [rustc_lints_out, clippy_lints_out, rustdoc_lints_out]

ctx.actions.run(
outputs = outputs,
executable = ctx.file._cargo_toml_info,
inputs = inputs,
arguments = [args],
mnemonic = "CargoLints",
progress_message = "Reading Cargo metadata to get Lints for {}".format(ctx.attr.name),
)

return [
DefaultInfo(files = depset(outputs), runfiles = ctx.runfiles(outputs)),
LintsInfo(
rustc_lint_flags = [],
rustc_lint_files = [rustc_lints_out],
clippy_lint_flags = [],
clippy_lint_files = [clippy_lints_out],
rustdoc_lint_flags = [],
rustdoc_lint_files = [rustdoc_lints_out],
),
]

extract_cargo_lints = rule(
implementation = _extract_cargo_lints,
attrs = {
"manifest": attr.label(
allow_single_file = True,
mandatory = True,
doc = "Cargo.toml to read lints from.",
),
"workspace": attr.label(
allow_single_file = True,
doc = "Workspace Cargo.toml that the manifest Cargo.toml inherits from.",
),
"_cargo_toml_info": attr.label(
allow_single_file = True,
executable = True,
default = Label("//cargo/private/cargo_toml_info:cargo_toml_info"),
cfg = "exec",
),
},
)
12 changes: 12 additions & 0 deletions cargo/private/cargo_toml_info/3rdparty/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
load("//crate_universe:defs.bzl", "crate", "crates_vendor")

crates_vendor(
name = "crates_vendor",
cargo_lockfile = "Cargo.Bazel.lock",
mode = "remote",
packages = {
"cargo_toml": crate.spec(version = "0.20.5"),
},
repository_name = "rrcti",
tags = ["manual"],
)
155 changes: 155 additions & 0 deletions cargo/private/cargo_toml_info/3rdparty/Cargo.Bazel.lock

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

38 changes: 38 additions & 0 deletions cargo/private/cargo_toml_info/3rdparty/crates/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
###############################################################################
# @generated
# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To
# regenerate this file, run the following:
#
# bazel run @@//cargo/private/cargo_toml_info/3rdparty:crates_vendor
###############################################################################

package(default_visibility = ["//visibility:public"])

exports_files(
[
"cargo-bazel.json",
"crates.bzl",
"defs.bzl",
] + glob(
include = ["*.bazel"],
allow_empty = True,
),
)

filegroup(
name = "srcs",
srcs = glob(
include = [
"*.bazel",
"*.bzl",
],
allow_empty = True,
),
)

# Workspace Member Dependencies
alias(
name = "cargo_toml",
actual = "@rrcti__cargo_toml-0.20.5//:cargo_toml",
tags = ["manual"],
)
Loading

0 comments on commit 3d63631

Please sign in to comment.