rust-lang / cargo

The Rust package manager
https://doc.rust-lang.org/cargo
Apache License 2.0
12.81k stars 2.43k forks source link

Bad error message when a strong dep features's depedency is an unused optional dependency #14016

Open epage opened 5 months ago

epage commented 5 months ago

Problem

If nothing can activate an optional dependency, cargo acts as if the dependency doesn't exist which creates poor error messages.

Also, with how things are arranged, the unused optional dependency lint doesn't get reported which could at least reduce the burden on the error message.

Steps

Baseline Cargo.toml:

cargo-features = ["edition2024"]
[package]
name = "cargo-14010"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1.0.203", optional = true }
$ cargo +nightly check -Zcargo-lints
warning: unused optional dependency
 --> Cargo.toml:8:1
  |
8 | serde = { version = "1.0.203", optional = true }
  | -----
  |
  = note: `cargo::unused_optional_dependency` is set to `warn` by default
  = help: remove the dependency or activate it in a feature with `dep:serde`
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s

Add the following to Cargo.toml:


[features]
serde = ["serde/derive"]
$ cargo +nightly check -Zcargo-lints
error: failed to parse manifest at `/home/epage/src/personal/dump/cargo-14010-1/Cargo.toml`

Caused by:
  feature `serde` includes `serde/derive`, but `serde` is not a dependency

Possible Solution(s)

Notes

This was split out of #14015 assuming we'll go with a different solution than it

Version

No response

epage commented 5 months ago

https://github.com/rust-lang/cargo/issues/14010#issuecomment-2148408439

This makes me worried about dep_name/feature_name syntax on Edition 2024. It now requires also adding dep:dep_name at which point it isn't much different than dep_name?/feature_name.

I had missing this part in past discussions (e.g. this zulip topic). It seems like we should have either changed it in Edition 2024 to not need dep:dep_name or transition out the syntax according to #10556. I worry that changing the meaning of dep_name/feature_name would be too subtle. Maybe we should at least go forward with the deprecation of dep_name/feature_name as mentioned in the comments of #10556?

epage commented 5 months ago

https://github.com/rust-lang/cargo/issues/14010#issuecomment-2148415751

Also, another reason why deprecating implicit features doesn't play well with dep_name/feature_name is that

[features]
foo = ["dep:dep_name"]
bar = ["dep_name/feature_name"]

and

[features]
bar = ["dep:dep_name", "dep_name/feature_name"]

are valid while the following is not

[features]
bar = ["dep_name/feature_name"]

That inconsistency feels weird.

epage commented 5 months ago

We discussed this in today's team meeting and lean towards "dep_name/feature_name" implying "dep:dep_name" in Edition 2024 with it being normalized in the published Cargo.toml file.

epage commented 3 months ago

Re-opened due to #14295. See #14283 for more context.

epage commented 1 month ago

When we tried to fix this with #14221, we caused #14283.

The following content is adapted from #14283 and https://blog.rust-lang.org/inside-rust/2024/08/15/this-development-cycle-in-cargo-1.81.html#removing-implicit-features

Let's step back to the beginning of how all of this works.

package.edition = "2021"
[features]
feature = ["dep/feature"]
[dependencies]
dep = { version = "1", optional = true }

will cause a dep implicit feature to be created

As we want to suppress implicit features in 2024, we strip all optional dependencies like that. This led to a new problem:

package.edition = "2024"
[features]
feature = ["dep/feature"]
[dependencies]
dep = { version = "1", optional = true }

As dep is stripped, this fails because dep/feature can't be resolved. The user can workaround this by adding dep:dep (discoverability is poor).

We solved this by implicitly adding dep:dep to feature. However, I forgot to check dependencies.dep.optional, so if it isn't optional, it also fails but in a way the user can't workaround.

Logically, checking dependencies.dep.optional gets circular because

We could break this cycle by making a lot of assumptions about the unresolved forms.

If we solved this now, we would be stuck with it forever. In examining the potential alternatives, it seemed better to design a more holistic solution rather than being stuck with the maintenance burden that solving this now would cause.

Other options include

Whatever we design, we need to keep in mind that "dep_name/feature_name" is more like "dep_name"?, "dep:dep_name"?, "dep_name?/feature_name"