How can I have a shared Clippy configuration for all the crates in a workspace?

3.2k views Asked by At

I have an application split into several crates. I want to deny or allow a specific lint in all crates. For example:

#![deny(clippy::print_stdout)]

It seems I have to add this to lib.rs in each of the crates.

There is an ticket with Cargo to allow configuring this somehow, but it has been open for several years with no clear conclusion.

Is there a workaround to avoid having these allow/deny/warn lines duplicated per crate?

One idea I had was to include! the lines by creating a clippy_config.rs at the workspace root, then in each crate's lib.rs adding

include!("../../clippy_config.rs");

However this fails with

error: an inner attribute is not permitted in this context
 --> app/src/../../clippy_config.rs:1:1
  |
1 | #![deny(clippy::print_stdout)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files. Outer attributes, like `#[test]`, annotate the item following them.

My other thought to use a macro also does not work for much the same reason.

Is there a simple way to do this, except writing an external script to modify the Rust files to automate the duplication? (as mentioned in this comment describing Embark Studio's setup).

2

There are 2 answers

0
user459872 On

Starting from Rust 1.74(Released on 16-Nov-2023), Cargo.toml supports [lints] table which you can use to configure the lints from the compiler and other tools.

[lints.clippy]
print_stdout = "deny"

Quoting from documentation:

As proposed in RFC 3389, the Cargo.toml manifest now supports a [lints] table to configure the reporting level (forbid, deny, warn, allow) for lints from the compiler and other tools. So rather than setting RUSTFLAGS with -F/-D/-W/-A, which would affect the entire build, or using crate-level attributes like:

#![forbid(unsafe_code)]
#![deny(clippy::enum_glob_use)]

You can now write those in your package manifest for Cargo to handle:

[lints.rust]
unsafe_code = "forbid"

[lints.clippy]
enum_glob_use = "deny"

These can also be configured in a [workspace.lints] table, then inherited by [lints] workspace = true like many other workspace settings. Cargo will also track changes to these settings when deciding which crates need to be rebuilt.

4
Matthieu M. On

From Rust 1.74 onwards

Rust and Clippy lints can be configured:

  • In Cargo.toml, see below.
  • With attributes: #[deny(clippy::print_stdout)].
  • With flags: -Dclippy::print-stdout.

The configuration in Cargo.toml can be done in several ways.

For an individual crate, using the [lints] section:

[lints.rust]
unsafe_code = "forbid"

[lints.clippy]
enum_glob_use = "deny"

For a workspace crate, using the [lints] table:

[workspace]
members = [...]

[workspace.lints.rust]
unsafe_code = "forbid"

Then in each crate in the workspace which wishes to inherit:

[lints]
workspace = true

Note: when using cargo new to create a new crate in an existing workspace, the [lints] section is automatically added.

When to use which?

Attributes are best used for one-off situations. For example, I regularly use #[allow(clippy::too_many_arguments)] on internal functions, but I wouldn't want it on public API functions.

Cargo.toml is best used for project wide settings which accommodate both the development workflow and CI. For example, I encourage the use of unsafe_ops_in_unsafe_fn = "forbid" so each unsafe operation safety pre-conditions are clearly documented, rather than a hand-wavy "trust me".

Flags are best used for workflow specific settings. For example, I would encourage -Drust::dead-code in CI, but it would be a pain during development.

Prior Versions

(Also works in Rust 1.74 onwards, just possibly less convenient)

Clippy offers 3 configuration modes:

  • Attributes: #[deny(clippy::print_stdout)]
  • Flags: -Dclippy::print-stdout
  • Configuration file: clippy.toml, though restricted to a subset of configurable lints.

For project-wide configuration, across crates, the configuration file is the best option if it works.

Otherwise, the second best (hacky) option is to use flags indirectly. That is, rather than specifying RUSTFLAGS=-Dclippy::print-stdout in front of your compiler invocation, you can let Cargo do it, and configure Cargo project-wide.

At the root of your project, create a .cargo/config.toml file with the following content:

[build]
rustflags = ["-Dclippy::print-stdout"]

Cargo will then pass this flag to clippy when it invokes it.

Note: in a workspace setting, .cargo/config.toml in individual crate folders is ignored when invoking Cargo from the root of the workspace, so best place this in the .cargo at the root.