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

**URL:** <https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979>\
**Category:** cargo\
**Created:** [December 6, 2024, 2:09am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979 "2024-12-06T02:09:20Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 6, 2024, 2:09am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/1 "2024-12-06T02:09:20Z")

</div>

- 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)

The word _target_ is extensively used in this document. The [glossary](https://doc.rust-lang.org/cargo/appendix/glossary.html#target) defines its many meanings. Here, _target_ refers to the "Target Architeture" for which a package is built. Otherwise, the terms "cargo-target" and "target-triple" are used in accordance with their definitions in the glossary.

# 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`.

# Motivation

Some packages do not support every possible `rustc` target. Currently, there is no way to formally specify which targets a package 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 ensures that unsupported targets cannot build the crate, and also makes build errors specific.

This feature also enhances developer experience when working in workspaces containing packages designed for many different targets. Commands run on a workspace ignore packages that don't support the selected target.

### Cross Compilation

Once it is known that a package will only ever build for a subset of targets, it opens the door for more advanced control over dependencies. For example, transient dependencies declared under a `[target.**.dependencies]` table are excluded from `Cargo.lock` if the dependent's `supported-targets` is mutually exclusive with the target preconditions under which the dependencies are included. 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 under `[target.**.dependencies]` tables always make their way in the dependency tree, even though they may not actually be used.

# Guide-level explanation

The `supported-targets` field can be added to `Cargo.toml` under the `[package]` table.

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., `cfg(test)`, `cfg(debug_assertions)`, and `cfg(proc_macro)` are not supported). If a selected target satisfies any entry of the `supported-targets` list, then the package can be built for that target.

**For example:**

```toml
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
supported-targets = [
    "wasm32-unknown-unknown",
    'cfg(target_os = "linux")',
    'cfg(target_os = "macos")'
]

```

Here, only targets satisfying: the `wasm32-unknown-unknown` target, **or** the `linux` OS, **or** the `macos` OS, are allowed to build the package. If no `supported-targets` was specified, then any target would be allowed.

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.

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

This feature should be used when a package clearly does 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 increase cargo's knowledge of a package. For example, when working in a workspace where some packages compile with `#[no_std]` and `target_os = "none"`, and some others are tools that require a desktop OS, using `supported-targets` makes `cargo <command>` ignore packages which have `supported-targets` that are not satisfied by the selected target.

# Reference-level explanation

When `cargo` is run on a package, it checks that the selected target satisfies the `supported-targets` of the package. If it does not, an error is raised and the build fails.

## Compatibility of `[dependencies]`

The set of `supported-targets` of a package must be a subset of the `supported-targets` of its `[dependencies]`. If the crate itself has no `supported-targets` specified, then all dependencies must support all targets.

If a dependency does not respect this requirement (if it is not compatible), an error is raised and the build fails.

If this was not enforced, a package could support targets that are not supported by its dependencies, which does not make sense.

## Compatibility of `[dev-dependencies]`

`[dev-dependencies]` are checked the using the same method as regular `[dependencies]`. That is, the package's `supported-targets` must be a subset of every `[dev-dependencies]`'s `supported-targets`. The rationale is that an example, test, or benchmark has access to the package's library and binaries, and so it must respect the `supported-targets` of the package.

## Compatibility of `[build-dependencies]`

What makes `[build-dependencies]` unique is that they are built for the host computer, and not the selected target. As such, they are not restrained by the `supported-targets` of the package. Hence, all dependencies are allowed in the `[build-dependencies]` table. However, a build error is raised if one of the build dependencies does not support the _host_'s target-triple at build time.

In the future, having all build dependencies support all targets could be enforced to ensure that a crate can be built on any host. This is left as a [future possibility](#restrict-build-dependencies).

## Platform-specific dependencies

Platform-specific dependencies are dependencies under the `[target.**]` table. This includes normal dependencies, build-dependencies, and dev-dependencies.

When platform-specific dependencies are declared, the conditions under which they are declared must be a subset of each dependency's `supported-targets`. For example, a dependency declared under `[target.'cfg(target_os = "linux")'.dependencies]` must at least support the `linux` OS.

For regular dependencies and dev-dependencies, it suffices for a platform-specific dependency to support the _intersection_ of the package's supported-targets, and the target conditions it is declared under. For example:

```toml
[package]
# ...
supported-targets = ['cfg(target_os = "linux")']

[target.'cfg(target_pointer_width = "64")'.dependencies]
foo = "0.1.0"

```

Here, it suffices for `foo` to support `cfg(all(target_os = "linux", target_pointer_width = "64"))`.

## Artifact dependencies

If an artifact dependency has a `target` field, then the dependency is not checked against the package's `supported-targets`. However, the selected `target` for the dependency must be compatible with the dependency's `supported-targets`. If it does not have a `target` field, then it is checked against the package's `supported-targets`, like any other dependency.

## Comparing `supported-targets`

When comparing two `supported-targets` lists, it is necessary to know if one is a _subset_ of the other, or if both are _mutually exclusive_. To proceed, both lists are flattened to the same representation, and they are then compared.

### 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 on a `cfg` specification is a list of `cfg` specifications that either contains a single specification, or an `all` operator with no nested operators.

This procedure is run on all `cfg` elements of a `supported-targets` list. The resulting list can then be used to evaluate relations. To be clear, the flattened list can only contain explicit target-triples, `cfg(A)` containing a single `A`, or `cfg(all(A, B, ...))`, all `A, B, ...` being single elements.

### The subset relation

To determine if a `supported-targets` list "A" is a subset of another such list "B", the standard mathematical definition of subset is used. That is, "A" is a subset of "B" if and only if each element of "A" is contained in "B".

So each element of "A" is compared against each element of "B" using the following rules:

- A `target-triple` is a subset of another `target-triple` if they are the same.
- A `target-triple` is a subset of a `cfg(..)` if the `cfg(..)` is satisfied by the `target-triple`.
- A `cfg(..)` is not a subset of a `target-triple`.
- A `cfg(all(A, B, ...))` is a subset of a `cfg(all(C, D ...))`, if the list `C, D, ...` is a subset of the list `A, B, ...`.

_Note_: All possible cases have been covered, since `cfg(A) == cfg(all(A))`.

More rules could be defined, but these are left as a [future possibility](#more-cfg-relations).

### Mutual exclusivity

For a `supported-targets` list "A" to be mutually exclusive with another such list "B", each element of "A" must be mutually exclusive with _all_ elements of "B" (The inverse is also true).

So each element of "A" is compared against each element of "B" using the following rules:

- A `target-triple` is mutually exclusive with another `target-triple` if they are different.
- A `target-triple` is mutually exclusive with a `cfg(..)` if the `cfg(..)` is not satisfied by the `target-triple`.
- A `cfg(all(A, B, ...))` is mutually exclusive with a `cfg(all(C, D, ...))` if any element of the list `A, B, ...` is mutually exclusive with any element of the list `C, D, ...`.

_Note_: All possible cases have been covered, since `cfg(A) == cfg(all(A))`.

Two `cfg` singletons are mutually exclusive under the following rules:

- `cfg(A)` is mutually exclusive with `cfg(not(A))`.
- `cfg(<option> = "A")` is mutually exclusive with `cfg(<option> = "B")` if `A` and `B` are different, and `<option>` has mutually exclusive elements.

Some `cfg` options 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`
- `target_family`

`target_family = "windows` and `target_family = "unix"` could also be defined as mutually exclusive to enhance usability, but this is left as a [future possibility](#more-cfg-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 package being built, the package will either be skipped or an error will be raised, depending on how `cargo` was invoked. If cargo is invoked in a workspace or virtual workspace without specifying a specific package, then `cargo` skips the package. If a specific package is specified using `--package`, or if `cargo` is invoked on a single package, then an error is raised.

## Eliminating unused dependencies from `Cargo.lock`

A package's dependencies may themselves have `[target.'cfg(..)'.dependencies]` tables, which may never be used because of the `supported-targets` restrictions of the package. These can safely be eliminated from the dependency tree of the package.

Consider the following example:

```toml
[package]
name = "foo"
# ...
supported-targets = ['cfg(target_os = "linux")']

[dependencies]
bar = "0.1.0"

```

```toml
[package]
name = "bar"

[target.'cfg(target_os = "macos")'.dependencies]
baz = "0.1.0"

```

Currently, `baz` is included in the dependency tree of `foo`, even though `foo` is never built for `macos`. With the addition of `supported-targets`, `baz` can be purged from the dependency tree of `foo`, since `target_os = "macos"` is mutually exclusive with `target_os = "linux"`.

Formally, dependencies (and transitive dependencies) under `[target.**.dependencies]` tables are eliminated from the dependency tree of a package if the `supported-targets` of the package is mutually exclusive with the target preconditions of the dependency.

# Drawbacks

- The `cfg` syntax is very expressive, but also very complex. As outlined above, detecting subset and mutual exclusivity relations is not trivial.
- Comparing `supported-targets` lists is an `O(n * m)` operation, with `n` and `m` being the number of elements in the lists. (I doubt this will be a problem, as `n` and `m` are expected to be small).
- Performance: this is a lot of extra calls to rustc. Hopefully these are all compatible with the rustc cache, so they won't make things too bad.
- This feature must be learned by those wanting to use dependencies with `supported-targets` specified.
- This is the first step towards a target aware `cargo`, which may increase `cargo`'s complexity, and bring more feature requests along these lines.

# Rationale and alternatives

## Format

The list of strings format was chosen because of its simplicity and expressiveness. Other formats can also be considered:

- Using the `[target]` table, for example:

```toml
[target.'cfg(target_os = "linux")']
supported = true

```

If the list of supported targets is long (should it ever be?), then the `Cargo.toml` file becomes very verbose as well.
- A `[suppported]` table, with `arch = ["<arch>", ...]`, `os = ["<os>", ...]`, `target = ["<target>", ...]`, etc. This is more verbose, complex to implement, learn, and remember. It is also not obvious how `not` and `all` could be represented in this format. For example:

```toml
[supported]
os = ["linux", "macos"]
arch = ["x86_64"]

```

## Naming

Some other names for this field can be considered:

- `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).
- `targets`. As in "this package _targets_ ...". Pro: Concise. Con: Ambiguous, and could be confused with the `target` table.

## Package scope vs. cargo-target scope

The `supported-targets` field is placed at the package level, and not at the cargo-target level (i.e., under, `[lib]`, `[[bin]]`, etc.). A rationale is given for why the cargo-target level is not used.

Dependencies are resolved at the package level, so even if one would have cargo-targets with different `supported-targets`, the dependencies would still be available to all cargo-targets. So, either cargo-targets would have access to dependencies that they cannot use, or all dependencies would need to support the union of all `supported-targets` of all cargo-targets.

Examples, tests and benchmarks also have access to the package's library and binaries, so they must have the same set of `supported-targets`.

It is possible to allow cargo-targets to further restrict the `supported-targets` of the package, but this is left as a [future possibility](#future-possibilities).

See also: using a package vs. using a workspace.

## Not using `cfg`

Using `cfg` complicates the implementation (and thus maintenance), and may require a substantial amount of calls to `rustc` to check target-`cfg` compatibility. Some alternatives are discussed here along with their drawbacks.

### Using wildcards

Instead of using `cfg` specifications, one could use wildcards (e.g., `x86_64-*-linux-*`). This is much simpler to implement, target-triples are syntactically checked for a match instead of solving set relations for `cfg`. However, this is not as expressive as `cfg`, and does not correctly represent the semantics of target triples. For example, supporting `target_family = "unix"` would require an annoyingly long list of wildcard patterns. Things like `target_pointer_width = "32"` are even harder to represent, and things like `target_feature = "avx"` are basically not representable. Also, this is new syntax not currently used by cargo.

### Allowing only target triples

This is an even stricter version of the above. Being even simpler to implement, this alternative may not be expressive enough for the common use case. Packages rarely support specific target triples, rather they support/require specific target attributes. What would likely happen is that packages would copy and paste the target triple list matching their requirements from somewhere or someone else. Every time a new target with the same attribute is added, the whole ecosystem would have to be updated.

# Prior art

Has this feature been seen anywhere else before?

# Unresolved questions

- What if one wants to exclude a single target-triple? Groups can be excluded with `cfg(not(..))`, but there is currently no way of excluding specific targets (Would anyone ever require this?).
- Some crates will inevitably have target requirements that are too strict, how do we make users bypass the error (probably with some `--ignore-**` flag)? Do we want to allow this?
- Should we solve for this during dependency version resolution? (the current rationale is that we do not want targets to affect package version resolution).

# Future possibilities

## Restrict build dependencies

Currently, a build script can have any dependency. A problem can arise if a crate's build script depends on a package that does not support `target_os = "windows"` for example. With this RFC, it would be possible to only allow dependencies supporting all targets in build scripts.

## Lint against useless target-specific tables

If a package has:

```toml
[package]
name = "example"
# ...
supported-targets = ['cfg(target_os = "linux")']

[target.'cfg(target_os = "windows")'.dependencies]
# ...

```

A lint could be added to highlight the fact that the `[target]` table is useless.

## More `cfg` relations

Consider the following scenario:

```toml
[package]
name = "bar"
supported-targets = ['cfg(target_family = "unix")']
# ...

```

```toml
[package]
name = "foo"
supported-targets = ['cfg(target_os = "macos")']

[dependencies]
bar = "0.1.0"

```

With the RFC implemented, a build error would be raised when compiling `foo`, because `cargo` does not understand that `target_os = "macos"` is a subset of `target_family = "unix"`.

To go around this issue, `foo` would need to use:

```toml
[package]
name = "foo"
supported-targets = ['cfg(all(target_family = "unix", target_os = "macos"))']

[dependencies]
bar = "0.1.0"

```

To improve usability, two extra relations can be defined:

- `cfg(target_os = "windows")` ⊆ `cfg(target_family = "windows")`.
- `cfg(target_os = <unix-os>)` ⊆ `cfg(target_family = "unix")`, where `<unix-os>` 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. This would make the first example compile.

_Note:_ The contrapositive of these relations is also true.

Also, `target_family` is currently defined as not having mutually exclusive elements. This is because `target_family = "wasm"` is not mutually exclusive with other target families. But, `target_family = "unix"` could be defined as mutually exclusive with `target_family = "windows"` to increase usability.

## Misc

- Have `cargo add` check the `supported-targets` before adding a dependency.
- Make this process part of the resolver.
- Show which targets are supported on `docs.rs`.
- Have search filters on `crates.io` for crates with support for specific targets.
- Also add this field at the cargo-target level.

---

<div class="post-metadata">

**Author:** ![jhpratt](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/jhpratt/32/11640_2.png) [@jhpratt](https://internals.rust-lang.org/u/jhpratt)\
**Post date:** [December 6, 2024, 2:19am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/2 "2024-12-06T02:19:19Z")

</div>

Targets can and have been renamed (let alone removed). How do you propose this is handled?

---

<div class="post-metadata">

**Author:** ![epage](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/epage/32/3171_2.png) [@epage](https://internals.rust-lang.org/u/epage)\
**Post date:** [December 6, 2024, 2:39am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/3 "2024-12-06T02:39:25Z")

</div>

It would help if this

- gave a motivation and justified the design around the motivation
- contrasted itself with the designs I gave in those issues for why different decisions were made

---

<div class="post-metadata">

**Author:** ![djc](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/djc/32/1592_2.png) [@djc](https://internals.rust-lang.org/u/djc)\
**Post date:** [December 6, 2024, 9:32am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/4 "2024-12-06T09:32:56Z")

</div>

Thanks for working on this!

Should the `package` be able to set a default for all of its targets, to avoid unnecessary repetition?

A little bikeshedding: I don't love the `target-requirements` name, which feels a little indirect. Maybe `supported-targets`?

Is there any interaction with the resolver? IMO it would be nice if `Cargo.lock` does not end up containing dependencies for unsupported targets.

Should there be a CLI interface for ignoring the set of currently supported targets, to enable the use case of someone trying to add support for another (set of) target(s)?

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [December 6, 2024, 12:25pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/5 "2024-12-06T12:25:54Z")

</div>

The way this is framed, `target-requirements` is effectively a top-level `any`. This isn't entirely obvious, though, and it'd be easy for someone to think it was a top-level `all` instead.

---

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 7, 2024, 2:17am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/6 "2024-12-07T02:17:37Z")

</div>

This is a good one which I had not thought about.

So if I understand correctly this is only an issue when targets are explicitly named, not when `cfg(..)` is used.

I ran some quick tests to see how `[target.non-existent-target.dependencies]` tables are handled, and it seems like they are ignored without a warning.

My first idea was that we could have a warning against this sort of thing when the "unknown" target comes from the packages' supported target, and we silently ignore when the unknown specification lives in a dependency.

However, I could see why we may want to ignore unknown targets without warning. In a sense it does not pose a problem because we are doing an "or" on all entries, so if the target "never matches," then it is as if it was never there (in set theory `A ∪ {} = A`). Also, say if someone writes a package for a specific board, and for a specific custom target, then they would likely want to be able to specify only this "unknown" custom target.

I will add this to the more fleshed out draft I am working on.

---

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 7, 2024, 2:36am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/7 "2024-12-07T02:36:04Z")

</div>

> Should the `package` be able to set a default for all of its targets, to avoid unnecessary repetition?

I was trying to see if `cargo` does this for other fields, and I was not able to find any instance, which I why I was reluctant to add this in. I do not have a strong opinion on this idea, and I will add this to my second draft to have opinions of others.

> A little bikeshedding: I don't love the `target-requirements` name, which feels a little indirect. Maybe `supported-targets`?

Soon after I posted this draft I realized how much I prefered `supported-targets`, it also emphasizes that the list is an or/union over its elements, not an and/intersection. This will be the identifier used in my next draft.

> Is there any interaction with the resolver? IMO it would be nice if `Cargo.lock` does not end up containing dependencies for unsupported targets.

As [this comment](https://github.com/rust-lang/cargo/issues/6179#issuecomment-2452268252) says, I think we should focus on having the feature well defined at first, and then we can consider `Cargo.lock` later as an extension to this RFC. But this is definitely a feature people seem to want.

> Should there be a CLI interface for ignoring the set of currently supported targets, to enable the use case of someone trying to add support for another (set of) target(s)?

I'm not sure I fully understand the extent of your question, but I think it is important for users to be able to bypass the `supported-targets` (hence why it is only a lint) because people may get these wrong.

---

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 7, 2024, 2:37am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/8 "2024-12-07T02:37:21Z")

</div>

Do you think `supported-targets` would be a better choice to indicate that the list is a "union"?

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [December 7, 2024, 4:40am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/9 "2024-12-07T04:40:39Z")

</div>

Yeah, making "targets" plural seems like it'd more obviously convey that each one is independent.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [December 7, 2024, 4:38pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/10 "2024-12-07T16:38:41Z")

</div>

> [@epage](#):
>
> It would help if this [...] contrasted itself with the designs I gave in those issues for why different decisions were made

I skimmed those issues and it was not clear to me what these "different decisions" you're talking about even _were_. Perhaps @carloskiki has a better sense of them but I think it would be helpful if you would lay out what _you_ perceive as the differences between this proposal and any conclusions reached in the issues cited.

Having said that, I _did_ notice that allowing `cfg` expressions in `target-requirements` appears to be new in this proposal, and so I want to speak up specifically in favor of that. It is very common to have programs that are specific to one operating system but don't care about any other target property. For example, right now I'm working on a couple of crates whose entire purpose is to make use of Linux-kernel-specific functions, but they have no CPU-specific requirements. For such crates, `target-requirements = ['cfg(target_os = "linux")']` would be easier to write and easier to verify than enumerating every `*-*-linux*` target tuple. Maybe even more important, _it doesn't need to be updated_ when Linux and/or Rust add support for another CPU architecture. It only needs to change if the program itself changes, e.g. by gaining support for another operating system.

Programs specific to one CPU architecture (or a handful of them), but not to the combination of a CPU and OS, are less common, but not rare, and a similar argument would apply.

---

<div class="post-metadata">

**Author:** ![Vorpal](https://avatars.discourse-cdn.com/v4/letter/v/aca169/32.png) [@Vorpal](https://internals.rust-lang.org/u/Vorpal)\
**Post date:** [December 7, 2024, 5:54pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/11 "2024-12-07T17:54:53Z")

</div>

What should happen if a crate declares itself Linux specific (due to a libc API wrapping a Linux specific syscall for example) but then another OS implements support for that same syscall and libc API? Ideally the crate should be updated to add thst other OS to the allowlist. But what happens if the crate is no longer actively maintained (as happens often)?

---

<div class="post-metadata">

**Author:** ![josh](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/josh/32/5934_2.png) [@josh](https://internals.rust-lang.org/u/josh)\
**Post date:** [December 7, 2024, 8:50pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/12 "2024-12-07T20:50:44Z")

</div>

Mechanisms like `supported-targets` are not just about what _works_, they're also about what's _supported_. If a non-Linux OS adds support for a syscall, that doesn't mean the crate author is prepared to support that OS going forward, either because they don't want to test on that OS, or because they may expect to make future changes that adopt new Linux-specific mechanisms and don't intend to limit themselves to what that other OS supports.

That said, for the benefit of old unsupported crates, I expect we'd probably have some kind of `--force-something` option, the same way we have such an option to override a crate's specified MSRV.

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [December 8, 2024, 5:41pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/13 "2024-12-08T17:41:55Z")

</div>

Giving the information to Cargo is still beneficial for earlier, clearer, and more consistent error messaging, but if this doesn't interact with package resolution at all, then it's worth noting that it's roughly equivalent to today writing

```rust
#[cfg(not(any(…)))]
compile_error!("unsupported target cfg");

```

which also has the added benefit of allowing the crate to say _why_ the cfg is unsupported instead of just _that_ the cfg is unsupported. (Only if they put in that effort, of course.)

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [December 8, 2024, 5:50pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/14 "2024-12-08T17:50:29Z")

</div>

Yeah, the #1 reason why I'd personally want this over a `compile_error!` for those Linux-specific crates I was talking about, is if it would insulate me from problems like "[rustix depends transitively on multiple versions of windows-sys · Issue #1233 · bytecodealliance/rustix · GitHub](https://github.com/bytecodealliance/rustix/issues/1233) and this causes clippy's `multiple-crate-versions` lint to fire on _my_ crate (which uses rustix) even though I know that my crate will never be used on Windows."

It seems to me that there are "ecosystem" reasons to want this feature, though. For instance, structured, declarative metadata about which crates can be used on which targets could be valuable for searching and tagging on `crates.io` and `lib.rs`.

---

<div class="post-metadata">

**Author:** ![RalfJung](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/ralfjung/32/2415_2.png) [@RalfJung](https://internals.rust-lang.org/u/RalfJung)\
**Post date:** [December 9, 2024, 8:43am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/15 "2024-12-09T08:43:53Z")

</div>

If we ever get targets which we do _not_ expect crates to support by default since they are sufficiently non-standard (e.g., CHERI), this may also be a good way to explicitly opt-in to support such targets.

---

<div class="post-metadata">

**Author:** ![newpavlov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/newpavlov/32/3290_2.png) [@newpavlov](https://internals.rust-lang.org/u/newpavlov)\
**Post date:** [December 9, 2024, 1:27pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/16 "2024-12-09T13:27:18Z")

</div>

Somewhat relevant proposal (see the "Integration with Cargo.toml" section):

> [@\[Pre-Pre-RFC\] Target restriction contexts](https://internals.rust-lang.org/t/7163):
>
> The following text is braindump-like description of how I would like to see Rust work with various target options. (target\_arch, target\_feature, target\_os and [others](https://doc.rust-lang.org/reference/attributes.html#conditional-compilation)) This approach aims to provide stronger compile time guarantees compared to current cfg setups, remove most of the unsafe in the [stdsimd](https://github.com/rust-lang-nursery/stdsimd) crate and integrate target parameters closer with Cargo.toml. Also it could be useful for improving Rust [portability story](https://internals.rust-lang.org/t/a-vision-for-portability-in-rust/6719). While [RFC 2045](https://github.com/rust-lang/rfcs/pull/2045) is an important step towards [stabilization](https://github.com/rust-lang/rust/issues/48556) of SIMD, I don…

---

<div class="post-metadata">

**Author:** ![epage](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/epage/32/3171_2.png) [@epage](https://internals.rust-lang.org/u/epage)\
**Post date:** [December 9, 2024, 6:11pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/17 "2024-12-09T18:11:27Z")

</div>

See my posts

- [Allow specifying a set of supported target platforms in Cargo.toml · Issue #6179 · rust-lang/cargo · GitHub](https://github.com/rust-lang/cargo/issues/6179#issuecomment-1736090525)
- [Allow specifying a set of supported target platforms in Cargo.toml · Issue #6179 · rust-lang/cargo · GitHub](https://github.com/rust-lang/cargo/issues/6179#issuecomment-2452268252)
- [https://github.com/rust-lang/cargo/issues/9208#issuecomment-1769772176](https://github.com/rust-lang/cargo/issues/9208#issuecomment-1769772176)

Items include

- Naming: Though this acknowledges my proposed name of `required-targets`, it does say why it went with a name that is inconsistent with the existing manifest format
- Semantics: I specified them to be like `required-features`, skip a build-target if its an unsupported platform-target, instead of being a deny-by-default lint
  - At least for myself, I would be interested in auto-enabling a build-target's `required-features` if it was explicitly requested (for some definition of explicit) and would see us doing similar here if `--target` isn't specified

- `cfg`: I excluded it for now for an MVP because supporting it in `package.required-targets` adds a lot of complexity to handle it for the `Cargo.lock` file. Likely, we'll have to decide between `cfg` and having smaller lockfiles / vendored results
  - No where was globbing a consideration. so what little comparison is made to my proposal is not even for my proposal
  - Why an array and why `any` rather than `all? because `cfg` wasn't going to be supported in the MVP and this allowed for specifying multiple platform tuples

- Validating a build-target or package is a subset of dependencies: This also hinges on not supporting `cfg`. We can check `required-targets` against `requires-targets` easily if they are platform tuples. If our `required-targets` is a platform tuple, we can enumerate all of them with rustc to check if `target.cfg()` matches. However, if we're doing set logic with `cfg` against `cfg`, I have questions. rustc only tells us what `cfg` values exist for a given platform-target. We do not know the relationship to be able to do things like `target_os = "windows" ⊆ target_family = "windows"`. We could say that the same set conditions must exist but then we'll have to do the set logic to handle `any`, `all`, and `not` which I worry will be a lot of complexity.

---

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 15, 2024, 2:53am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/18 "2024-12-15T02:53:24Z")

</div>

I have updated (75% rewritten) the Pre-RFC. I edited the top post with the new version, while the first draft is available [here](https://github.com/carloskiki/rfcs/blob/06a31069cc1bb8cd50f9100d523409e21369bb2e/text/0000-cargo-supported-targets.md).

---

<div class="post-metadata">

**Author:** ![carloskiki](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/carloskiki/32/12774_2.png) [@carloskiki](https://internals.rust-lang.org/u/carloskiki)\
**Post date:** [December 15, 2024, 2:58am UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/19 "2024-12-15T02:58:56Z")

</div>

I believe most of your bullet points are now covered in this new draft.

Make sure to correct me if I am missing anything!

---

<div class="post-metadata">

**Author:** ![epage](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/epage/32/3171_2.png) [@epage](https://internals.rust-lang.org/u/epage)\
**Post date:** [December 16, 2024, 2:08pm UTC](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979/20 "2024-12-16T14:08:45Z")

</div>

> [@carloskiki](#):
>
> - "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.

Would be good to link to the [glossary](https://doc.rust-lang.org/cargo/appendix/glossary.html#target) and either use the terms from there (cargo-target, target triple) or my own spin which is build-target and platform-target.

> [@carloskiki](#):
>
> Compatibility with dependencies

This never says what happens. Is it an error? A warning? A lint?

> [@carloskiki](#):
>
> 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.

Since this gets into terminology, there are "crates" which are build units and there are `.crate` files which are published.

This can matter for more than just published packages, like git dependencies or even path dependencies in a monorepo.

Also, publish-time is too late to notify people of a problem as that is the very tail end of a release process. Feedback needs to happen during development. Also, irreversible actions may have already been taken as part of the release process.

> [@carloskiki](#):
>
> ## 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.

This seems like the place to specify this behavior or at least provide guidance on when each is chosen. Leaving it this vague prevents meaningful discussion to work out the design.

> [@carloskiki](#):
>
> Detecting unused dependencies

Isn't this the same check as "Compatibility with dependencies" and one says it should lint as incompatible while this says it is unused?

> [@carloskiki](#):
>
> `required-targets`. Although it matches with `required-features`, `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).

I don't think that is enough of a difference to justify a different name.

> [@carloskiki](#):
>
> ## Not using `cfg`
> 
> Using `cfg` complicates the implementation, and may require a substantial amount of calls to `rustc` to check target-`cfg` compatibility. Some alternatives are discussed here along with their drawbacks.

This leaves off a big problem with `cfg`: if we support this in `package` _and_ want to rely on this for trimming the `Cargo.lock` file, things may get messy.

> [@carloskiki](#):
>
> How do we make users bypass the lint (probably with some `--ignore-**` flag)?

imo this is my problem with calling it a lint: Cargo only offers static lint control and I don't see it making sense to set this _and_ override it with static lint control.

It can just be an error instead, even if there is a flag for overriding it. The question is, do we need to override it?

> [@carloskiki](#):
>
> Should we solve for this during dependency version resolution? (the current rationale is that we do not want targets to affect package version resolution). In the future, this could be implemented in `pubgrub` using [constraints](https://github.com/pubgrub-rs/pubgrub/issues/120).

A big problem with dependency resolution is you'd have to set this for all cargo-targets.

Something this leaves out from the Alternatives is why not also support this in `package` which would apply to everything and could be easier to use in most cases and be to apply to dependency resolution.

[Next page](https://internals.rust-lang.org/t/pre-rfc-allow-packages-to-specify-a-set-of-supported-targets/21979.md?page=2)
