feat(compile): Stabilize build.warnings (#16796)

*[View all
comments](https://triagebot.infra.rust-lang.org/gh-comments/rust-lang/cargo/pull/16796)*

### What does this PR try to resolve?

This allows users to either
- hide warnings (#14258)
- error on warnings (#8424)

`build.warnings` serves a similar purpose as `RUSTFLAGS=-Dwarnings` /
`RUSTFLAGS=-Awarnings` but without invalidation caches.

`build.warnings = "deny"` will
- only errors for lint warnings and not hard warnings
- only errors for local warnings and not non-local warnings visible with
`--verbose --verbose`
- stop the build without `--keep-going`

(this matches `RUSTFLAGS=-Dwarnings`)

These conditions were not originally met and also came as feedback from
rust-lang/rust which has been dogfooding this since the merge of
rust-lang/rust#148332.

`build.warnings = "allow"` will
- only hide lint warnings and not hard warnings
  - Note: `RUSTFLAGS=-Awarnings` will suppress rustc hard warnings
- hide non-local warnings for `--verbose --verbose`
- hide the warning summary line (number of warnings per crate)

Closes #14802

### How to test and review this PR?

My main concern over this was how the naming scheme would extend to
https://github.com/rust-lang/rfcs/pull/3730 but that RFC has not gained
much interest

`build` seems as good of a home as any.
This commit is contained in:
Weihang Lo
2026-04-24 20:35:58 +00:00
committed by GitHub
7 changed files with 59 additions and 86 deletions
+3 -2
View File
@@ -913,7 +913,6 @@ unstable_cli_options!(
target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"),
trim_paths: bool = ("Enable the `trim-paths` option in profiles"),
unstable_options: bool = ("Allow the usage of unstable options"),
warnings: bool = ("Allow use of the build.warnings config key"),
);
const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \
@@ -1002,6 +1001,8 @@ const STABILIZED_CONFIG_INCLUDE: &str = "The `include` config key is now always
const STABILIZED_LOCKFILE_PATH: &str = "The `lockfile-path` config key is now always available";
const STABILIZED_WARNINGS: &str = "The `build.warnings` config key is now always available";
fn deserialize_comma_separated_list<'de, D>(
deserializer: D,
) -> Result<Option<Vec<String>>, D::Error>
@@ -1391,6 +1392,7 @@ impl CliUnstable {
"build-dir" => stabilized_warn(k, "1.91", STABILIZED_BUILD_DIR),
"config-include" => stabilized_warn(k, "1.93", STABILIZED_CONFIG_INCLUDE),
"lockfile-path" => stabilized_warn(k, "1.97", STABILIZED_LOCKFILE_PATH),
"warnings" => stabilized_warn(k, "1.97", STABILIZED_WARNINGS),
// Unstable features
// Sorted alphabetically:
@@ -1457,7 +1459,6 @@ impl CliUnstable {
"target-applies-to-host" => self.target_applies_to_host = parse_empty(k, v)?,
"panic-immediate-abort" => self.panic_immediate_abort = parse_empty(k, v)?,
"unstable-options" => self.unstable_options = parse_empty(k, v)?,
"warnings" => self.warnings = parse_empty(k, v)?,
_ => bail!(
"\
unknown `-Z` flag specified: {k}\n\n\
+1 -5
View File
@@ -2147,11 +2147,7 @@ impl GlobalContext {
/// Get the global [`WarningHandling`] configuration.
pub fn warning_handling(&self) -> CargoResult<WarningHandling> {
if self.unstable_flags.warnings {
Ok(self.build_config()?.warnings.unwrap_or_default())
} else {
Ok(WarningHandling::default())
}
Ok(self.build_config()?.warnings.unwrap_or_default())
}
pub fn ws_roots(&self) -> MutexGuard<'_, HashMap<PathBuf, WorkspaceRootConfig>> {
@@ -204,7 +204,34 @@ This tries to balance thoroughness with turnaround time:
- `cargo check` is used as most issues contributors will run into are API availability and not behavior.
- Unpublished packages are skipped as this assumes only consumers of the verified project, through a registry, will care about `rust-version`.
## Checking for warnings
Customarily, projects want to be "warnings clean" on official branches while being lax for local development.
[`build.warnings = "deny"`] can be used to fail a CI job if warnings are present.
An example CI job to check for warnings using GitHub Actions:
```yaml
jobs:
warnings:
runs-on: ubuntu-latest
env:
CARGO_BUILD_WARNINGS: deny
steps:
- uses: actions/checkout@v4
- run: rustup update stable && rustup default stable
- run: rustup component add clippy
- run: cargo clippy --all-targets --all-features --keep-going
```
Considerations:
- CI can fail due to new toolchain versions because there are limited compatibility guarantees around warnings.
Consider pinning the toolchain version with an automated job that creates a PR to upgrade the toolchain on new releases.
- Balance between exhaustiveness and turnaround time in selecting the combinations of platforms, features, and package/build-target combinations to check
- Some CI systems have direct integration for reporting lints, e.g. using [`clippy-sarif`] with GitHub
[`build.warnings = "deny"`]: ../reference/config.md#buildwarnings
[`cargo add`]: ../commands/cargo-add.md
[`cargo install`]: ../commands/cargo-install.md
[`clippy-sarif`]: https://crates.io/crates/clippy-sarif
[Dependabot]: https://docs.github.com/en/code-security/dependabot/working-with-dependabot
[RenovateBot]: https://renovatebot.com/
+18
View File
@@ -64,6 +64,7 @@ recursive_example = "rr --example recursions"
space_example = ["run", "--release", "--", "\"command list\""]
[build]
warnings = "warn" # adjust the effective lint level for warnings
jobs = 1 # number of parallel jobs, defaults to # of CPUs
rustc = "rustc" # the rust compiler tool
rustc-wrapper = "…" # run this wrapper instead of `rustc`
@@ -458,6 +459,23 @@ recursive_example = "rr --example recursions"
The `[build]` table controls build-time operations and compiler settings.
### `build.warnings`
* Type: string
* Default: `"warn"`
* Environment: `CARGO_BUILD_WARNINGS`
Adjust the effective level of lint warnings for local packages.
Allowed levels are:
* `"warn"`: continue to emit the lints as warnings (default).
* `"allow"`: hide the lints.
* `"deny"`: emit an error for a crate that has lint warnings.
Use `--keep-going` to see the lint warnings for all dependent crates.
Only warnings that are lints (i.e. level is adjustable) are affected,
e.g. leaving as-is non-lint warnings or warnings from dependencies visible through `--verbose --verbose`.
> **MSRV:** Respected as of 1.97.
#### `build.jobs`
* Type: integer or string
* Default: number of logical CPUs
+4 -23
View File
@@ -131,7 +131,6 @@ Each new feature described below should explain how to use it.
* [gitoxide](#gitoxide) --- Use `gitoxide` instead of `git2` for a set of operations.
* [script](#script) --- Enable support for single-file `.rs` packages.
* [native-completions](#native-completions) --- Move cargo shell completions to native completions.
* [warnings](#warnings) --- controls warning behavior; options for allowing or denying warnings.
* [Package message format](#package-message-format) --- Message format for `cargo package`.
* [`fix-edition`](#fix-edition) --- A permanently unstable edition migration helper.
* [Plumbing subcommands](https://github.com/crate-ci/cargo-plumbing) --- Low, level commands that act as APIs for Cargo, like `cargo metadata`
@@ -1806,28 +1805,6 @@ When in doubt, you can discuss this in [#14520](https://github.com/rust-lang/car
- powershell:
Add `CARGO_COMPLETE=powershell cargo +nightly | Invoke-Expression` to `$PROFILE`.
## warnings
* Original Issue: [#8424](https://github.com/rust-lang/cargo/issues/8424)
* Tracking Issue: [#14802](https://github.com/rust-lang/cargo/issues/14802)
The `-Z warnings` feature enables the `build.warnings` configuration option to control how
Cargo handles warnings. If the `-Z warnings` unstable flag is not enabled, then
the `build.warnings` config will be ignored.
This setting currently only applies to rustc warnings. It may apply to additional warnings (such as Cargo lints or Cargo warnings)
in the future.
### `build.warnings`
* Type: string
* Default: `warn`
* Environment: `CARGO_BUILD_WARNINGS`
Controls how Cargo handles warnings. Allowed values are:
* `warn`: warnings are emitted as warnings (default).
* `allow`: warnings are hidden.
* `deny`: if warnings are emitted, an error will be raised at the end of the current crate and the process. Use `--keep-going` to see all warnings.
## feature unification
* RFC: [#3692](https://github.com/rust-lang/rfcs/blob/master/text/3692-feature-unification.md)
@@ -2319,3 +2296,7 @@ The `pubtime` index field has been stabilized in Rust 1.94.0.
## lockfile-path
Support for `resolver.lockfile-path` config field has been stabilized in Rust 1.97.0.
## warnings
The `build.warnings` config field has been stabilized in Rust 1.97.
+6 -8
View File
@@ -1,4 +1,4 @@
<svg width="1255px" height="992px" xmlns="http://www.w3.org/2000/svg">
<svg width="1255px" height="974px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { fill: #000000 }
@@ -112,17 +112,15 @@
</tspan>
<tspan x="10px" y="874px"><tspan> -Z unstable-options Allow the usage of unstable options</tspan>
</tspan>
<tspan x="10px" y="892px"><tspan> -Z warnings Allow use of the build.warnings config key</tspan>
<tspan x="10px" y="892px">
</tspan>
<tspan x="10px" y="910px">
<tspan x="10px" y="910px"><tspan>Run with `cargo -Z [FLAG] [COMMAND]`</tspan>
</tspan>
<tspan x="10px" y="928px"><tspan>Run with `cargo -Z [FLAG] [COMMAND]`</tspan>
<tspan x="10px" y="928px">
</tspan>
<tspan x="10px" y="946px">
<tspan x="10px" y="946px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
</tspan>
<tspan x="10px" y="964px"><tspan>See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html for more information about these flags.</tspan>
</tspan>
<tspan x="10px" y="982px">
<tspan x="10px" y="964px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

-48
View File
@@ -22,31 +22,11 @@ fn make_project_with_rustc_warning() -> Project {
.build()
}
#[cargo_test]
fn requires_nightly() {
// build.warnings has no effect without -Zwarnings.
let p = make_project_with_rustc_warning();
p.cargo("check")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
[CHECKING] foo v0.0.1 ([ROOT]/foo)
[WARNING] unused variable: `x`
...
[WARNING] `foo` (bin "foo") generated 1 warning[..]
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test]
fn always_show_error_diags() {
let p = make_project_with_rustc_warning();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.env("RUSTFLAGS", "-Dunused_variables")
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='allow'")
.with_stderr_data(str![[r#"
@@ -76,8 +56,6 @@ fn clippy() {
.build();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.env("RUSTC_WORKSPACE_WRAPPER", tools::wrapped_clippy_driver())
@@ -97,8 +75,6 @@ fn clippy() {
fn config() {
let p = make_project_with_rustc_warning();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.env("CARGO_BUILD_WARNINGS", "deny")
.with_stderr_data(str![[r#"
[CHECKING] foo v0.0.1 ([ROOT]/foo)
@@ -113,8 +89,6 @@ fn config() {
// CLI has precedence over env
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='warn'")
.env("CARGO_BUILD_WARNINGS", "deny")
@@ -132,8 +106,6 @@ fn config() {
fn unknown_value() {
let p = make_project_with_rustc_warning();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='forbid'")
.with_stderr_data(str![[r#"
@@ -166,8 +138,6 @@ fn keep_going() {
.build();
p.cargo("build")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
@@ -184,8 +154,6 @@ fn keep_going() {
assert!(!p.bin("foo").is_file());
p.cargo("build --keep-going")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
@@ -209,8 +177,6 @@ fn keep_going() {
fn rustc_caching_allow_first() {
let p = make_project_with_rustc_warning();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='allow'")
.with_stderr_data(str![[r#"
@@ -221,8 +187,6 @@ fn rustc_caching_allow_first() {
.run();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
@@ -240,8 +204,6 @@ fn rustc_caching_allow_first() {
fn rustc_caching_deny_first() {
let p = make_project_with_rustc_warning();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
@@ -256,8 +218,6 @@ fn rustc_caching_deny_first() {
.run();
p.cargo("check")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='allow'")
.with_stderr_data(str![[r#"
@@ -328,8 +288,6 @@ fn hard_warning_deny() {
// Behavior under test
p.cargo("rustc")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.arg("--")
@@ -407,8 +365,6 @@ fn hard_warning_allow() {
// Behavior under test
p.cargo("rustc")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='allow'")
.arg("--")
@@ -489,8 +445,6 @@ fn cap_lints_deny() {
// Behavior under test
p.cargo("check -vv")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='deny'")
.with_stderr_data(str![[r#"
@@ -563,8 +517,6 @@ fn cap_lints_allow() {
// Behavior under test
p.cargo("check -vv")
.masquerade_as_nightly_cargo(&["warnings"])
.arg("-Zwarnings")
.arg("--config")
.arg("build.warnings='allow'")
.with_stderr_data(str![[r#"