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

RFC: Allow packages to specify a set of supported targets #3759

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

Conversation

carloskiki
Copy link

@carloskiki carloskiki commented Jan 8, 2025

Summary

The addition of supported-targets to Cargo.toml. This field is an array of target-triple/cfg specifications that restricts the set of targets which a package supports. Packages must meet the supported-targets of their dependencies, and they can only be built for targets that satisfy their supported-targets.

Rendered

Somtimes use "crate" instead of "cargo-target" for better readability

added section on handling cfgs

miscellaneous fixes

testing

fix dead links
fix links and typos

fix example

fix note

remove note

more fixes

fixes

minor fixes

Third draft

add note to cargo-target level

remove todo
- fix GH ui bug (indented codeblocks)
@Lokathor
Copy link
Contributor

Lokathor commented Jan 8, 2025

I think that there should be a little more explanation about how docs generation works with this. Specifically: Can I build docs for a target that's unsupported, such as if my host machine isn't supported, can i cargo doc to still just see the documentation?

@ehuss ehuss added the T-cargo Relevant to the Cargo team, which will review and decide on the RFC. label Jan 8, 2025
@opeik
Copy link

opeik commented Jan 8, 2025

This would be a godsend for crates that link against third party libraries, thus limiting supported targets.

@carloskiki
Copy link
Author

I think that there should be a little more explanation about how docs generation works with this. Specifically: Can I build docs for a target that's unsupported, such as if my host machine isn't supported, can i cargo doc to still just see the documentation?

Indeed, and this is especially important since docs.rs needs to be able to generate the docs for all crates. I added it here.

@ahicks92

This comment was marked as off-topic.

@Lokathor
Copy link
Contributor

Lokathor commented Jan 9, 2025

Crates assuming that they're running on one of several targets could even end up making unsafe code decisions based on that fact. Forcing the code to "just build anyway" would naturally lead to problems.

And crates can already force themselves to only build only on a specific target, this would not be a new ability, but instead it's only a way to better organize that information.

@workingjubilee
Copy link
Member

The author already mentions in the Prior art that the following Rust can be written:

#[cfg(target_arch = "lol"))]
compile_error!("experience bij)";

Please do not make comments on the RFC which do not engage with the RFC's content.


User experience is enhanced by raising an error that fails compilation when the supported targets
of a package are not satisfied by the selected target. A package's `supported-targets` must be a subset
of its dependencies' `supported-targets`, otherwise the build also fails.
Copy link
Member

@joshtriplett joshtriplett Jan 9, 2025

Choose a reason for hiding this comment

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

This seems helpful, but at the same time, it may prove annoying to have to copy these across, if a dependency has a very specific list. And it may be non-trivial to enforce.

I think we should downgrade this to a lint, and say that it's best-effort, not mandatory.

Copy link
Member

Choose a reason for hiding this comment

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

This also seems complicated by the fact that I might have a dependency that only supports (say) wasm, but I'm using it solely as a dev-dep (e.g., in tests). I don't think that case merits also setting supported targets on the containing package as a whole, since downstream consumers might not care about that limitation for running on tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it's just a lint then unsafe coders can't depend on this, and they will still need to just use a compile_error! or something if they're really trying to avoid unsoundness.

Copy link
Contributor

@epage epage Jan 9, 2025

Choose a reason for hiding this comment

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

This is effectively saying that if you have a dependency that can't build on a target you claim to support, the build will fail and you should instead move it to a target.*.dependencies table.

imo that seems like something that should be a hard error to me.

I could see loosening the restriction on

When supported-targets is not specified, any target is accepted, so all dependencies must support all targets.

To me "we don't check", like package.rust-version, requiring supported-targets = ["cfg(true)"] to get the checking.

Copy link
Author

Choose a reason for hiding this comment

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

I totally agree with @epage here.

Using the same logic as package.rust-version is also something I have not thought of but is a great idea.

Copy link
Author

Choose a reason for hiding this comment

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

A problem I could see however is that if a package foo does not use supported-targets but one of its dependencies does, then when depending on foo errors of incompatible targets can be hard to solve since they come from transitive dependencies.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not quiet sure the problem. When building foo, you are building for a specific target and the error would be for that target. With package.rust-version, we check what all packages aren't compatible with the current toolchain and provide a single error message, see https://github.com/rust-lang/cargo/blob/9589831f61a8259919e64c6d68c1a36efc6efd20/src/cargo/ops/cargo_compile/mod.rs#L494-L543

Copy link
Member

Choose a reason for hiding this comment

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

If it's just a lint then unsafe coders can't depend on this, and they will still need to just use a compile_error! or something if they're really trying to avoid unsoundness.

supported-targets would still be enforced (modulo some kind of force option); I'm talking about softening the enforcement that a crate's supported-targets is a subset of its dependencies.

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI this has moved to a Future Possibility

@joshtriplett
Copy link
Member

This looks excellent!

@joshtriplett
Copy link
Member

Let's go ahead and start the process of asynchronously checking for consensus.

@rfcbot merge

@rfcbot
Copy link
Collaborator

rfcbot commented Jan 9, 2025

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Jan 9, 2025
@joshtriplett
Copy link
Member

@rfcbot concern should-crates-have-to-set-supported-targets-to-match-their-dependencies

Comment on lines 353 to 355
- `required-targets`. Pro: it matches with the naming of `required-features`. Con: `required-features` is a list of features
that must _all_ be enabled (conjunction), whereas `supported-targets` is a list of targets
where _any_ is allowed (disjunction).
Copy link
Contributor

Choose a reason for hiding this comment

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

For me the important precedence is that the field means "skip if the qualification is not met" and for that reason I favor using this name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently, this is different than required-features as this errors, rather than skips.

I raised this at https://github.com/rust-lang/rfcs/pull/3759/files#r1909208702

Copy link
Contributor

Choose a reason for hiding this comment

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

I also feel like using the word "support" carries a lot of unnecessary connotations that complicate the conversation. For example, with MSRV, the Cargo team has been leaning in the direction that "support" is an active process that gets tested. However, applying that here would lead to people over-constraining their targets.

Copy link
Author

Choose a reason for hiding this comment

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

The difference with this and required-features is that supported-targets is at the package level, not at the cargo-target level.

So if I run cargo build in a package with a binary foo which does not have its required-features met, then cargo can still possibly "do work" if there is another cargo-target which has its required-features met (for instance a library cannot have any required-features).

However if the supported-targets are not met for a package, then there is no chance of cargo doing compilation work for that package. That is why the packages are skipped when in a workspace, but an error is raised in a single package. This is just how if I ran cargo build --bin foo in the previous example, then cargo errors instead of skipping.

Copy link
Author

Choose a reason for hiding this comment

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

I do not have a preference for the name, I kept it as is to not confuse people who had read the Pre-RFC. I would not be against changing it.

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 think this being at the package or build-target level makes much of a difference. The RFC is starting at the package level and has build-target as a future possibility. In that case, we can treat this as the package is providing the default for all build-targets like package.edition

@epage
Copy link
Contributor

epage commented Jan 9, 2025

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 feature needed for proc-macros in a workspace like this? With #14903, this would also extend to workspace members that exist to be build scripts.

I'm wondering if there is any design overlap here that could benefit from considering proc-macros / build script cargo check --workspace filtering in the future.

Copy link
Author

Choose a reason for hiding this comment

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

Yes I think this is a viable option. I'm not sure how this should be specified but it is clearly relevant.

Copy link
Author

Choose a reason for hiding this comment

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

This comment is not directly related but I think that a proc-macro is like a build script in that it theoretically should allow any target to build it, because it might be run on any host imaginable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, a proc-macro is buildable in any host-supported target.

What I'm trying to highlight is if I run cargo check --target <tuple> --workspace, then a proc-macro will be built twice

  • Once for the host toolchain for being used in the workspace members that depend on it
  • Once for the target toolchain because that was requested

I was wondering if there could be a way for people to opt-in to cargo check --target <tuple> --workspace only building the proc-macro once. This RFC has some similar concepts (skipping of packages based on platform-related info) which is why I was wondering if (1) people find value in this idea and (2) if its close enough to this that we should see if we can design with this in mind as a future possibility

Comment on lines 285 to 296
Even then, it will happen that crates unnecessarily limit their dependents and users, because of
over restrictive `supported-targets`. So, users must be able to disable the lint or error.
To alleviate this, a flag like `--ignore-supported-targets` could be added to `cargo` to ignore the `supported-targets` of a
package, and a field like
```toml
[dependencies]
overrestrictive-dep = { version = "0.1.0", ignore-supported-targets = true }
```
could be added to ignore the `supported-targets` of a specific dependency. This functionality
could otherwise be added to the [`[patch]` table](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html#the-patch-section),
or be added as mutable metadata in package registries
([related discussion](https://blog.rust-lang.org/inside-rust/2024/03/26/this-development-cycle-in-cargo-1.78.html#why-is-this-yanked)).
Copy link
Author

Choose a reason for hiding this comment

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

A question I had related to the interop of ignoring errors and Cargo.lock trimming:

A package may add restrictions to supported-targets even though the package builds fine without the restrictions for the sake of Cargo.lock trimming. Let's say then someone builds the package for a target that is not accepted by ignoring supported-targets somehow (say with --ignore-supported-targets). Should the package still trim out dependencies?

I would argue that yes, because I think its important for a package to generate the same lockfile without regards for which target was selected. Also, there was clear intent behind the supported-targets specification, even if it may not be obvious why. I would argue that a user should be able to bypass the supported-targets of a package, but should not be able to change the behavior of the package (bringing in extra deps) without making modifications to the code. Similarly, a library should be able to guarantee which dependencies it has no matter the target used. So ignore-supported-targets on a dependency should not change the dependency graph of the dependency.

Choose a reason for hiding this comment

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

But then what happens if cargo build is run on an arch that's been trimmed out? Does it force-ignore supported-targets and amend Cargo.lock? Does it result in the generation of some kind of Cargo.lock.local? Does it result in every build potentially causing an implicit cargo update?

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like this is covered previously (#3759 (comment)) and was part of what I was asking to have included in #3759 (comment)

Copy link
Author

@carloskiki carloskiki Jan 22, 2025

Choose a reason for hiding this comment

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

But then what happens if cargo build is run on an arch that's been trimmed out? Does it force-ignore supported-targets and amend Cargo.lock? Does it result in the generation of some kind of Cargo.lock.local? Does it result in every build potentially causing an implicit cargo update?

It does none of that, it builds as if the supported-targets were respected and prunes the dependencies accordingly. If the build fails or does not behave normally at runtime because a dependency is missing then the user should realize that the supported-targets were there for a reason.

Keep in mind, this is a future possibility.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

- Should we strip the `cfg` prefix from the field e.g., `supported-targets = 'target_os = "linux"'`?
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 think we should. This makes the form of the syntax explicit, follows the pattern of how cfg syntax is used everywhere else, and gives us room to evolve this field

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. T-cargo Relevant to the Cargo team, which will review and decide on the RFC.
Projects
Status: FCP blocked
Development

Successfully merging this pull request may close these issues.