Skip to content
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

Lint against unexpected cfgs in [target.'cfg(...)'] #14581

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

Urgau
Copy link
Member

@Urgau Urgau commented Sep 23, 2024

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 on rustc --print=check-cfg.

The feature voluntarily does not always follow RUSTFLAGS (to avoid mistakes with one-off and for consistency) but instead follows Rust unexpected_cfgs lint configuration:

[target.'cfg(foo)'.dependencies] # will not get linted about
cfg-if = "1.0"

[lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(foo)'] # because it's mark as expected here

In terms of implementation, one global --print=check-cfg is retrieved and fetched, packages that have their own check-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

@rustbot rustbot added A-cfg-expr Area: Platform cfg expressions A-documenting-cargo-itself Area: Cargo's documentation A-unstable Area: nightly unstable support S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 23, 2024
@@ -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 {
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Member Author

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)

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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

Copy link
Contributor

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.

Comment on lines 996 to 1023
let check_cfg = {
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")?;

let lines = output.lines();

lines.fold(CheckCfg::default(), |mut check_cfg, line| {
check_cfg.process_line(line);
check_cfg
})
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The feature voluntarily does not follow RUSTFLAGS (to avoid mistakes with one-off and for consistency) but instead follows Rust unexpected_cfgs lint configuration:

Could you clarify what you mean by this?

Calling rustc is "expensive" for no-op builds which is especially important for cargo-script. Yes, this is cached, but I'd like to better understand why we need this over adding it to another call.

Copy link
Member Author

@Urgau Urgau Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to avoid confusing users who might add --check-cfg args in RUSTFLAGS, thinking that it works while at the second they will remove them from RUSTFLAGS it will not anymore; but now that I think about it more, it's already the case for rustc (since it gets RUSTFLAGS), so this behaviour would be worse than not forwarding it as it would work with rustc but not Cargo.

I will revert this behaviour and merge it into the others --prints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this respect RUSTFLAGS would likely require this to be processed during compilation. Properly processing RUSTFLAGS is a complex, expensive operation as it is fixed point as it evaluates how new --cfgs opt-in more RUSTFLAGS.

Copy link
Member Author

@Urgau Urgau Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TargetInfo as some handling of RUSTFLAGS, so we now use it's handling of RUSTFLAGS. 18f2caf (#14581)

Comment on lines 997 to 1015
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")?;
Copy link
Contributor

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

tests/testsuite/cfg.rs Outdated Show resolved Hide resolved
Comment on lines 618 to 631
gctx.shell().warn(format!(
"{prefix}unexpected `cfg` condition value: `{value}` for `{cfg}` in `[target.'cfg({cfg_expr})'{suffix}]`"
))?;
}
None => {
gctx.shell().warn(format!(
"{prefix}unexpected `cfg` condition name: `{name}`{cfg} in `[target.'cfg({cfg_expr})'{suffix}]`",
cfg = if value.is_some() {
format!(" for `{cfg}`")
} else {
format!("")
}
))?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this aligns closely with our new linting system, I'm assuming @Muscraft would prefer these to be using annotate-snippets

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last revision of this PR is now using annotate-snippets.

@epage
Copy link
Contributor

epage commented Sep 25, 2024

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!

Comment on lines 639 to 640
if lint_rustflags.iter().any(|a| a == "--check-cfg") {
Ok(Some(CheckCfg::new(gctx, rustc, Some(lint_rustflags))?))
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Member Author

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.

Comment on lines 969 to 971
// FIXME: While it doesn't doesn't really matter for `--print=check-cfg`, wee should
// still pass the actual requested targets instead of an empty array so that the
// target info can be de-duplicated.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are we tracking this?

src/cargo/util/lints.rs Outdated Show resolved Hide resolved
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
Comment on lines 976 to 662
return Ok(());
};

// 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") {
Some(target_info.check_cfg_with_extra_args(gctx, &rustc, lint_rustflags)?)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we are doing both a "global" and " extra_args" calls rather than just the latter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "global" call is always done, cached and already contains the --print=check-cfg, so getting it is mostly "free", and even if we don't use the print-check-cfg it parsed it is still useful as it gives us all the RUSTFLAGS args that we can just re-use for free to the "extra_args" case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't need to ask for it from rustc twice, we probably shouldn't. Removing the global check_cfg seems like it would simplify this overall PR as we no longer need to interject ourselves into the regular rustc query, even if we still need it for processing rustflags.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well we don't really ask for rustc twice, we are just re-using a already available information for when there is no [lints.rust.unexpected_cfgs.check-cfg] specified, which I think is the vast majority of projects.

The same "global" call will be used for cfgs, split-debuginfo support, ...


Schematically, if we have 3 packages, we would currently do 1 rustc call for the "global".

Now imaging that one the package as extra check-cfg args from the lint config, with the current strategy we would only do 2 rustc calls, the "global" and the one specialized for the package.

If we were to not "interject ourselves into the regular rustc query" (aka "global") there would 4 rustc calls (the global and 1 per package) instead of 2.


Btw I'm getting mixed signal with this comment and the one this conversation:

Calling rustc is "expensive" for no-op builds which is especially important for cargo-script. Yes, this is cached, but I'd like to better understand why we need this over adding it to another call.

Which is why I changed strategy to reduce the number of calls.

src/cargo/util/lints.rs Outdated Show resolved Hide resolved
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
@Urgau Urgau force-pushed the check-cfg-target-lint branch 3 times, most recently from 8855ab4 to dbc30b8 Compare September 29, 2024 17:33
@bors
Copy link
Contributor

bors commented Oct 1, 2024

☔ The latest upstream changes (presumably #14630) made this pull request unmergeable. Please resolve the merge conflicts.

@Urgau Urgau force-pushed the check-cfg-target-lint branch 2 times, most recently from da4efc6 to fd48e03 Compare October 2, 2024 09:31
src/cargo/util/lints.rs Outdated Show resolved Hide resolved
@bors
Copy link
Contributor

bors commented Oct 8, 2024

☔ The latest upstream changes (presumably #14137) made this pull request unmergeable. Please resolve the merge conflicts.

@bors
Copy link
Contributor

bors commented Oct 31, 2024

☔ The latest upstream changes (presumably #14752) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-cfg-expr Area: Platform cfg expressions A-documenting-cargo-itself Area: Cargo's documentation A-unstable Area: nightly unstable support A-workspaces Area: workspaces S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants