-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Lint against unexpected cfgs in [target.'cfg(...)']
#14581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
@@ -56,6 +57,80 @@ pub struct TargetInfo { | |||
pub rustdocflags: Rc<[String]>, | |||
} | |||
|
|||
/// Check Config (aka `--print=check-cfg` representation) | |||
#[derive(Debug, Default, Clone)] | |||
pub struct CheckCfg { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this live in cargo-platform
?
A valid answer can "maybe later" so we can keep it close for now as we figure out what this should look like
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk, but one thing to keep in mind is that --print=check-cfg
is still unstable and I don't if we should expose unstable rustc parts in cargo-platform
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While that flag is unstable, the formatting of check-cfg's is already stable and this is using the same format, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Negative, the input of --check-cfg
and the output of --print=check-cfg
are different.
I choose to make the output of --print=check-cfg
very close to --print=cfg
, so it would be easier to parse for consumer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested I put those in cargo-platform
for now. f8179c5
(#14581)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I choose to make the output of --print=check-cfg very close to --print=cfg, so it would be easier to parse for consumer.
Is there a place that documents differences?
I worry that having check-cfg and print-cfg be different will make things more difficult, not less, as we now have two distinct ways of talking about the same kind of thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the documentation of --print=check-cfg
, on the left is the --check-cfg
input and on the right is the --print=check-cfg
output.
My concern about re-using the --check-cfg
syntax is that it's not easy to parse and would basically require every user to have a MetaItem parser, while with the simplified output you only need to split the first =
and strip the "
for the value, much easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned we may add support for something that isn't expressible in this syntax, e.g. https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618#additional-globalsvalues-validation-rules-24
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo the name --print=check-cfg
needs to change because that is not what its doing (but printing another format with the same intent) and giving it this name is confusing as I look through the PR. I know in the context of this PR what it means now but in viewing the code as someone without that context, that isn't clear at all.
Similarly, this is terrible UI design for rustc's command-line. The user has to not just read the docs but carefully parse a bullet list to try to understand that this is not check-cfg thats being printed.
let mut process = rustc.workspace_process(); | ||
|
||
apply_env_config(gctx, &mut process)?; | ||
process | ||
.arg("-") | ||
.arg("--print=check-cfg") | ||
.arg("--check-cfg=cfg()") | ||
.arg("-Zunstable-options") | ||
.env_remove("RUSTC_LOG"); | ||
|
||
// Removes `FD_CLOEXEC` set by `jobserver::Client` to pass jobserver | ||
// as environment variables specify. | ||
if let Some(client) = gctx.jobserver_from_env() { | ||
process.inherit_jobserver(client); | ||
} | ||
|
||
let (output, _error) = rustc | ||
.cached_output(&process, 0) | ||
.with_context(|| "failed to run `rustc` to learn about check-cfg information")?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I've not yet inspected this for alignment with our other rustc calls
This probably could have used a conversation with the Cargo team first to ensure alignment with where we are going with lints. If there are questions about the direction I'm pointing this towards, feel free to reach out! |
src/cargo/ops/cargo_compile/mod.rs
Outdated
if lint_rustflags.iter().any(|a| a == "--check-cfg") { | ||
Ok(Some(CheckCfg::new(gctx, rustc, Some(lint_rustflags))?)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel comfortable having us parse a command line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we don't parse it but forward it to rustc but then we are conditionally including unrelated flags in the call to rustc which feels odd and potentially brittle
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's a hack and I don't really like it either, but we just deduplicated all the logic in #14567, that I didn't wanted to reintroduce one that will potentially stay indefinitely and the unrelated flags are only allow/warn/deny which is fine for --print=check-cfg
.
ad80bc5
to
18f2caf
Compare
18f2caf
to
abc9f45
Compare
This comment has been minimized.
This comment has been minimized.
ab47bd4
to
257f0c5
Compare
This comment has been minimized.
This comment has been minimized.
465bec1
to
5e1f93d
Compare
@epage @Muscraft I have updated the PR with the conclusion of our discussion at the All Hands. That is, the lint is now under Cargo I don't remember if we had settle on the lint config position, ie if we should share |
This comment has been minimized.
This comment has been minimized.
with the help of the Cargo lints infra
5e1f93d
to
decce97
Compare
[lints.rust.unexpected_cfgs] | ||
level = "warn" | ||
check-cfg = ['cfg(foo)'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remember if we had settle on the lint config position, ie if we should share check-cfg = [...] from rust.unexpected_cfgs or if cargo.unexpected_cfgs should have a distinct one. I've left the sharing for now.
I would say if we have our own lint, we have our own lint config.
If anything, in light of lints being fractured like this, I would see us re-evaluating whether we should have per-lint config or a general config table.
@@ -871,3 +872,255 @@ fn unexpected_cfgs_target() { | |||
"#]]) | |||
.run(); | |||
} | |||
|
|||
#[cargo_test(nightly, reason = "--print=check-cfg is unstable in rustc")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we consolidate all of the tests into one commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a feature gate test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace rustc --print=cfg parsing with handrolled loop
There a reason this isn't just squashed into the commit that used the iterators?
process.arg("--print=crate-name"); // `___` as a delimiter. | ||
process.arg("--print=check-cfg"); | ||
|
||
process.arg("--check-cfg=cfg()"); // otherwise `--print=check-cfg` won't output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is a bit misleading, --print=check-cfg
alone would print something, just not what we want, without --check-cfg=cfg()
it would be like we didn't enable check-cfg checking and so --print=check-cfg
would only return the default state (of no checking).
We also have --check-cfg=cfg()
when generating the check_cfg_args
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intent of --check-cfg=cfg()
in here or in that comment is unclear.
let mut check_cfg = CheckCfg::default(); | ||
check_cfg.exhaustive = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API-wise, this feels dirty that we are having to tell it that the default state before parsing is exhaustive = true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only know if it's exhaustive by the absence of any()=any()
and any()
, which we can't know inside parse_print_check_cfg_line
since it doesn't know if we have finished processing all the lines.
What about having associated function CheckCfg::parse_print_check_cfg_lines
(note the s
at the end), which would return a CheckCfg
(no Default impl anymore), this function would do the business of parsing all the lines and setting the exhaustive
flag appropriately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about having associated function CheckCfg::parse_print_check_cfg_lines
That could work.
Would it also make sense for the constructor to set exhaustive
to true
?
// FIXME: While it doesn't doesn't really matter for `--print=check-cfg`, we should | ||
// still pass the actual requested targets instead of an empty array so that the | ||
// target info can always be de-duplicated. | ||
// FIXME: Move the target info creation before any linting is done and re-use it for | ||
// all the packages. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are we expected to track these FIXMEs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk, what about creating GitHub issues about those.
One thing to note is that if we follow #14581 (comment), and have the --print=check-cfg
in the same check/build unit, those fixme disappear.
groups: &[], | ||
default_level: LintLevel::Warn, | ||
edition_lint_opts: None, | ||
feature_gate: None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now only the acquisition of check-cfg
s from the compiler is feature gated. The lint should be feature gated as well.
), | ||
}; | ||
|
||
pub fn unexpected_target_cfgs( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Muscraft could you also review this new lint since we are so new at these?
// Retrieving the span can fail if our pretty-priting doesn't match what the | ||
// user wrote: `unix="foo"` and `unix = "foo"`, for that reason we don't fail | ||
// and just produce a slightly worth diagnostic. | ||
if let Some(span) = get_span(manifest.document(), &["target", &*format!("cfg({cfg_expr})")], false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I wonder how often this will happen and if we need to do something else.
I wish we had a style guide for Cargo fleshed out so we could just assume someone is using whatever style is emitted by cargo fmt
.
// If we have extra `--check-cfg` args comming from the lints config, we need to | ||
// refetch the `--print=check-cfg` with those extra args. | ||
let lint_rustflags = pkg.manifest().lint_rustflags(); | ||
let check_cfg = if lint_rustflags.iter().any(|a| a == "--check-cfg") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel too comfortable having this stringly-typed API from our Cargo.toml
parser to here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me either. Looking at the flags like this is ugly, I would love to not have to do this. A better way would probably be to pass always pass --print=check-cfg
(with a file path, with maybe with the JSON output at some point) in the check/build invocation of the crate, that way we only have one compilation unit. It would be a much larger change though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we could refactor things so the generation of lint_rustflags
is delayed and so we have access to more specific check-cfg
configuration.
// refetch the `--print=check-cfg` with those extra args. | ||
let lint_rustflags = pkg.manifest().lint_rustflags(); | ||
let check_cfg = if lint_rustflags.iter().any(|a| a == "--check-cfg") { | ||
Some(target_info.check_cfg_with_extra_args(gctx, &rustc, lint_rustflags)?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we calling rustc for this rather than processing them ourselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--check-cfg
args are opaque for Cargo and I would like for it to stay that way, otherwise will have to duplicate the parsing logic in Cargo, keep them consistent, it's already non trivial in rustc
. Processing them in Cargo would also means that any evolution would be bound on both Cargo and rustc implementing them and stabilizing them at the same time. I really don't want to go that route.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except instead we have a parser for something not quite like check-cfg
and have to process that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--check-cfg
and --print=check-cfg
are very different, --check-cfg
is an input that accepts multiple ways for doing the same thing, it can evolve, and new way of doing things, for example, we may one day add the shorthand target(...)
; on the other-hand the output of --print=check-cfg
is meant to be stable, no matter the inputs given in --check-cfg
.
With --print=check-cfg
each line a expected key-value (except for any()=any()
or any()
but those are exceptions), it's much simpler to parse and it doesn't vary if the input changes, like if introduced the shorthand target(...)
to --check-cfg
, the output would stay with the full form, no matter the input given.
What does this PR try to resolve?
Since #13571, we now lint by default on unexpected cfgs in Rust code, but one area that is missing are cfg expressions in
Cargo.toml
and.cargo/config.toml
. This PR address this missing piece by (unstably) introducing-Zcheck-target-cfgs
based onrustc --print=check-cfg
.The feature
voluntarily does not always followbut instead follows RustRUSTFLAGS
(to avoid mistakes with one-off and for consistency)unexpected_cfgs
lint configuration:In terms of implementation, one global
--print=check-cfg
is retrieved and fetched, packages that have their owncheck-cfg
inputs have each their own--print=check-cfg
, otherwise the expected cfgs will get mixed up. Each--print=check-cfg
is cached and only done if necessary.How should we test and review this PR?
As asked I tried putting everything into small commits, they may not reflect the de-coupling possible as they follow the implementation but they are still IMO very useful. However I strongly recommand doing a first-pass of the complete diff first.
In terms of tests, I added some combinations that I though would be useful, I can add more if desired.
Additional information
Documentation for
--print=check-cfg
. It was one of motivation of the compiler MCP rust-lang/compiler-team#743.r? @epage