rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
96.2k stars 12.44k forks source link

New rustc nightly suggests adding a `build.rs` to use conditional compilation #124800

Closed TheBlueMatt closed 3 months ago

TheBlueMatt commented 3 months ago

We use conditional compilation in a lot of places (eg the fuzzing cfg flag is standard to conditionally compile when fuzzing in the rust-fuzz ecosystem) and have a hard rule against unnecessary build.rss (as running build-time code is a red flag when auditing a crate and requires special care). Latest rustc nightly now generates a huge pile of warnings encouraging us to add a build.rs to make them go away, which isn't really acceptable. It seems this is encouraging bad practice to respond to a common practice - is there some way to just list fuzzing and some other super common flags and allow those?

saethlin commented 3 months ago

Yes there is a list of well-known cfgs, for example https://github.com/rust-lang/rust/pull/124742. fuzzing would be a good addition. We did already try to add the super common ones, so you'll have to be specific about what others would be helpful.

TheBlueMatt commented 3 months ago

Fuzzing is the biggest ecosystem-wide one, but we also use cfg flags to land code that isn't complete yet and we don't want to enable but which requires a long series of steps and we don't want to try to land it all in one go, so we need the ability to add our own in Cargo.toml. I can't imagine this is a super uncommon use?

apoelstra commented 3 months ago

I fairly frequently use cfg flags to disable key parts of my crates (mainly: heavy cryptography) for the purpose of speeding up a fuzzer or allowing it to forge signatures so it can hit more paths. The names of these are deliberately uncommon and project-specific.

So I would also like a way to whitelist cfg flags. Cargo.toml would be great.

For now I am disabling the lint entirely with allow but I would like to have the lint on, since I've been burned by cfg typos many times in the past. But adding a build.rs will slow down compilation and raise eyebrows of anybody reviewing my crates.

Urgau commented 3 months ago

have a hard rule against unnecessary build.rs

It wouldn't be unnecessary, it would be used.

It seems this is encouraging bad practice

Adding a build.rs is not a bad practice.

fuzzing would be a good addition

The rule of thumb that was agreed in the stabilization of --check-cfg was to only include a restricted set of cfgs and only the least common for all users of Rust, including ones that do not use Cargo, like Rust-for-Linux.

is there some way to just list fuzzing

Yes in a build.rs with cargo::rustc-check-cfg=cfg(fuzzing). Or using a Cargo feature instead.

TheBlueMatt commented 3 months ago

Sorry, but "add additional code that runs at build-time that users have to audit separately just to disable a compile-time warning" is really offensive stance. Indeed, some folks have legitimate uses for build.rs, and within parts of the Rust ecosystem build.rs is a totally accepted practice, but that definitely isn't universal and for those building security-conscious software build.rs is an orange flag that requires extra auditing and care, and is absolutely not something that is universally acceptable without consideration.

More broadly, I also work on some projects that are optionally being built directly with rustc rather than cargo. While those builds won't suffer from this issue (AFAIU?), having a build.rs is obviously pretty painful in those cases.

epage commented 3 months ago

Fuzzing is the biggest ecosystem-wide one, but we also use cfg flags to land code that isn't complete yet and we don't want to enable but which requires a long series of steps and we don't want to try to land it all in one go, so we need the ability to add our own in Cargo.toml. I can't imagine this is a super uncommon use?

imo this sounds like a case for features. If you are concerned about others using it, there is a practice for unstable- prefixes and a proposal for official unstable feature support.

So I would also like a way to whitelist cfg flags. Cargo.toml would be great.

The original RFC proposed a [cfg] syntax but the cargo team rejected it as --cfgs are a low level rustc abstraction that don't fit within cargo (iiuc).

I've created the following Pre-RFC that would create something for Cargo but don't have the time to drive it to completion and no one else has picked it up

https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618

TheBlueMatt commented 3 months ago

imo this sounds like a case for features. If you are concerned about others using it, there is a practice for unstable- prefixes and a proposal for official unstable feature support.

There's a few issues with this - first of all just calling something "unstable-" doesn't mean no one can enable it, and it means a transitive dependency somewhere having a bug will mean my crate will suddenly generate completely bogus results. More generally, this isn't an "unstable" feature but rather a "you should never, ever, ever, ever enable this feature unless you're fuzzing, don't touch this".

However, more broadly, we rely on transitive fuzzing features in some cases, and the standard, ecosystem-wide cfg=fuzzing is really nice for that - I don't have to think about which transitive dependencies I have that have fuzzing-specific optimizations, and those transitive dependencies don't have to worry about exposing any kind of public API for fuzzing code enabling - the fuzzers just automatically set cfg=fuzzing and we're off to the races.

The original RFC proposed a [cfg] syntax but the cargo team rejected it as --cfgs are a low level rustc abstraction that don't fit within cargo (iiuc).

Then maybe the warnings should be off by default until we make progress here? Or, at a minimum, the suggested fix should be to turn off the warnings, rather than add a build.rs to all our crates.

--cfgs are a low level rustc abstraction that don't fit within cargo (iiuc).

Yep, that's exactly why we use them - they're "transparent" to cargo which makes them incredibly useful for things like fuzzing and in-progress code. Suddenly throwing tons of warnings at users for what seems like a perfectly reasonable use of a language feature seems like quite a regression (but of course I understand the utility of these warnings - we have existing python scripts that parse our code and detect undefined cfg flags :) ).

saethlin commented 3 months ago

imo this sounds like a case for features

Not the fuzzing component. cfgs like miri, fuzzing, and loom are used for ecosystem-wide coordination. They exist to be well-known. https://github.com/rust-lang/rust/pull/124804

apoelstra commented 3 months ago

The original RFC proposed a [cfg] syntax but the cargo team rejected it as --cfgs are a low level rustc abstraction that don't fit within cargo (iiuc).

If this is true then why is Cargo giving flags to rustc that complain about them?

As Matt is saying, (one) purpose of using cfg flags is to bypass Cargo and its opinions.

workingjubilee commented 3 months ago

I refuse to believe that T-compiler, no matter what was proposed or read, really intended for dropping large swathes of needless lints on crates.io. The implications of this seem like an accident, much like #121708 was an accident.

It is my opinion that the lint is actually quite useful when it detects things like this:

#[cfg(target_os = "widnosw")]

or things like this:

#[cfg(linux)]

It is not impossible someone would want to use #[cfg(linux)], but that's a known value of target_os, so it is more likely you meant that. So it seems it would be acceptable to fire the lint on cases where rustc thinks you meant something else, but is wrong.

But I don't think "this only bothers maintainers"... since when are those people with lots of time on their hands and happy to spend it on squashing trivial lints?... justifies the current implementation. It is particularly sad to me because I noticed that the large swathes of false positives almost completely drown out cases where a more cautious implementation would catch something. I only caught the snippets of signal it actually sent to backtrace-rs because I paid very close attention... and I still ended up only throwing in those two actual fixes and simply adding this to the Cargo.toml:

[lints]
unexpected_cfgs = "allow"

As to whatever ideas the Cargo team has regarding future work done in this area: those sound good! I think I've expressed tentative support for one of them, the global features thing, and it cuts fairly close to Job Reasons, so I might even be able to start working on it out of paid time. But they also seem like a good reason for the lint to be more restrained than firing according to its current brutalistically simple logic, since the preferred implementation depends on work that hasn't been done yet.

workingjubilee commented 3 months ago

Also, I should note, that it was always my impression that this was primarily intended to catch this, or I might have said something sooner:

#[cfg(target_os = "widnosw")]

This is an expansive set of linting decisions that have all been landed as one blob. Deciding to lint on #[cfg(target_os = "widnosw")] is different than deciding to lint on #[cfg(linux)], which is different from deciding to lint on #[cfg(ynix)], which is different than deciding to lint on #[cfg(fuzzing)]. The last is the one we have the least information about unless we bake it into the compiler (and then we re-raise the question of other one-off cfgs).

epage commented 3 months ago

I can understand being surprised by this and then having to clean it up out of cycle from your other work can be frustrating.

In seeing these cases though, I wonder how much we need to keep in mind how representative our own experience might be for others. This feature is only on nightly so far which includes a certain level of sampling bias for those who are regularly running nightly in a way to notice this. Looking over the crater run results

How much overlap these have is unclear.

iiuc the rest are either single digit project counts, look like bugs, or were marked as well known.

My own personal anecdote for this is that I ran this on a good sampling of the packages I help maintain when the Call for Testing was made and only had bugs left after getting docsrs accepted as a well-known cfg. Since it got stabilized on nightly, I've only had 1 warning so far which admittedly was a false positive (I think this was missed during the sampling because I forgot to pass --workspace ). Of course, the sampling bias here is my involvement encourages a homogenization across them.

I refuse to believe that T-compiler, no matter what was proposed or read, really intended for dropping large swathes of needless lints on crates.io.

The stabilization report was included the crater run analysis. Now, internalizing those numbers and weighing the impact isn't always straightforward.

This is also a weird one because T-compiler was approving a generic mechanism that is controlled by the caller. In this case, Cargo is the main caller and T-cargo approved enabling it for everyone unconditionally.

epage commented 3 months ago

Speaking of Call for Testing, @workingjubilee as someone who maintains a project with a lot of cfgs, is there a reason you didn't participate? I wonder if there is some way we could change how Call for Testing is done to improve engagement. This is timely as I need to write up the Call for Testing for MSRV-aware resolver.

Some other potential elements in this thread that might be worth exploring include

workingjubilee commented 3 months ago

uhh the call for testing? I absolutely did not hear about them, or didn't notice it.

yes I realize I may seem (or actually am??) Very Plugged In, what with following links like a hyperactive animated sprocket, I still did not hear about it.

TheBlueMatt commented 3 months ago

In seeing these cases though, I wonder how much we need to keep in mind how representative our own experience might be for others.

Irrespective of how common this is, I'm not sure its sensible. In the above discussion it was highlighted that --cfg without features is useful for the precise purpose of being transparent to cargo, replacing it either with a feature or a build.rs isn't really a fix given this being the goal, but listing out the allowed cfgs either in Cargo.toml or, ideally, source allow this language feature to keep working as it exists today rather than blocking it off from future users.

epage commented 3 months ago

This is an example of a Call for Testing. It used to be lower in the template but got moved up at Urgau's request out of concern that the first round of Call for Testing was too buried, so it was moved and we did a second Call for Testing.

workingjubilee commented 3 months ago

I don't really read TWiR because I don't need more interesting Rust projects to nerdsnipe myself with.

saethlin commented 3 months ago

Looking over the crater run results

Ah I wish I had looked at this at the time. The crater run analysis makes the classic mistake of only looking at regressions. Fortunately in this case it doesn't matter that much.

iiuc the rest are either single digit project counts, look like bugs, or were marked as well known.

This analysis you're doing here does not follow from the way the crater run data was summarized. The lists from that Python script are truncated to the top 50, but by the number of errors, not by affected projects.

My much-less-pretty analysis is this:

rg "unexpected \`cfg\` condition name:" --no-line-number | sort | uniq | rev | cut -d' ' -f1 | rev | counts

The list of cfgs with at least 10 affected crates is this:

(  1)     1999 (29.5%, 29.5%): `docsrs`
(  2)      820 (12.1%, 41.5%): `rustfmt`
(  3)      212 ( 3.1%, 44.7%): `tarpaulin_include`
(  4)      157 ( 2.3%, 47.0%): `nightly`
(  5)      130 ( 1.9%, 48.9%): `doc_cfg`
(  6)      104 ( 1.5%, 50.4%): `fuzzing`
(  7)       97 ( 1.4%, 51.8%): `tarpaulin`
(  8)       78 ( 1.1%, 53.0%): `loom`
(  9)       74 ( 1.1%, 54.1%): `features`
( 10)       54 ( 0.8%, 54.9%): `debug`
( 11)       54 ( 0.8%, 55.7%): `tests`
( 12)       50 ( 0.7%, 56.4%): `ci_arti_nightly`
( 13)       49 ( 0.7%, 57.1%): `ci_arti_stable`
( 14)       37 ( 0.5%, 57.7%): `RUSTC_WITH_SPECIALIZATION`
( 15)       36 ( 0.5%, 58.2%): `CHANNEL_NIGHTLY`
( 16)       31 ( 0.5%, 58.7%): `coverage_nightly`
( 17)       31 ( 0.5%, 59.1%): `docs_rs`
( 18)       30 ( 0.4%, 59.6%): `linux`
( 19)       28 ( 0.4%, 60.0%): `macos`
( 20)       26 ( 0.4%, 60.4%): `target`
( 21)       24 ( 0.4%, 60.7%): `bench`
( 22)       24 ( 0.4%, 61.1%): `kani`
( 23)       24 ( 0.4%, 61.4%): `std`
( 24)       23 ( 0.3%, 61.8%): `test_utilities`
( 25)       19 ( 0.3%, 62.0%): `coverage`
( 26)       18 ( 0.3%, 62.3%): `no_global_oom_handling`
( 27)       17 ( 0.3%, 62.6%): `docs`
( 28)       17 ( 0.3%, 62.8%): `exhaustive`
( 29)       17 ( 0.3%, 63.1%): `has_not_matches`
( 30)       16 ( 0.2%, 63.3%): `documenting`
( 31)       16 ( 0.2%, 63.5%): `never`
( 32)       15 ( 0.2%, 63.8%): `tokio_unstable`
( 33)       14 ( 0.2%, 64.0%): `RUSTC_NEEDS_PROC_MACRO_HYGIENE`
( 34)       14 ( 0.2%, 64.2%): `has_i128`
( 35)       14 ( 0.2%, 64.4%): `icu4x_custom_data`
( 36)       14 ( 0.2%, 64.6%): `procmacro2_semver_exempt`
( 37)       12 ( 0.2%, 64.8%): `release`
( 38)       12 ( 0.2%, 64.9%): `releasing`
( 39)       11 ( 0.2%, 65.1%): `slow_assertions`
( 40)       11 ( 0.2%, 65.3%): `wasm`
( 41)       10 ( 0.1%, 65.4%): `can_vector`
( 42)       10 ( 0.1%, 65.6%): `clippy`
( 43)       10 ( 0.1%, 65.7%): `write_all_vectored`
ChrisDenton commented 3 months ago

Just to centralize discussion, this was also an issue for tokio. hyper and windows-rs. A workaround has been suggested by dtolnay.

epage commented 3 months ago

My much-less-pretty analysis is this:

Thanks for doing that! Yes, I had forgotten to look closely at the constraints of the data I was looking at.

epage commented 3 months ago

@workingjubilee

I don't really read TWiR because I don't need more interesting Rust projects to nerdsnipe myself with.

Are there other ways we could be communicating out for Call for Testings that would help?

workingjubilee commented 3 months ago

Well... so, for the TWiR posting, when you mentioned it, I did check it out. But the first time I interacted with the process "Try to find the testing steps" I actually somehow clicked on the link that goes to the RFC. That was the first unnecessary opportunity for me to fail to find the testing steps. The content of the RFC is mostly meaningless to me because it was heavily revised in its journey to implementation.

Even if I could have easily found it, it might, in general, have been best if the "call for testing" test steps had not been a stray comment near the end of a long RFC thread. The standard of discourse tolerated in RFC threads is... low, even by my standards. So I find myself increasing averse to even clicking on them.

I also think the issue I have with the places the testing steps was mentioned is the same problem. It's... indirection. Yeah, programmers are comfy with a lot of indirection, usually, but a human has to deref it in this case, not a compiler, so you can expect a high dropoff for every step. 50% would be optimistic. 80%? 90%?

So my first recommendation would be to make it as easy as possible to see the exact testing steps. They should probably get their own page, or at least be at the top of one. For example, their own GitHub issue or an Inside Rust blog post? That would be appropriate.

tbu- commented 3 months ago

Perhaps simple flags could be whitelisted in Cargo.toml instead of having to write a build.rs? A build.rs is a pretty heavy hammer that slows down build times.

epage commented 3 months ago

@tbu- that was covered earlier in https://github.com/rust-lang/rust/issues/124800#issuecomment-2096258595

As mentioned, a way to negate the cost of build scripts is to exclude the build.rs from packaging. It affects you build times (which should be minor) but doesn't contribute to build times to large graphs of dependencies.

See https://github.com/tokio-rs/tokio/pull/6542

tbu- commented 3 months ago

The original RFC proposed a [cfg] syntax but the cargo team rejected it as --cfgs are a low level rustc abstraction that don't fit within cargo (iiuc).

Do you have a link to that discussion?

TheBlueMatt commented 3 months ago

As mentioned, a way to negate the cost of build scripts is to exclude the build.rs from packaging

This wasn't mentioned anywhere in discussion (AFAICT?), but rather only done in Tokio. At a minimum this should probably be suggested as a part of the rustc help output for this error rather than suggesting every crate ship a build.rs, no?

tbu- commented 3 months ago

As mentioned, a way to negate the cost of build scripts is to exclude the build.rs from packaging. It affects you build times (which should be minor) but doesn't contribute to build times to large graphs of dependencies.

See https://github.com/tokio-rs/tokio/pull/6542

Having a different build on crates.io than what developers of the library see is unattractive: https://github.com/tokio-rs/tokio/pull/6542#issuecomment-2097816273

Generally I'm not a super big fan of this kind of thing. We would no longer be testing the same code as what someone who downloads Tokio from crates.io would get.


Perhaps this lint could be on the "allow" level by default until there is a better way of handling the false positives.

Nemo157 commented 3 months ago

Another approach to avoid a build.rs and only apply in development is to put the cfgs in a .cargo/config.toml:

host.rustflags = ["--check-cfg=cfg(loom)"]
target.'cfg(all())'.rustflags = ["--check-cfg=cfg(loom)"]
TheBlueMatt commented 3 months ago

Another approach to avoid a build.rs and only apply in development is to put the cfgs in a .cargo/config.toml

This means that anyone who tries to contribute to a project has to edit their local cargo config to avoid huge piles of warnings, which also isn't really an acceptable solution. It looks like tokio went with something similar, adding #![allow(unknown_lints, unexpected_cfgs)] to most of their source and then overriding it with RUSTFLAGS in CI so that this only gets hit in CI and doesn't warn contributors locally :(.

dtolnay commented 3 months ago

Having a different build on crates.io than what developers of the library see is unattractive

This can be mitigated by running cargo package in CI, which builds the thing that users will get (with package.include and package.exclude applied, i.e. no build script).

Nemo157 commented 3 months ago

This means that anyone who tries to contribute to a project has to edit their local cargo config to avoid huge piles of warnings

I meant you can commit this into the repository as a shared config (there are still issues around how to actually work with shared/personal .cargo/config.toml, but that has potentially some solution with the work on config-imports, or my approach is to use env-vars for all personal config).

epage commented 3 months ago

So we have two ways of controlling the user-expected cfgs now

We have a long term proposal that needs a lot of design work.

Where we can go from here (or a combination of these)

As for alternatives, the Cargo team had discussed having a [cfg] table but was rejected in https://github.com/rust-lang/cargo/pull/11631#issuecomment-1487424886

There are likely more but the important question is how do the cargo and compiler teams work through this to come to a decision?

Jules-Bertholet commented 3 months ago

Not the fuzzing component. cfgs like miri, fuzzing, and loom are used for ecosystem-wide coordination. They exist to be well-known. #124804

Is the expectation that every new loom-style tool will need to make a PR to the compiler to get its cfg blessed? cargo-careful for example. This clearly doesn't scale…

Urgau commented 3 months ago

Is the expectation that every new loom-style tool will need to make a PR to the compiler to get its cfg blessed? cargo-careful for example. This clearly doesn't scale…

No, the expectation is that users who want to use a custom cfg, which is what loom or fuzzing are, must declare that they want to use it. The canonical way to do this is via cargo::rustc-check-cfg in a build script.

Jules-Bertholet commented 3 months ago

Build scripts bloat compile times, why should that be necessary for what can be a simple declarative attribute? Why not add an entry to Cargo.toml for this?

epage commented 3 months ago

@Jules-Bertholet that is covered earlier in the thread, see https://github.com/rust-lang/rust/issues/124800#issuecomment-2096258595

tbu- commented 3 months ago

Build scripts bloat compile times, why should that be necessary for what can be a simple declarative attribute? Why not add an entry to Cargo.toml for this?

@Jules-Bertholet that is covered earlier in the thread, see https://github.com/rust-lang/rust/issues/124800#issuecomment-2096258595

Note that this only says that the Cargo team preferred not to add this to Cargo.toml, not that this is unfit for a declarative attribute. I think it looks like something that is very much doable as a declarative attribute, especially since the list of possible cfg attributes/values does not change with the build environment, but remains constant.

epage commented 3 months ago

Ah, I misunderstood and thought they were talking loosely about.

For myself, I feel like we should exercise caution in committing to a new feature that we have to maintain compatibility on that does not align with long term objectives or principles.

wesleywiser commented 3 months ago

There are likely more but the important question is how do the cargo and compiler teams work through this to come to a decision?

Thanks for stating that so succinctly @epage! I think it's worthwhile to separate the lint itself from the policy that it implements.

The lint itself is not enabled by default, the user or build system must pass at least one --check-cfg argument to rustc for it to be enabled and the default level of the lint is a warning. I think these are reasonable defaults on both counts. As such, I don't believe a change to the compiler's current behavior or reverting the stabilization of the rustc feature is desirable. If I've missed discussion that points to the contrary, please let me know!

As for the policy the lint implements, that ultimately is up to the build system driving rustc, in this case Cargo. I personally would strongly advocate for this approach from @epage's list:

Limit the scope by allowing all configs, only checking values until an unspecified point

I think this strikes a good balance between providing clear value to users and not placing extra burden on crate maintainers. In particular, I think the behavior should be that if no cargo::rustc-check-cfg directives have been emitted by the crate, Cargo should pass --check-cfg='cfg(any())' --check-cfg='cfg(feature, values({features defined in Cargo.toml}))'. This will enable rustc to catch typos in feature names as well as the built-in configuration options like target_os. It will not enable checking of any other configuration options so things like #[cfg(loom)] will not generate a warning (but neither will #[cfg(limux)]).

If a crate does opt-in to additional checking by providing one or more cargo::rustc-check-cfg directives, then Cargo should only pass --check-cfg='cfg(feature, values({features defined in Cargo.toml})) as well as the provided directives. I particularly like this approach because it first allows us to demonstrate the utility of the feature by catching common issues without false positives and if the user wants additional checking they can opt-in by using the same mechanism that they need to define the valid set of configuration options. (Further, they can use the trick mentioned earlier in the thread to have a build.rs that enables the extra checks which is not distributed when the crate is published so there is no compile time impact to downstream crates.)

I don't think taking a small step now like the one above rules out the possibility of expanding this in the future if/when Cargo global features or another appropriate mechanism becomes available. Landing a lint that targets a specific set of issues and then gradually expanding that over time is very common for compiler lints.

epage commented 3 months ago

@wesleywiser that is an interesting idea, thank you for sharing it.

The challenge with this is trying to grasp

And figuring out how to balance those.

In packages that make no use of --cfg, users lose the benefits of checking keys.

I'm particularly concerned by the fact that more novice users will likely be weighted towards being in these situations. I wish I had recorded more details about my experience using it, either way, as I believe I found bad cfg names in my code (besides rustbuild which was more obsolete than anything)

I don't think taking a small step now like the one above rules out the possibility of expanding this in the future if/when Cargo global features or another appropriate mechanism becomes available. Landing a lint that targets a specific set of issues and then gradually expanding that over time is very common for compiler lints.

Frankly, globals has been dead in the water. I don't have time and I talk to many people who want it but won't step up to finish the design work. I worry about coupling re-enabling the full policy to some indeterminate deadline like that.

wesleywiser commented 3 months ago

@epage I fully agree with your point, there is a delicate balance to maintain. While reading the various tracking issues on both rust-lang/rust and rust-lang/cargo for check-cfg, one thing that stuck out to me was that several different users reported finding actual issues (yay!) but often having to wade through a bunch of incorrect warnings to do so.

For me, something like what I proposed above clears the quality bar and is more of a net-positive than a net-negative to the developer experience. It's true that it won't catch everything and that some users might expect that with no further configuration out of the box (at the same time, I could also see some developers intuitively feel that it probably only will work for built-in stuff and not custom cfgs). I believe there's a bunch of different ways that could be dealt with:

I'm not particularly advocating for any of those ideas, I just want to point out that we have a lot of ways we can evolve the feature and related tools to make it more useful to novices, developers without custom cfgs and all kinds of other users.

My feeling is that starting to turn this lint on gradually still provides a ton of value with the main disadvantage being that it doesn't catch all cases it could catch (which, frankly, is true of most of rustc's lints).

I worry about coupling re-enabling the full policy to some indeterminate deadline like that.

I definitely see why that is concerning since indeterminate deadlines are rarely motivating to either teams or contributors (the deadline is indeterminate so does it really need to be done now?). At the same time, my sense from reading RFC 3031 has always been that the full policy (checking of all cfgs by default) would probably not happen, rather the goal was to catch common issues that we can be certain of. Of course, if users want to opt-in to enable additional checking then that would be encouraged (and RFC 3031 makes mention of a stable --check-cfg Cargo option for this purpose) but not mandated.

workingjubilee commented 3 months ago

I believe it would benefit things if the rollout was more-gradual between "checking some configurations" and "checking all", even if it was only to allow the ecosystem more time to absorb the impact and you simply threw the switch to the current policy sometime later. People are... very visibly rapidly learning what ways there are to integrate this new feedback into their workflows. The current lesson they're learning is mostly "silence the machine forever", and they will teach other programmers that. Let's not pretend crate maintainers and novice Rust programmers don't talk to each other. Indeed I've heard that people have copied my code several times... even when (especially when!) I reaaally kinda wish they hadn't.

And for the linting itself, there were also edge-cases that I noticed that might have benefited from closer attention, too, and might even reflect bugs. But I haven't even mentioned them yet as I simply stared down the number of errors and just swept anything that required thinking under allow(unexpected_cfgs). It felt pointless to discuss improving more specific things if it doesn't even make a dent in the number of warnings. I'm not special, there. If people don't feel incremental improvement is on the table, they often prefer to respond by simplifying.

chorman0773 commented 3 months ago

Another example of where this can be seen is the improper_c_types lint. I've learned that the way to "fix" getting that lint (or improper_c_types_definitions) is to #[allow] it. Which works until I make an actual mistake that puts an repr(Rust) type by-value in a function signature.

fintelia commented 3 months ago

Another point I want to highlight is that the reason that concerns are being raised now, rather than several months from now is that people are testing with the nightly toolchain. Either they're running locally and getting a bunch of messages in their terminal or they're running in CI with the -D warnings flag (or equivalent). This is incredibly valuable for the ecosystem, but it means that those projects have to rapidly deal with any new warnings added to the nightly compiler or they'll be overrun by noise / have perpetually failing CI.

Rather than expecting all such projects to mitigate unexpectedly disruptive lints whenever they are introduced, I'd argue that the compiler have a policy of temporarily reverting (or setting the lint to allow) when significant concerns are raised. That would provide time to discuss and chart the best path forward before re-enabling. Which might mean rolling out the lint alongside an automated fix, narrowing the scope, etc. It would reduce the burden on the projects that do test on nightly and signal that feedback is being heard.

I fear that without this sort of measure, some folks may start to wonder whether the toil of addressing unpolished lints is even worth it and stop testing the nightly lints altogether

Urgau commented 3 months ago

I agree with @epage that the (only?) inconvenience of the the feature must be balanced out with the all benefits of it, and that it is highly non-trivial to so so.

One way to figure this out is IMO to look at the Crater run we did, https://github.com/rust-lang/rust/issues/120701#issuecomment-1937010106, that crater run analysed 414692 projects (from GitHub and crates.io), we found out that, at the time, 6708 projects (1.62%) would have been affected one way or another by the feature.

After fixing some genuine false-positive on our part (mainly around docsrs, Clippy, and rustfmt) we are left with 5959 projects that would still need some form of action, that represented 1.4% of all the projects.

The vast majority of those projects didn't use any custom cfgs, they were in large majority, missing Cargo features and invalid target_* cfgs. Those warnings could simply be fixed by either:

Now onto the custom cfgs that were (the most) detected in the Crater run[^1]: tarpaulin_include (212 projects), nightly (157 projects) fuzzing (103 projects), tarpaulin (97), loom (78 projects), test_utilities (23 projects).

They represent at most[^2] 670 projects, so around 0.16% of all the projects tested!

This number is IMO not insignificant but also relatively small. So we should make sure to not overly give an importance to this group over 99% of all other projects.

[^1]: I'm excluding both the doc_cfg and docs_rs cfgs since I believe they both should be replaced by the docsrs cfg.

[^2]: given that some projects many use multiple custom cfgs at the same time in their projects


Now onto the most stated inconvenience reported, the use of a build.rs to expected custom cfgs. This was persued because as said by @epage, because T-cargo rejected (for good reasons IMO) a #[cfg] table https://github.com/rust-lang/cargo/pull/11631#issuecomment-1487424886.

The major inconvenience of the build.rs is their relative slowness, but as stated earlier in this thread, https://github.com/rust-lang/rust/issues/124800#issuecomment-2097248696, one can create a local only build.rs by excluding the build.rs from the distributed package:

  [package]
  name = "foo"
  version = "0.1.0"
+ exclude = ["build.rs"]
fn main() {
    // Warning: build.rs is not published to crates.io.

    println!("cargo::rerun-if-changed=build.rs");
    println!("cargo::rustc-check-cfg=cfg(loom)");
}

This solution albeit unconventional is a solution, that I think shouldn't be dismissed at all, but instead should maybe be pursued. [^3]

This solution also as the advantage of being easy to implement for users and don't require much.

Some other solutions have been proposed but I think they all have caveats that local-only build.rs doesn't have.

[^3]: concerned users about the discrepancies between local and non-local can just run cargo package

tbu- commented 3 months ago

I find it bad that you claimed "no false positives[^1]" in the summary post, this doesn't look like honest communication in retrospect.

This number is IMO not insignificant but also relatively small. So we should make sure to not overly give an importance to this group over 99% of all other projects.

I find this sentence misleading. Lowering the amount of false positives and true positives isn't necessarily "giving importance to 99% of the projects", but it might mean that you get almost no false positives at all, making the acceptance of the lint in the ecosystem better, directly enhancing the effectiveness of the lint, because it's deactivated less often. Even in some proposed weaker forms where it doesn't warn (by default) for everything it would warn for today, it's still a huge improvement.

[^1]: by "false positive" I mean: missing well known names/value

Urgau commented 3 months ago

I find it bad that you claimed "no false positives1" in the summary post, this doesn't look like honest communication in retrospect.

The point of the feature is to lint on unexpected cfgs and in order to do that we take the well known cfgs (derived from the Rust toolchain) and the expected cfgs from the user. So if a cfg is in neither of them, it's for the Rust compiler and Cargo an unexpected cfg that the feature should lint about, and so by definition is not a false positive since it is unexpected.

It may be that the user is expecting the cfg, in which case using the cargo:rustc-check-cfg instruction solves the issue.

Even in some proposed weaker forms where it doesn't warn (by default) for everything it would warn for today, it's still a huge improvement.

Maybe for your projects but not for all the other projects that don't use custom cfgs, and so would positively benefits from the feature as it currently is.

tbu- commented 3 months ago

The point of the feature is to lint on unexpected cfgs

No. The point of the feature is to make users aware of mistyped or invalid cfgs. Warning for all lints not explicitly listed somewhere is a means to it, but it's certainly not the point of the feature.

Maybe for your projects

I don't have any projects using custom cfgs. I generally dislike causing work for others, and lints with false-positives that could be reduced is something that can be tackled in that direction.

apoelstra commented 3 months ago

The vast majority of those projects didn't use any custom cfgs, they were in large majority, missing Cargo features and invalid target_* cfgs. Those warnings could simply be fixed by either:

  • fixing the typo
  • removing the staled condition
  • adding the feature to the features table
  • ...

I have multiple projects which I guess fall into the "..." category in this list, which I further guess indicates "no simple fix exists". Can you provide an actual count of these ones?

Now onto the custom cfgs that were (the most) detected in the Crater run1: tarpaulin_include (212 projects), nightly (157 projects) fuzzing (103 projects), tarpaulin (97), loom (78 projects), test_utilities (23 projects).

They represent at most2 670 projects, so around 0.16% of all the projects tested!

Saying "at most" is misleading here because it excludes the indefinite number of "..." crates mentioned above. You mean to say "at least".

This number is IMO not insignificant but also relatively small. So we should make sure to not overly give an importance to this group over 99% of all other projects.

This mode of communication is, bluntly, extremely insulting. I do not understand how your lint made it into any release, even a nightly one, if your opinion is that "well, this breaks at least hundreds of crates, even after categorically excluding ones that I don't think matter, but that's only a small number so it's ok to enable this feature with no way to disable it".

This lint is not about soundness or safety or anything critical. It should not be breaking anyone without a clear way to disable it.

But okay, I understand that you tried to estimate the impact on the ecosystem and you gauged it to be small (and nobody took the time to check your work). But when multiple projects, including several major projects, take the time to file issues and comments saying that their projects are broken, this indicates that there is something wrong and the lint needs to be backed out until a less-disruptive method of deployment can be found.

ChrisDenton commented 3 months ago

I think this lint is fine on the rustc side. It doesn't do anything unless you ask it to by using --check-cfg and it is a very useful feature. If it wasn't desirable to have this then this conversation would be a lot shorter.

The issue here is the interaction with cargo, which makes use of --check-cfg out of the box. Up until now, cargo was really only aware of Cargo.toml features. It didn't know about custom global cfgs, and didn't need to. Now it needs to.

If cargo could, by default, only enable checks for built-ins or feature cfgs then I think that would fit the previous cargo model. Though I acknowledge the problem with that is it may not be obvious to users why some cfgs are checked but others aren't. Or worse, they may assume cfgs are being checked when they aren't.

I think may main issue with the cargo interface as it stands is how different it is from any other lint. Default-enabled lints usually have few, if any, false positives yet any use of custom global cfgs is immediately a false positive by default. Also no other default enabled lint I'm aware of requires configuration.

I suspect a number of projects will just allow(unexpected_cfgs). Which is a shame because it is a useful lint.