From 3573719650cd637ec9769d14a8a47f1f8847421c Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Fri, 6 Dec 2024 19:39:02 -0500 Subject: [PATCH 01/15] Copy template for RFC --- text/0000-cargo-supported-targets.md | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 text/0000-cargo-supported-targets.md diff --git a/text/0000-cargo-supported-targets.md b/text/0000-cargo-supported-targets.md new file mode 100644 index 00000000000..a2ab4c4c8a6 --- /dev/null +++ b/text/0000-cargo-supported-targets.md @@ -0,0 +1,97 @@ +- Feature Name: (fill me in with a unique ident, `my_awesome_feature`) +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +One paragraph explanation of the feature. + +# Motivation +[motivation]: #motivation + +Why are we doing this? What use cases does it support? What is the expected outcome? + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: + +- Introducing new named concepts. +- Explaining the feature largely in terms of examples. +- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. +- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? + +For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This is the technical portion of the RFC. Explain the design in sufficient detail that: + +- Its interaction with other features is clear. +- It is reasonably clear how the feature would be implemented. +- Corner cases are dissected by example. + +The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- Why is this design the best in the space of possible designs? +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? + +# Prior art +[prior-art]: #prior-art + +Discuss prior art, both the good and the bad, in relation to this proposal. +A few examples of what this can include are: + +- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? +- For community proposals: Is this done by some other community and what were their experiences with it? +- For other teams: What lessons can we learn from what other communities have done here? +- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. + +This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. +If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. + +Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. +Please also take into consideration that rust sometimes intentionally diverges from common language features. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +# Future possibilities +[future-possibilities]: #future-possibilities + +Think about what the natural extension and evolution of your proposal would +be and how it would affect the language and project as a whole in a holistic +way. Try to use this section as a tool to more fully consider all possible +interactions with the project and language in your proposal. +Also consider how this all fits into the roadmap for the project +and of the relevant sub-team. + +This is also a good place to "dump ideas", if they are out of scope for the +RFC you are writing but otherwise related. + +If you have tried and cannot think of any future possibilities, +you may simply state that you cannot think of anything. + +Note that having something written down in the future-possibilities section +is not a reason to accept the current or a future RFC; such notes should be +in the section on motivation or rationale in this or subsequent RFCs. +The section merely provides additional information. From 06a31069cc1bb8cd50f9100d523409e21369bb2e Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Fri, 6 Dec 2024 19:42:54 -0500 Subject: [PATCH 02/15] First draft --- text/0000-cargo-supported-targets.md | 188 +++++++++++++++++++-------- 1 file changed, 132 insertions(+), 56 deletions(-) diff --git a/text/0000-cargo-supported-targets.md b/text/0000-cargo-supported-targets.md index a2ab4c4c8a6..62040278753 100644 --- a/text/0000-cargo-supported-targets.md +++ b/text/0000-cargo-supported-targets.md @@ -6,92 +6,168 @@ # Summary [summary]: #summary -One paragraph explanation of the feature. +The addition of `target-requirements` (name to be discussed) to `Cargo.toml`. +This field is an array of `target-triple`/`cfg` specification that restrict the set of target which a package +supports. dependents of packages must meet the `target-requirements` of their dependencies, and +binaries can only be built for targets that meet their `target-requirements`. # Motivation [motivation]: #motivation -Why are we doing this? What use cases does it support? What is the expected outcome? +Some packages don't support every possible rustc target. Trying to depend on a package that does +not support one's target often produces cryptic build errors, or fails at runtime. Allowing libraries to +specify target requirements makes build errors nicer, and then opens the door for more advanced +cross-compilation control. This is especially relevant to embedded programming, where one usually +supports only a few specific targets. Wasm projects also benefit. Along with `default-target`, this feature +has the potential to enhance DX when working in workspaces with packages designed for different targets. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means: - -- Introducing new named concepts. -- Explaining the feature largely in terms of examples. -- Explaining how Rust programmers should *think* about the feature, and how it should impact the way they use Rust. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, describe the differences between teaching this to existing Rust programmers and new Rust programmers. -- Discuss how this impacts the ability to read, understand, and maintain Rust code. Code is read and modified far more often than written; will the proposed feature make code easier to maintain? - -For implementation-oriented RFCs (e.g. for compiler internals), this section should focus on how compiler contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms. +The `target-requirements` field can be added to `Cargo.toml` in any/all Cargo-target +tables (`[lib]`, `[[bin]]`, `[[example]]`, `[[test]]`, and `[[bench]]`). + +This field consists of an array of strings, where each string is an explicit target-triple, or a `cfg` requirement +like for the `[target.'cfg(**)']` table. The supported `cfg` syntax is the same as the one for +[platform-specific dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies) +(i.e., we do not support `cfg(test)`, `cfg(debug_assertions)`, and `cfg(proc_macro)`. + +__For example:__ +```toml +[package] +name = "hello_cargo" +version = "0.1.0" +edition = "2021" + +[lib] +target-requirements = [ + 'cfg(target_family = "unix")', + "wasm32-unknown-unknown" +] +``` +Here we only support targets with `target_family = "unix"` __or__ the `wasm32-unknown-unknown` target. + +User experience is enhanced by providing a lint (deny by default) that fails compilation when +the target requirements of a package or one of its dependencies are not satisfied. If a package +has `target-requirements` specified, then all of its dependencies' `target-requirements` +must be a superset of its own (see [[#Reference-level explanation]] for details). + +When the field is not specified, any target is accepted. _But_, dependencies are checked for compatibility +only when building for a specific target. This is best illustrated with an example. Consider our package +`foo`, which depends on `bar`. `bar` has `target-requirements = ['cfg(target_os = "linux")']`. + +If `foo`: +- does not specify `target-requirements`, and tries to build for a linux target, compilation succeeds. +- specifies `target-requirements = ["cfg(all())"]` (this is a tautology, "true for any target"), + Then building for a linux target will fail (denied by lint), because `foo`'s requirements are not a subset + of `bar`'s requirements. + +This feature should not be eagerly used; most packages are not tailored for a specific subset of targets. +It should be used when packages clearly don't support all targets. For example: `io-uring` +requires `cfg(target_os = "linux")`, `gloo` requires `cfg(target_family = "wasm")`, and +`riscv` requires `cfg(target_arch = "riscv32")` or `cfg(target_arch = "riscv64")`. +This feature should also be used to enhance cargo's knowledge about your package. For example, +when working in a workspace where some packages compile with `#[no_std]` and `target_os = "none"`, +and some other packages are tools that require a desktop OS, using `target-requirements`, makes +`cargo --workspace` ignore packages which have `target-requirements` that are not +satisfied. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This is the technical portion of the RFC. Explain the design in sufficient detail that: - -- Its interaction with other features is clear. -- It is reasonably clear how the feature would be implemented. -- Corner cases are dissected by example. +_This is incomplete, semantics are not decided yet_. + +Please [[#Unresolved questions]] and [[#Rationale and alternatives]] before this section to +understand issues discussed. + +## Cargo procedure overview +There will be two new steps that `cargo` will perform. + +1. Some moment after `cargo` is done with dependency resolution, if a package contains a + `target-requirements` field, the following is done: + For each requirement `R`, and for each dependency `D`, `R` is checked against `D`, making sure that + `R` fully satisfies the `target-requirements` of `D`. That is, no target can satisfy the requirement + `R` and fail the requirements of `D` (one can see this as a "subset" relationship). + If this procedure fails, then a lint is raised. The behavior for checking `cfg` set relationships is + defined further below. _Note:_ This step is listed in [[#Rationale and alternatives]] as somewhat + optional, we do not _need_ it, but I think it would be nice to validate such things. + +2. Some moment after step 1, the build target is checked against the package's `target-requirements`, + and also against every dependency for the same thing. If it does not satisfy all dependencies the + package itself, a lint is raised. + +## Behavior with custom targets +Since we allow `cfg(..)` requirements, custom targets have well defined behavior. The problem arises +if we only supported target names and wildcards, because in this case custom targets would "never match". +## Determining `cfg` set belonging +If it is decided that a package's `target-requirements` are checked to make sure that they are a +subset of all dependencies `target-requirements`, then relation between `cfg` settings need to +be established. For example: +- `target_os = "windows"` ⊆ `target_family = "windows"` (although the inverse is true as well + currently). +- `target_os not in ["wasm", "wasi", "windows", "none", "uefi", "cuda"]` ⊆ + `target_family = "unix"`. + +These are the only two relations that I believe could be made (I have checked them against every +target available). The full list of configuration options is given +[here](https://doc.rust-lang.org/reference/conditional-compilation.html), +and I believe there are not +any other relations that can naturally be made. Apart from the cases above, `cfg` requirements +are only satisfied by themselves (e.g., `target_abi = "eabi"` satisfies `target_abi = "eabi"`). + +__TODOs:__ +- How this interacts with `[target.'cfg(..)']`. +- How this interacts with `bindeps`. -The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work. # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +- It adds yet another field to `Cargo.toml`. +- It complicates how `Cargo` works. +- Perhaps an external tool could achieve similar results? (I personally don't know how). +- from @epage: +> Performance: this is a lot of extra calls to rustc. Hopefully these +> are all compatible with our rustc cache so they won't make things too bad. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- Why is this design the best in the space of possible designs? -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain? +The feature described above is the most comprehensive that I could think of. Other simpler alternatives +are described here. +## Alternatives +- Have something more like `required-targets` (similar to `required-features`) that + also supports target glob expressions like `x86_64-*-linux-*`. +- Do not validate that a package's `target-requirements` satisfy all dependencies' + `target-requirements`. Only check that a selected target satisfies the package's and the dependencies' + requirements. +- Maybe we could use the `[target.**]` table instead. Having something like + ```toml + [target.'cfg(..)'] + allowed = false + ``` +- Naming alternatives: `supported-targets`, `required-targets`. + # Prior art [prior-art]: #prior-art -Discuss prior art, both the good and the bad, in relation to this proposal. -A few examples of what this can include are: - -- For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had? -- For community proposals: Is this done by some other community and what were their experiences with it? -- For other teams: What lessons can we learn from what other communities have done here? -- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background. - -This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. -If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages. - -Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. -Please also take into consideration that rust sometimes intentionally diverges from common language features. +Does any one know how other build tools solve this? # Unresolved questions [unresolved-questions]: #unresolved-questions -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before stabilization? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? +- What if we want to exclude specific target? We can exclude groups with `cfg(not(..))`, but there + is currently no way of excluding specific targets. +- If the field is not set for a cargo-target, and some dependencies have `target-requirements`, what + should we do? +- How do we make users bypass the lint? +- Should we solve for this during version solving? (the current rationale is that we don't want + targets to affect package version decisions). # Future possibilities [future-possibilities]: #future-possibilities -Think about what the natural extension and evolution of your proposal would -be and how it would affect the language and project as a whole in a holistic -way. Try to use this section as a tool to more fully consider all possible -interactions with the project and language in your proposal. -Also consider how this all fits into the roadmap for the project -and of the relevant sub-team. - -This is also a good place to "dump ideas", if they are out of scope for the -RFC you are writing but otherwise related. - -If you have tried and cannot think of any future possibilities, -you may simply state that you cannot think of anything. - -Note that having something written down in the future-possibilities section -is not a reason to accept the current or a future RFC; such notes should be -in the section on motivation or rationale in this or subsequent RFCs. -The section merely provides additional information. +- Make targets have an effect on which vendored dependencies make their way into `Cargo.lock` (I don't +have experience with this, someone please add corrections/details). +- Have different entry points for different targets (see [#9208](https://github.com/rust-lang/cargo/issues/9208)). From 8913c0588239e1081d6832e60695127678e37167 Mon Sep 17 00:00:00 2001 From: Charles Edward Gagnon Date: Sat, 14 Dec 2024 16:04:18 -0500 Subject: [PATCH 03/15] Second draft Somtimes use "crate" instead of "cargo-target" for better readability added section on handling cfgs miscellaneous fixes testing fix dead links --- text/0000-cargo-supported-targets.md | 385 +++++++++++++++++++-------- 1 file changed, 274 insertions(+), 111 deletions(-) diff --git a/text/0000-cargo-supported-targets.md b/text/0000-cargo-supported-targets.md index 62040278753..4af145130d9 100644 --- a/text/0000-cargo-supported-targets.md +++ b/text/0000-cargo-supported-targets.md @@ -3,34 +3,52 @@ - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +The word _target_ in `cargo` has two different meanings which are both relevant to this RFC. +The following terms will be used: +- "cargo-target": The part of a package that is built (`[lib]`, `[[bin]]`, `[[example]]`, `[[test]]`, or `[[bench]]`). +- "target": The architecture/platform that `rustc` is compiling for. + # Summary -[summary]: #summary -The addition of `target-requirements` (name to be discussed) to `Cargo.toml`. -This field is an array of `target-triple`/`cfg` specification that restrict the set of target which a package -supports. dependents of packages must meet the `target-requirements` of their dependencies, and -binaries can only be built for targets that meet their `target-requirements`. +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 crate supports. Crates must meet the `supported-targets` of their +dependencies, and they can only be built for targets that satisfy their `supported-targets`. # Motivation -[motivation]: #motivation -Some packages don't support every possible rustc target. Trying to depend on a package that does -not support one's target often produces cryptic build errors, or fails at runtime. Allowing libraries to -specify target requirements makes build errors nicer, and then opens the door for more advanced -cross-compilation control. This is especially relevant to embedded programming, where one usually -supports only a few specific targets. Wasm projects also benefit. Along with `default-target`, this feature -has the potential to enhance DX when working in workspaces with packages designed for different targets. +Some crates do not support every possible `rustc` target. Currently, there is no way to formally +specify which targets a crate does, or does not support. + +### Developer Experience + +Trying to depend on a crate that does not support one's target often produces cryptic build errors, +or worse, fails at runtime. Being able to specify which targets are supported ensure that unsupported +targets cannot build the crate. This also makes build errors specific. This feature also enhances +developer experience when working in workspaces or with packages designed for different targets simultaneously. + +### Cross Compilation + +Once it is known that a crate will only ever build for a subset of targets, it opens +the door for more advanced control over dependencies. +For example, transient dependencies under a `[target.**.dependencies]` table could be +excluded from `Cargo.lock` if the target restriction is not supported by the crate. +This is especially relevant to areas such as WebAssembly and embedded programming, +where one usually supports only a few specific targets. Currently, auditing and vendoring +is tedious because dependencies in `[target.**.dependencies]` tables always make their way +in the dependency tree, even though they may not actually be used. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The `target-requirements` field can be added to `Cargo.toml` in any/all Cargo-target +The `supported-targets` field can be added to `Cargo.toml` in any/all cargo-target tables (`[lib]`, `[[bin]]`, `[[example]]`, `[[test]]`, and `[[bench]]`). -This field consists of an array of strings, where each string is an explicit target-triple, or a `cfg` requirement -like for the `[target.'cfg(**)']` table. The supported `cfg` syntax is the same as the one for +This field consists of an array of strings, where each string is an explicit target-triple, or a `cfg` specification +(as for the `[target.'cfg(**)']` table). The supported `cfg` syntax is the same as the one for [platform-specific dependencies](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies) -(i.e., we do not support `cfg(test)`, `cfg(debug_assertions)`, and `cfg(proc_macro)`. +(i.e., `cfg(test)`, `cfg(debug_assertions)`, and `cfg(proc_macro)` are not supported). __For example:__ ```toml @@ -40,134 +58,279 @@ version = "0.1.0" edition = "2021" [lib] -target-requirements = [ +supported-targets = [ 'cfg(target_family = "unix")', - "wasm32-unknown-unknown" + "wasm32-unknown-unknown" ] ``` -Here we only support targets with `target_family = "unix"` __or__ the `wasm32-unknown-unknown` target. +Here, only targets with `target_family = "unix"` __or__ the `wasm32-unknown-unknown` target are allowed +to build the library. User experience is enhanced by providing a lint (deny by default) that fails compilation when -the target requirements of a package or one of its dependencies are not satisfied. If a package -has `target-requirements` specified, then all of its dependencies' `target-requirements` -must be a superset of its own (see [[#Reference-level explanation]] for details). - -When the field is not specified, any target is accepted. _But_, dependencies are checked for compatibility -only when building for a specific target. This is best illustrated with an example. Consider our package -`foo`, which depends on `bar`. `bar` has `target-requirements = ['cfg(target_os = "linux")']`. - -If `foo`: -- does not specify `target-requirements`, and tries to build for a linux target, compilation succeeds. -- specifies `target-requirements = ["cfg(all())"]` (this is a tautology, "true for any target"), - Then building for a linux target will fail (denied by lint), because `foo`'s requirements are not a subset - of `bar`'s requirements. - -This feature should not be eagerly used; most packages are not tailored for a specific subset of targets. -It should be used when packages clearly don't support all targets. For example: `io-uring` +the supported targets of a cargo-target or one of its dependencies are not satisfied. If a crate +has `supported-targets` specified, then all of its dependencies' `supported-targets` +must be a superset of its own, for it to be publishable. + +When `supported-targets` is not specified, any target is accepted as long as the target used satisfies +all dependencies' `supported-targets`. + +This feature should not be eagerly used; most crates are not tailored for a specific subset of targets. +It should be used when crates clearly do not support all targets. For example: `io-uring` requires `cfg(target_os = "linux")`, `gloo` requires `cfg(target_family = "wasm")`, and `riscv` requires `cfg(target_arch = "riscv32")` or `cfg(target_arch = "riscv64")`. -This feature should also be used to enhance cargo's knowledge about your package. For example, -when working in a workspace where some packages compile with `#[no_std]` and `target_os = "none"`, -and some other packages are tools that require a desktop OS, using `target-requirements`, makes -`cargo --workspace` ignore packages which have `target-requirements` that are not + +This feature should also be used to increase cargo's knowledge of a cargo-target. For example, +when working in a package where some cargo-targets compile with `#[no_std]` and `target_os = "none"`, +and some other cargo-targets are tools that require a desktop OS, using `supported-targets` makes +`cargo ` ignore cargo-targets which have `supported-targets` that are not satisfied. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -_This is incomplete, semantics are not decided yet_. - -Please [[#Unresolved questions]] and [[#Rationale and alternatives]] before this section to -understand issues discussed. - -## Cargo procedure overview -There will be two new steps that `cargo` will perform. - -1. Some moment after `cargo` is done with dependency resolution, if a package contains a - `target-requirements` field, the following is done: - For each requirement `R`, and for each dependency `D`, `R` is checked against `D`, making sure that - `R` fully satisfies the `target-requirements` of `D`. That is, no target can satisfy the requirement - `R` and fail the requirements of `D` (one can see this as a "subset" relationship). - If this procedure fails, then a lint is raised. The behavior for checking `cfg` set relationships is - defined further below. _Note:_ This step is listed in [[#Rationale and alternatives]] as somewhat - optional, we do not _need_ it, but I think it would be nice to validate such things. - -2. Some moment after step 1, the build target is checked against the package's `target-requirements`, - and also against every dependency for the same thing. If it does not satisfy all dependencies the - package itself, a lint is raised. - -## Behavior with custom targets -Since we allow `cfg(..)` requirements, custom targets have well defined behavior. The problem arises -if we only supported target names and wildcards, because in this case custom targets would "never match". -## Determining `cfg` set belonging -If it is decided that a package's `target-requirements` are checked to make sure that they are a -subset of all dependencies `target-requirements`, then relation between `cfg` settings need to -be established. For example: -- `target_os = "windows"` ⊆ `target_family = "windows"` (although the inverse is true as well - currently). -- `target_os not in ["wasm", "wasi", "windows", "none", "uefi", "cuda"]` ⊆ - `target_family = "unix"`. - -These are the only two relations that I believe could be made (I have checked them against every -target available). The full list of configuration options is given -[here](https://doc.rust-lang.org/reference/conditional-compilation.html), -and I believe there are not -any other relations that can naturally be made. Apart from the cases above, `cfg` requirements -are only satisfied by themselves (e.g., `target_abi = "eabi"` satisfies `target_abi = "eabi"`). - -__TODOs:__ -- How this interacts with `[target.'cfg(..)']`. -- How this interacts with `bindeps`. +## Compatibility with the selected target + +When `cargo` is run on a cargo-target, it checks that the selected target satisfies the `supported-targets` +of the cargo-target, and of all dependencies. If it does not, a lint is raised and the build fails. + +## Compatibility with dependencies + +For each dependency `D` of a crate, the `supported-targets` of `D` must be a superset of +the `supported-targets` of the crate. + +If the crate itself has no `supported-targets` specified, then all dependencies must support all targets. + +This is enforced only upon publishing (and thus only for crates). Since `supported-targets` is only useful +for dependents of a library or binary, enforcing this at build time is not necessary and overly intrusive. + +### Subset and superset relations +[subsets-and-supersets]: #subset-and-superset-relations + +When checking if a crate's `supported-targets` are a subset of all dependencies' `supported-targets`, +the standard mathematical definition of subset is used. That is, `A ⊆ B` if and only if every element +of `A` is also an element of `B`. + +- When comparing a `target-triple` 'A' against another `target-triple` 'B', A ⊆ B if + A and B are the same. +- When comparing a `target-triple` against a `cfg(..)`, the `target-triple` is a subset + of the `cfg(..)` if the `target-triple` satisfies the `cfg(..)`. +- When comparing a `cfg(..)` against a `target-triple`, the `cfg(..)` is never a subset of the `target-triple`. +- When comparing a `cfg(A)` against another `cfg(B)`, `cfg(A)` ⊆ `cfg(B)` if + A and B are the same. (e.g., `target_abi = "eabi"` ⊆ `target_abi = "eabi"`) + +To improve usability, two extra relations are defined: +- `cfg(target_os = "windows")` ⊆ `cfg(target_family = "windows")`. +- `cfg(target_os = )` ⊆ `cfg(target_family = "unix")`, where `` is any of + `["freebsd", "linux", "netbsd", "redox", "illumos", "fuchsia", "emscripten", "android", "ios", "macos", "solaris"]`. + This list needs to be updated if a new `unix` OS is supported by `rustc`'s official target list). + +The contrapositive of these relations are also true. + +The full list of configuration options is given [here](https://doc.rust-lang.org/reference/conditional-compilation.html). + +### Flattening `not`, `any` and `all` in `cfg` specifications +[flattening-cfg]: #flattening-not-any-and-all-in-cfg-specifications + +Since `cfg` specifications can contain `not`, `any`, and `all` operators, these must be handled. +This is done by flattening the `cfg` specification to a specific form. + +The `not` operator is "passed through" `any` and `all` operators using De Morgan's laws, until it +reaches a single `cfg` specification. For example, `cfg(not(all(target_os = "linux", target_arch = "x86_64")))` +is equivalent to `cfg(any(not(target_os = "linux"), not(target_arch = "x86_64")))`. + +Top level `any` operators are separated into multiple `cfg` specifications. For example, +```toml +supported-targets = ['cfg(any(target_os = "linux", target_os = "macos"))'] +``` +is transformed into +```toml +supported-targets = ['cfg(target_os = "linux")', 'cfg(target_os = "macos")'] +``` + +Top level `all` operators are kept as is, as long as they do not contain nested `any`s or `all`s. +If there is an `any` inside an `all`, the statement is split into multiple `all` statements. +For example, +```toml +supported-targets = ['cfg(all(target_os = "linux", any(target_arch = "x86_64", target_arch = "arm"))'] +``` +is transformed into +```toml +supported-targets = [ + 'cfg(all(target_os = "linux", target_arch = "x86_64"))', + 'cfg(all(target_os = "linux", target_arch = "arm"))' +] +``` +If an `all` contains an `all`, the inner `all` is flattened into the outer `all`. + +The result of these transformations is always a list of `cfg` specifications that +either contains a single specification, or an `all` operator with no nested operators. +This structure can then be used to evaluate subset relations. + +## Behavior with unknown entries (and custom targets) + +Just as `[target.my-custom-target.dependencies]` is allowed by `cargo`, `supported-targets` can contain +unknown entries. This is important because users may have different `rustc` versions, and the set of +official `rustc` targets is unstable; Targets can change name or be removed. Also, developers +may want to support their custom target. + +To determine if an entry in `supported-targets` is a target name or a `cfg` specification, the +same mechanism as for `[target.'cfg(..)']` is used (using +[`cargo-platform`](https://docs.rs/cargo-platform/latest/cargo_platform/index.html)). + +## Ignoring builds for unsupported targets + +When the target used is not supported by the cargo-target being built, the cargo-target will either be +skipped or a lint will be raised, depending on how `cargo` was invoked. +`cargo` behaves identically to when the `required-features` of the cargo-target are not satisfied. + +__Note:__ `required-features` is not applicable to `libraries`, while `supported-targets` is. Nevertheless, libraries +behave the same way as other cargo-targets. + +## Detecting unused dependencies + +An MVP implementation of this RFC does not _require_ this feature, but it is a natural extension, and a +major part of the motivation. Hence, it is informally described here. + +Dependencies may have `[target.'cfg(..)'.dependencies]` tables, which may never be used because of a +`supported-targets` restriction. + +When resolving dependencies for a package, each cargo-target can have a list of "unused dependencies" +if the `supported-targets` restriction is mutually exclusive with dependencies behind a `[target.'cfg(..)'.**]` +table. + +For each set of dependencies (normal, build, and dev), the intersection of these unused dependencies +can be purged from the dependency tree. + +This could be implemented either as a separate pass on the `Resolve` graph, or as part of dependency resolution. +If this is implemented as part of dependency resolution, it may or may not be favorable for +`supported-targets` to influence the version resolution of dependencies. + +When detecting unused dependencies for a cargo-target, _all_ targets specified in `supported-targets` must +be mutually exclusive with the target from a `[target.'cfg(..)'.dependencies]` table. If an entry in +`supported-targets` is not mutually exclusive with the target from a `[target.'cfg(..)'.dependencies]` +table, then the dependency cannot be considered unused. + +### Mutually exclusive `cfg` settings +[mutually-exclusive-cfg]: #mutually-exclusive-cfg-settings + +Some `cfg` settings have mutually exclusive elements, while some do not. What is meant here is, for example, +`target_arch = "x86_64"` and `target_arch = "arm"` are mutually exclusive (a target-triple cannot have both), +while `target_feature = "avx"` and `target_feature = "rdrand"` are not. + +`cfg` options that have mutually exclusive elements: +- `target_arch` +- `target_os` +- `target_env` +- `target_abi` +- `target_endian` +- `target_pointer_width` +- `target_vendor` + +Those that do not: +- `target_feature` +- `target_has_atomic` + +Special case: +- `target_family = "windows` and `target_family = "unix"` are mutually exclusive, while all other `target_family` + value pairs are not mutually exclusive. + +`cfg(not(