From 8fd93091024086bbf8e3aafa38fb85a3f4258559 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Fri, 11 Jul 2025 16:53:50 -0700 Subject: [PATCH 01/11] Remove unnecessary parentheses around closure body, to fix unused-parens warning --- src/cargo/sources/registry/index/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cargo/sources/registry/index/cache.rs b/src/cargo/sources/registry/index/cache.rs index 3a9017dafdc..eea1c894b74 100644 --- a/src/cargo/sources/registry/index/cache.rs +++ b/src/cargo/sources/registry/index/cache.rs @@ -204,7 +204,7 @@ impl<'a> SummariesCache<'a> { let size = self .versions .iter() - .map(|(_version, data)| (10 + data.len())) + .map(|(_version, data)| 10 + data.len()) .sum(); let mut contents = Vec::with_capacity(size); contents.push(CURRENT_CACHE_VERSION); From a206766e435ad3db22d9a1db0fb89f9399dbf8dc Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 17 Jun 2025 23:53:48 -0700 Subject: [PATCH 02/11] Add initial version of hints tests, to show pre-`[hints]` Cargo behavior These tests show what prior versions of Cargo will do with hints. The subsequent addition of support for hints will modify these tests to reflect the corresponding changes to Cargo. --- tests/testsuite/hints.rs | 59 ++++++++++++++++++++++++++++++++++++++++ tests/testsuite/main.rs | 1 + 2 files changed, 60 insertions(+) create mode 100644 tests/testsuite/hints.rs diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs new file mode 100644 index 00000000000..2618479781b --- /dev/null +++ b/tests/testsuite/hints.rs @@ -0,0 +1,59 @@ +//! Tests for hints. + +use crate::prelude::*; +use cargo_test_support::{project, str}; + +#[cargo_test] +fn empty_hints_warn() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [hints] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -v") + .with_stderr_data(str![[r#" +[WARNING] unused manifest key: hints +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + +#[cargo_test] +fn unknown_hints_warn() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [hints] + this-is-an-unknown-hint = true + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -v") + .with_stderr_data(str![[r#" +[WARNING] unused manifest key: hints +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index bcff5664a24..0c7b69ee147 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -106,6 +106,7 @@ mod git_shallow; mod glob_targets; mod global_cache_tracker; mod help; +mod hints; mod https; mod inheritable_workspace_fields; mod install; From f415c112f977c6aba6d9c245e8ecdb43050d9c3a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 18 Jun 2025 10:36:03 -0700 Subject: [PATCH 03/11] Add additional tests for `hints.mostly-unused` (showing existing behavior) These tests demonstrate the behavior of current Cargo, and will be changed when adding `hints.mostly-unused` to reflect the new behavior. --- tests/testsuite/hints.rs | 236 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index 2618479781b..1a69f730384 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -1,6 +1,7 @@ //! Tests for hints. use crate::prelude::*; +use cargo_test_support::registry::Package; use cargo_test_support::{project, str}; #[cargo_test] @@ -57,3 +58,238 @@ fn unknown_hints_warn() { "#]]) .run(); } + +#[cargo_test] +fn hint_unknown_type_warn() { + Package::new("bar", "1.0.0") + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "1.0.0" + edition = "2015" + + [hints] + mostly-unused = 1 + "#, + ) + .file("src/lib.rs", "") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -v") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_stderr_does_not_contain("-Zhint-mostly-unused") + .run(); +} + +#[cargo_test] +fn hints_mostly_unused_warn_without_gate() { + Package::new("bar", "1.0.0") + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "1.0.0" + edition = "2015" + + [hints] + mostly-unused = true + "#, + ) + .file("src/lib.rs", "") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -v") + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_stderr_does_not_contain("-Zhint-mostly-unused") + .run(); +} + +#[cargo_test(nightly, reason = "-Zhint-mostly-unused is unstable")] +fn hints_mostly_unused_nightly() { + Package::new("bar", "1.0.0") + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "1.0.0" + edition = "2015" + + [hints] + mostly-unused = true + "#, + ) + .file("src/lib.rs", "") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [dependencies] + bar = "1.0" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -Zprofile-hint-mostly-unused -v") + .masquerade_as_nightly_cargo(&["profile-hint-mostly-unused"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_stderr_does_not_contain( + "[RUNNING] `rustc --crate-name foo [..] -Zhint-mostly-unused [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zhint-mostly-unused is unstable")] +fn mostly_unused_profile_overrides_hints_nightly() { + Package::new("bar", "1.0.0") + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "1.0.0" + edition = "2015" + + [hints] + mostly-unused = true + "#, + ) + .file("src/lib.rs", "") + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [dependencies] + bar = "1.0" + + [profile.dev.package.bar] + hint-mostly-unused = false + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -Zprofile-hint-mostly-unused -v") + .masquerade_as_nightly_cargo(&["profile-hint-mostly-unused"]) + .with_stderr_data(str![[r#" +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_stderr_does_not_contain("-Zhint-mostly-unused") + .run(); +} + +#[cargo_test(nightly, reason = "-Zhint-mostly-unused is unstable")] +fn mostly_unused_profile_overrides_hints_on_self_nightly() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + edition = "2015" + + [hints] + mostly-unused = true + + [profile.dev] + hint-mostly-unused = false + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("check -v") + .with_stderr_data(str![[r#" +[WARNING] unused manifest key: hints +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .with_stderr_does_not_contain("-Zhint-mostly-unused") + .run(); +} From 1921d097ca06015865096fea37d8fdb70296b4ff Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 16 Jun 2025 10:30:35 -0700 Subject: [PATCH 04/11] Add `[hints]` table in `Cargo.toml`, and a `hints.mostly-unused` hint The `[hints]` table in a `Cargo.toml` manifest provides optional information that Cargo can use for building the package, and will use even when using the package as a dependency. All hints can be safely ignored, and Cargo only warns about unknown hints, but does not error. This allows packages to use hints without depending on new Cargo. Add a `mostly-unused` hint, which allows a package to hint that most users of the package will not use most of its items. This is useful for improving the build performance of crates with large dependencies. Crates can override this hint using `hint-mostly-unused = false` in their profile for a dependency. --- .../cargo-util-schemas/manifest.schema.json | 21 ++++++++++++++++++ crates/cargo-util-schemas/src/manifest/mod.rs | 9 ++++++++ src/cargo/core/compiler/mod.rs | 15 +++++++++---- src/cargo/core/manifest.rs | 9 +++++++- src/cargo/core/package.rs | 10 ++++++++- src/cargo/core/profiles.rs | 8 +++---- src/cargo/util/toml/mod.rs | 7 ++++++ tests/testsuite/hints.rs | 22 ++++++++++--------- 8 files changed, 81 insertions(+), 20 deletions(-) diff --git a/crates/cargo-util-schemas/manifest.schema.json b/crates/cargo-util-schemas/manifest.schema.json index 7ceca2589bb..3b17f326762 100644 --- a/crates/cargo-util-schemas/manifest.schema.json +++ b/crates/cargo-util-schemas/manifest.schema.json @@ -167,6 +167,16 @@ } ] }, + "hints": { + "anyOf": [ + { + "$ref": "#/$defs/Hints" + }, + { + "type": "null" + } + ] + }, "workspace": { "anyOf": [ { @@ -1017,6 +1027,17 @@ "$ref": "#/$defs/TomlValue" } }, + "Hints": { + "type": "object", + "properties": { + "mostly-unused": { + "type": [ + "boolean", + "null" + ] + } + } + }, "TomlWorkspace": { "type": "object", "properties": { diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index bca8d15056e..bf42b327578 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -56,6 +56,7 @@ pub struct TomlManifest { pub build_dependencies2: Option>, pub target: Option>, pub lints: Option, + pub hints: Option, pub workspace: Option, pub profile: Option, @@ -85,6 +86,7 @@ impl TomlManifest { .map(|_| "build-dependencies"), self.target.as_ref().map(|_| "target"), self.lints.as_ref().map(|_| "lints"), + self.hints.as_ref().map(|_| "hints"), ] .into_iter() .flatten() @@ -1644,6 +1646,13 @@ pub enum TomlLintLevel { Allow, } +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[serde(rename_all = "kebab-case")] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +pub struct Hints { + pub mostly_unused: Option, +} + #[derive(Copy, Clone, Debug)] pub struct InvalidCargoFeatures {} diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 4b990181fe4..8ae261f2ad4 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1138,6 +1138,7 @@ fn build_base_args( hint_mostly_unused, .. } = unit.profile.clone(); + let hints = unit.pkg.hints().cloned().unwrap_or_default(); let test = unit.mode.is_any_test(); cmd.arg("--crate-name").arg(&unit.target.crate_name()); @@ -1326,13 +1327,19 @@ fn build_base_args( opt(cmd, "-C", "incremental=", Some(dir)); } - if hint_mostly_unused { + if hint_mostly_unused.or(hints.mostly_unused).unwrap_or(false) { if bcx.gctx.cli_unstable().profile_hint_mostly_unused { cmd.arg("-Zhint-mostly-unused"); } else { - bcx.gctx - .shell() - .warn("ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it")?; + if hint_mostly_unused.is_some() { + bcx.gctx + .shell() + .warn("ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it")?; + } else if hints.mostly_unused.is_some() { + bcx.gctx + .shell() + .warn("ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it")?; + } } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index c9decda4d1e..273bd21df80 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use anyhow::Context as _; use cargo_util_schemas::manifest::RustVersion; -use cargo_util_schemas::manifest::{TomlManifest, TomlProfiles}; +use cargo_util_schemas::manifest::{Hints, TomlManifest, TomlProfiles}; use semver::Version; use serde::Serialize; use serde::ser; @@ -90,6 +90,7 @@ pub struct Manifest { metabuild: Option>, resolve_behavior: Option, lint_rustflags: Vec, + hints: Option, embedded: bool, } @@ -521,6 +522,7 @@ impl Manifest { metabuild: Option>, resolve_behavior: Option, lint_rustflags: Vec, + hints: Option, embedded: bool, ) -> Manifest { Manifest { @@ -551,6 +553,7 @@ impl Manifest { metabuild, resolve_behavior, lint_rustflags, + hints, embedded, } } @@ -668,6 +671,10 @@ impl Manifest { self.lint_rustflags.as_slice() } + pub fn hints(&self) -> Option<&Hints> { + self.hints.as_ref() + } + pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Manifest { Manifest { summary: self.summary.map_source(to_replace, replace_with), diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 916f02e04eb..b84df9ae707 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use std::time::{Duration, Instant}; use anyhow::Context as _; -use cargo_util_schemas::manifest::RustVersion; +use cargo_util_schemas::manifest::{Hints, RustVersion}; use curl::easy::Easy; use curl::multi::{EasyHandle, Multi}; use lazycell::LazyCell; @@ -95,6 +95,8 @@ pub struct SerializedPackage { metabuild: Option>, default_run: Option, rust_version: Option, + #[serde(skip_serializing_if = "Option::is_none")] + hints: Option, } impl Package { @@ -172,6 +174,11 @@ impl Package { self.manifest().rust_version() } + /// Gets the package's hints. + pub fn hints(&self) -> Option<&Hints> { + self.manifest().hints() + } + /// Returns `true` if the package uses a custom build script for any target. pub fn has_custom_build(&self) -> bool { self.targets().iter().any(|t| t.is_custom_build()) @@ -241,6 +248,7 @@ impl Package { publish: self.publish().as_ref().cloned(), default_run: self.manifest().default_run().map(|s| s.to_owned()), rust_version: self.rust_version().cloned(), + hints: self.hints().cloned(), } } } diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index 69dc9976533..de660380431 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -578,7 +578,7 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { profile.trim_paths = Some(trim_paths.clone()); } if let Some(hint_mostly_unused) = toml.hint_mostly_unused { - profile.hint_mostly_unused = hint_mostly_unused; + profile.hint_mostly_unused = Some(hint_mostly_unused); } profile.strip = match toml.strip { Some(StringOrBool::Bool(true)) => Strip::Resolved(StripInner::Named("symbols".into())), @@ -629,8 +629,8 @@ pub struct Profile { // remove when `-Ztrim-paths` is stablized #[serde(skip_serializing_if = "Option::is_none")] pub trim_paths: Option, - #[serde(skip_serializing_if = "std::ops::Not::not")] - pub hint_mostly_unused: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub hint_mostly_unused: Option, } impl Default for Profile { @@ -652,7 +652,7 @@ impl Default for Profile { strip: Strip::Deferred(StripInner::None), rustflags: vec![], trim_paths: None, - hint_mostly_unused: false, + hint_mostly_unused: None, } } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 3392bcba636..82a282153c3 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -327,6 +327,7 @@ fn normalize_toml( build_dependencies2: None, target: None, lints: None, + hints: None, workspace: original_toml.workspace.clone().or_else(|| { // Prevent looking for a workspace by `read_manifest_from_str` is_embedded.then(manifest::TomlWorkspace::default) @@ -571,6 +572,8 @@ fn normalize_toml( lints, }); + normalized_toml.hints = original_toml.hints.clone(); + normalized_toml.badges = original_toml.badges.clone(); } else { if let Some(field) = original_toml.requires_package().next() { @@ -1628,6 +1631,8 @@ pub fn to_real_manifest( .unwrap_or(&default), )?; + let hints = normalized_toml.hints.clone(); + let metadata = ManifestMetadata { description: normalized_package .normalized_description() @@ -1819,6 +1824,7 @@ pub fn to_real_manifest( metabuild, resolve_behavior, rustflags, + hints, is_embedded, ); if manifest @@ -3080,6 +3086,7 @@ fn prepare_toml_for_publish( None => None, }, lints: me.lints.clone(), + hints: me.hints.clone(), workspace: None, profile: me.profile.clone(), patch: None, diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index 1a69f730384..319615e1309 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -5,7 +5,7 @@ use cargo_test_support::registry::Package; use cargo_test_support::{project, str}; #[cargo_test] -fn empty_hints_warn() { +fn empty_hints_no_warn() { let p = project() .file( "Cargo.toml", @@ -22,7 +22,6 @@ fn empty_hints_warn() { .build(); p.cargo("check -v") .with_stderr_data(str![[r#" -[WARNING] unused manifest key: hints [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s @@ -50,7 +49,7 @@ fn unknown_hints_warn() { .build(); p.cargo("check -v") .with_stderr_data(str![[r#" -[WARNING] unused manifest key: hints +[WARNING] unused manifest key: hints.this-is-an-unknown-hint [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s @@ -92,16 +91,19 @@ fn hint_unknown_type_warn() { .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -v") + .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) -[CHECKING] bar v1.0.0 -[RUNNING] `rustc --crate-name bar [..]` -[CHECKING] foo v0.0.1 ([ROOT]/foo) -[RUNNING] `rustc --crate-name foo [..]` -[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s +[ERROR] invalid type: integer `1`, expected a boolean + --> ../home/.cargo/registry/src/-[HASH]/bar-1.0.0/Cargo.toml:8:29 + | +8 | mostly-unused = 1 + | ^ + | +[ERROR] failed to download replaced source registry `crates-io` "#]]) .with_stderr_does_not_contain("-Zhint-mostly-unused") @@ -146,6 +148,7 @@ fn hints_mostly_unused_warn_without_gate() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[WARNING] ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) @@ -197,7 +200,7 @@ fn hints_mostly_unused_nightly() { [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [CHECKING] bar v1.0.0 -[RUNNING] `rustc --crate-name bar [..]` +[RUNNING] `rustc --crate-name bar [..] -Zhint-mostly-unused [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s @@ -284,7 +287,6 @@ fn mostly_unused_profile_overrides_hints_on_self_nightly() { .build(); p.cargo("check -v") .with_stderr_data(str![[r#" -[WARNING] unused manifest key: hints [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s From 0c5ea24f13b2c000f70b4335fbf93e353317d3dc Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 16 Jun 2025 14:55:10 -0700 Subject: [PATCH 05/11] Add documentation for `hints.mostly-unused` --- src/doc/src/reference/unstable.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 00602d97471..70cbf56c75c 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -963,6 +963,18 @@ introduction of this feature will give an "unused manifest key" warning, but will otherwise function without erroring. This allows using the hint in a crate's `Cargo.toml` without mandating the use of a newer Cargo to build it. +A crate can also provide this hint automatically for crates that depend on it, +using the `[hints]` table (which will likewise be ignored by older Cargo): + +```toml +[hints] +mostly-unused = true +``` + +This will cause the crate to default to hint-mostly-unused, unless overridden +via `profile`, which takes precedence, and which can only be specified in the +top-level crate being built. + ## rustdoc-map * Tracking Issue: [#8296](https://github.com/rust-lang/cargo/issues/8296) From fe86023863ab4fdef29f98280d82c79312df1193 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 17 Jun 2025 23:25:20 -0700 Subject: [PATCH 06/11] Parse hints permissively to allow for future expansion Make it only a warning, not an error, to have a hint value of the wrong type. --- .../cargo-util-schemas/manifest.schema.json | 10 ++++++--- crates/cargo-util-schemas/src/manifest/mod.rs | 6 ++++- src/cargo/core/compiler/mod.rs | 22 +++++++++++++++---- tests/testsuite/hints.rs | 14 +++++------- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/crates/cargo-util-schemas/manifest.schema.json b/crates/cargo-util-schemas/manifest.schema.json index 3b17f326762..ec63f68ee0c 100644 --- a/crates/cargo-util-schemas/manifest.schema.json +++ b/crates/cargo-util-schemas/manifest.schema.json @@ -1031,9 +1031,13 @@ "type": "object", "properties": { "mostly-unused": { - "type": [ - "boolean", - "null" + "anyOf": [ + { + "$ref": "#/$defs/TomlValue" + }, + { + "type": "null" + } ] } } diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index bf42b327578..2aafcb4bc2a 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -1650,7 +1650,11 @@ pub enum TomlLintLevel { #[serde(rename_all = "kebab-case")] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] pub struct Hints { - pub mostly_unused: Option, + #[cfg_attr( + feature = "unstable-schema", + schemars(with = "Option") + )] + pub mostly_unused: Option, } #[derive(Copy, Clone, Debug)] diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 8ae261f2ad4..8915f16f8c1 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1135,7 +1135,7 @@ fn build_base_args( strip, rustflags: profile_rustflags, trim_paths, - hint_mostly_unused, + hint_mostly_unused: profile_hint_mostly_unused, .. } = unit.profile.clone(); let hints = unit.pkg.hints().cloned().unwrap_or_default(); @@ -1327,15 +1327,29 @@ fn build_base_args( opt(cmd, "-C", "incremental=", Some(dir)); } - if hint_mostly_unused.or(hints.mostly_unused).unwrap_or(false) { + let pkg_hint_mostly_unused = match hints.mostly_unused { + None => None, + Some(toml::Value::Boolean(b)) => Some(b), + Some(v) => { + bcx.gctx.shell().warn(format!( + "ignoring unsupported value type ({}) for 'hints.mostly-unused', which expects a boolean", + v.type_str() + ))?; + None + } + }; + if profile_hint_mostly_unused + .or(pkg_hint_mostly_unused) + .unwrap_or(false) + { if bcx.gctx.cli_unstable().profile_hint_mostly_unused { cmd.arg("-Zhint-mostly-unused"); } else { - if hint_mostly_unused.is_some() { + if profile_hint_mostly_unused.is_some() { bcx.gctx .shell() .warn("ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it")?; - } else if hints.mostly_unused.is_some() { + } else if pkg_hint_mostly_unused.is_some() { bcx.gctx .shell() .warn("ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it")?; diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index 319615e1309..6cbfedacb0e 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -91,19 +91,17 @@ fn hint_unknown_type_warn() { .file("src/main.rs", "fn main() {}") .build(); p.cargo("check -v") - .with_status(101) .with_stderr_data(str![[r#" [UPDATING] `dummy-registry` index [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) -[ERROR] invalid type: integer `1`, expected a boolean - --> ../home/.cargo/registry/src/-[HASH]/bar-1.0.0/Cargo.toml:8:29 - | -8 | mostly-unused = 1 - | ^ - | -[ERROR] failed to download replaced source registry `crates-io` +[WARNING] ignoring unsupported value type (integer) for 'hints.mostly-unused', which expects a boolean +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` +[CHECKING] foo v0.0.1 ([ROOT]/foo) +[RUNNING] `rustc --crate-name foo [..]` +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .with_stderr_does_not_contain("-Zhint-mostly-unused") From 28b9762466d1d40a2e68fd690e4cc66483e623ba Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 17 Jun 2025 23:43:25 -0700 Subject: [PATCH 07/11] Add stable documentation for the `[hints]` table --- src/doc/src/reference/manifest.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index 3d0a6f1b1ba..7c210f5b4cb 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -50,6 +50,7 @@ Every manifest file consists of the following sections: * [`[badges]`](#the-badges-section) --- Badges to display on a registry. * [`[features]`](features.md) --- Conditional compilation features. * [`[lints]`](#the-lints-section) --- Configure linters for this package. +* [`[hints]`](#the-hints-section) --- Provide hints for compiling this package. * [`[patch]`](overriding-dependencies.md#the-patch-section) --- Override dependencies. * [`[replace]`](overriding-dependencies.md#the-replace-section) --- Override dependencies (deprecated). * [`[profile]`](profiles.md) --- Compiler settings and optimizations. @@ -565,6 +566,26 @@ As for dependents, Cargo suppresses lints from non-path dependencies with featur > **MSRV:** Respected as of 1.74 +## The `[hints]` section + +The `[hints]` section allows specifying hints for compiling this package. Cargo +will respect these hints by default when compiling this package, though the +top-level package being built can override these values through the `[profile]` +mechanism. Hints are, by design, always safe for Cargo to ignore; if Cargo +encounters a hint it doesn't understand, or a hint it understands but with a +value it doesn't understand, it will warn, but not error. As a result, +specifying hints in a crate does not impact the MSRV of the crate. + +Individual hints may have an associated unstable feature gate that you need to +pass in order to apply the configuration they specify, but if you don't specify +that unstable feature gate, you will again get only a warning, not an error. + +There are no stable hints at this time. See the [hint-mostly-unused +documentation](unstable.md#profile-hint-mostly-unused-option) for information +on an unstable hint. + +> **MSRV:** Respected as of 1.90. + ## The `[badges]` section The `[badges]` section is for specifying status badges that can be displayed From 9949fa8a6645e03d646e014b2b67acc26668e1ad Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 21 Jun 2025 16:17:00 -0700 Subject: [PATCH 08/11] Include package ID in warnings to identify affected crate --- src/cargo/core/compiler/mod.rs | 22 +++++++++++++++------- tests/testsuite/hints.rs | 4 ++-- tests/testsuite/profiles.rs | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 8915f16f8c1..085b1541b7a 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1141,6 +1141,14 @@ fn build_base_args( let hints = unit.pkg.hints().cloned().unwrap_or_default(); let test = unit.mode.is_any_test(); + let warn = |msg: &str| { + bcx.gctx.shell().warn(format!( + "{}@{}: {msg}", + unit.pkg.package_id().name(), + unit.pkg.package_id().version() + )) + }; + cmd.arg("--crate-name").arg(&unit.target.crate_name()); let edition = unit.target.edition(); @@ -1331,7 +1339,7 @@ fn build_base_args( None => None, Some(toml::Value::Boolean(b)) => Some(b), Some(v) => { - bcx.gctx.shell().warn(format!( + warn(&format!( "ignoring unsupported value type ({}) for 'hints.mostly-unused', which expects a boolean", v.type_str() ))?; @@ -1346,13 +1354,13 @@ fn build_base_args( cmd.arg("-Zhint-mostly-unused"); } else { if profile_hint_mostly_unused.is_some() { - bcx.gctx - .shell() - .warn("ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it")?; + warn( + "ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it", + )?; } else if pkg_hint_mostly_unused.is_some() { - bcx.gctx - .shell() - .warn("ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it")?; + warn( + "ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it", + )?; } } } diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index 6cbfedacb0e..defe314b7a7 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -96,7 +96,7 @@ fn hint_unknown_type_warn() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) -[WARNING] ignoring unsupported value type (integer) for 'hints.mostly-unused', which expects a boolean +[WARNING] bar@1.0.0: ignoring unsupported value type (integer) for 'hints.mostly-unused', which expects a boolean [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) @@ -146,7 +146,7 @@ fn hints_mostly_unused_warn_without_gate() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) -[WARNING] ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it +[WARNING] bar@1.0.0: ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) diff --git a/tests/testsuite/profiles.rs b/tests/testsuite/profiles.rs index 3dd4dc5227f..61cc8d2187a 100644 --- a/tests/testsuite/profiles.rs +++ b/tests/testsuite/profiles.rs @@ -904,7 +904,7 @@ fn profile_hint_mostly_unused_warn_without_gate() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) -[WARNING] ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it +[WARNING] bar@1.0.0: ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) From 78b4afb4f390fc4dbb9fe20042963ce190d728a5 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 12 Jul 2025 12:03:31 -0700 Subject: [PATCH 09/11] Expand test to produce warnings for deps, which will subsequently get capped --- tests/testsuite/hints.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index defe314b7a7..04b912a035b 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -32,6 +32,21 @@ fn empty_hints_no_warn() { #[cargo_test] fn unknown_hints_warn() { + Package::new("bar", "1.0.0") + .file( + "Cargo.toml", + r#" + [package] + name = "bar" + version = "1.0.0" + edition = "2015" + + [hints] + this-is-an-unknown-hint = true + "#, + ) + .file("src/lib.rs", "") + .publish(); let p = project() .file( "Cargo.toml", @@ -41,6 +56,9 @@ fn unknown_hints_warn() { version = "0.0.1" edition = "2015" + [dependencies] + bar = "1.0" + [hints] this-is-an-unknown-hint = true "#, @@ -50,6 +68,12 @@ fn unknown_hints_warn() { p.cargo("check -v") .with_stderr_data(str![[r#" [WARNING] unused manifest key: hints.this-is-an-unknown-hint +[UPDATING] `dummy-registry` index +[LOCKING] 1 package to latest compatible version +[DOWNLOADING] crates ... +[DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[CHECKING] bar v1.0.0 +[RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) [RUNNING] `rustc --crate-name foo [..]` [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s @@ -86,6 +110,9 @@ fn hint_unknown_type_warn() { [dependencies] bar = "1.0" + + [hints] + mostly-unused = "string" "#, ) .file("src/main.rs", "fn main() {}") @@ -96,6 +123,7 @@ fn hint_unknown_type_warn() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[WARNING] foo@0.0.1: ignoring unsupported value type (string) for 'hints.mostly-unused', which expects a boolean [WARNING] bar@1.0.0: ignoring unsupported value type (integer) for 'hints.mostly-unused', which expects a boolean [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` @@ -136,6 +164,9 @@ fn hints_mostly_unused_warn_without_gate() { [dependencies] bar = "1.0" + + [hints] + mostly-unused = true "#, ) .file("src/main.rs", "fn main() {}") @@ -146,6 +177,7 @@ fn hints_mostly_unused_warn_without_gate() { [LOCKING] 1 package to latest compatible version [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) +[WARNING] foo@0.0.1: ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it [WARNING] bar@1.0.0: ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` From e44bde554d0c6be5d333f8ccdfc250b9592538e9 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 12 Jul 2025 12:37:53 -0700 Subject: [PATCH 10/11] Respect capped lints for dependencies --- src/cargo/core/compiler/mod.rs | 12 ++++++++++-- tests/testsuite/hints.rs | 2 -- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 085b1541b7a..fa8239edfba 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1148,6 +1148,13 @@ fn build_base_args( unit.pkg.package_id().version() )) }; + let unit_capped_warn = |msg: &str| { + if unit.show_warnings(bcx.gctx) { + warn(msg) + } else { + Ok(()) + } + }; cmd.arg("--crate-name").arg(&unit.target.crate_name()); @@ -1339,7 +1346,7 @@ fn build_base_args( None => None, Some(toml::Value::Boolean(b)) => Some(b), Some(v) => { - warn(&format!( + unit_capped_warn(&format!( "ignoring unsupported value type ({}) for 'hints.mostly-unused', which expects a boolean", v.type_str() ))?; @@ -1354,11 +1361,12 @@ fn build_base_args( cmd.arg("-Zhint-mostly-unused"); } else { if profile_hint_mostly_unused.is_some() { + // Profiles come from the top-level unit, so we don't use `unit_capped_warn` here. warn( "ignoring 'hint-mostly-unused' profile option, pass `-Zprofile-hint-mostly-unused` to enable it", )?; } else if pkg_hint_mostly_unused.is_some() { - warn( + unit_capped_warn( "ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it", )?; } diff --git a/tests/testsuite/hints.rs b/tests/testsuite/hints.rs index 04b912a035b..ef4d84a14bf 100644 --- a/tests/testsuite/hints.rs +++ b/tests/testsuite/hints.rs @@ -124,7 +124,6 @@ fn hint_unknown_type_warn() { [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [WARNING] foo@0.0.1: ignoring unsupported value type (string) for 'hints.mostly-unused', which expects a boolean -[WARNING] bar@1.0.0: ignoring unsupported value type (integer) for 'hints.mostly-unused', which expects a boolean [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) @@ -178,7 +177,6 @@ fn hints_mostly_unused_warn_without_gate() { [DOWNLOADING] crates ... [DOWNLOADED] bar v1.0.0 (registry `dummy-registry`) [WARNING] foo@0.0.1: ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it -[WARNING] bar@1.0.0: ignoring 'hints.mostly-unused', pass `-Zprofile-hint-mostly-unused` to enable it [CHECKING] bar v1.0.0 [RUNNING] `rustc --crate-name bar [..]` [CHECKING] foo v0.0.1 ([ROOT]/foo) From 94c27dfaea6f6c995ad455bd69943446a6433703 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sat, 12 Jul 2025 12:47:05 -0700 Subject: [PATCH 11/11] cargo-util-schemas: Bump breaking version for schema change --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/cargo-util-schemas/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62e3e4182ea..2eb4f2610b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,7 +315,7 @@ dependencies = [ "cargo-platform 0.3.1", "cargo-test-support", "cargo-util", - "cargo-util-schemas 0.9.1", + "cargo-util-schemas 0.10.0", "clap", "clap_complete", "color-print", @@ -517,7 +517,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.9.1" +version = "0.10.0" dependencies = [ "schemars", "semver", @@ -3373,7 +3373,7 @@ dependencies = [ "cargo", "cargo-platform 0.3.1", "cargo-util", - "cargo-util-schemas 0.9.1", + "cargo-util-schemas 0.10.0", "proptest", "varisat", ] diff --git a/Cargo.toml b/Cargo.toml index ba81b29b68c..7e849f18efc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.0" } cargo-test-macro = { version = "0.4.4", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.8.0", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.22", path = "crates/cargo-util" } -cargo-util-schemas = { version = "0.9.0", path = "crates/cargo-util-schemas" } +cargo-util-schemas = { version = "0.10.0", path = "crates/cargo-util-schemas" } cargo_metadata = "0.20.0" clap = "4.5.40" clap_complete = { version = "4.5.54", features = ["unstable-dynamic"] } diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 4159e33c280..2682b65e1de 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util-schemas" -version = "0.9.1" +version = "0.10.0" rust-version = "1.88" # MSRV:1 edition.workspace = true license.workspace = true