- Feature Name:
cargo_target_features
- Start Date: 2023-01-20
- RFC PR: rust-lang/rfcs#3374
- Tracking Issue: rust-lang/cargo#0000
This adds a new enable-features
field to Cargo Targets which forces a feature to be enabled if the target is being built.
There are several situations where a non-library target must have a particular feature enabled for it to work correctly. Although it is possible to manually enable these features on the command-line, this can make it awkward to use. This RFC adds a mechanism to make it easier to work with these targets.
Some example use cases are:
- Binaries that need additional dependencies like command-line processing libraries, or logging functionality.
- Examples illustrating how to use a library with a specific feature enabled.
- Benchmarks which require a separate benchmarking library, but you don't want to pay the cost of building that library when not running benchmarks.
The enable-features
field can be added to a target in Cargo.toml
to specify features or dependencies that will be automatically enabled whenever the target is being built.
For example:
[package]
name = "myproject"
version = "0.1.0"
edition = "2021"
[[example]]
name = "fetch-http"
enable-features = ["networking"]
[[bin]]
name = "myproject"
enable-features = ["dep:clap"]
[dependencies]
clap = { version = "4.0.26", optional = true }
[features]
networking = []
When using myproject
as a library dependency, by default the networking
feature and clap
will not be enabled.
When building the binary as in cargo install myproject
or cargo build
, the clap
dependency will automatically be enabled allowing the binary to be built correctly.
Similarly, cargo build --example fetch-http
will enable the networking
feature so that the example can be built.
This field can be specified for any Cargo Target except the [lib]
target.
Note: Because features and dependencies are package-wide, using
enable-features
does not narrow the scope of the features or dependencies to the specific target. The features and dependencies will be enabled for all targets that have been asked to be built.
Before doing dependency and feature resolution, Cargo will need to determine which features are being force-enabled by the targets selected on the command-line. These additional features will be combined with any features selected on the command-line before calling the resolver.
Unfortunately this selection of targets is quite complex. This may require some potentially invasive refactoring, as Cargo currently chooses the targets to select after resolution is done (including a separate phase for implicit binaries used for integration tests).
Hidden dep:
dependencies
enable-features
should allow the use of dep:
to enable an optional dependency.
The use of dep:
should behave the same as-if it was specified in the [features]
table.
That is, it should suppress the creation of the implicit feature of the same name.
This may require some challenging changes to the way the features table is handled,
as the corresponding code has no knowledge about the manifest.
For the purpose of generating the lock file, the dependency resolver will need to assume that all optional dependencies of the workspace packages are enabled.
Currently this is implicitly done by assuming --all-features
is passed.
However, if a enable-features
field specifies a dep:
dependency, and nothing else enables that dependency, then the resolver will not be able to see that it is enabled.
This may require some changes to differentiate between the use of explicit and implicit --all-features
.
When depending on a binary artifact dependency, Cargo should enable any enable-features
features of that binary.
This may require plumbing that information into the index so that the dependency and feature resolvers can determine that field is being set.
This RFC does not propose a specific plan here, but that it should eventually be supported.
required-features
is a very similar field to enable-features
, but works in a different way.
It essentially says that the target will not be built if the feature is not already enabled.
There are legitimate use cases where required-features
is more appropriate than enable-features
.
For example, there may be examples or tests that only work with a specific feature enabled.
However, the project may not want to make those examples or tests automatically build when running cargo test
.
The feature may be expensive to build, or may require specific components on the system to be installed.
In these situations, required-features
may be the appropriate way to conditionally include a target.
Documentation will need to emphasize the difference between these seemingly similar options.
There may be situations where an enable-features
field may enable features that were listed in required-features
of another target.
The resolver should iterate, enabling features unlocked by newly added targets now satisfying a required-features
field.
For example:
[[bin]]
name = "foo"
enable-features = ["a"]
[[bin]]
name = "bar"
required-features = ["a"]
enable-features = ["b"]
[[bin]]
name = "baz"
required-features = ["b"]
If this is built with no explicit feature flags, then foo
will enable feature a
.
Enabling feature a
now satisfies bar
's required features, and thus it is added to the set of enabled targets, which in turn enables feature b
.
Now that feature b
is enabled, baz
's required features are also now satisfied, so it is also enabled.
Thus, all three targets will be built, and features a
and b
are enabled.
This will likely require iterating over the targets until no additional targets can be added.
cargo metadata
and cargo tree
will behave as-if they are ignoring the enable-features
fields.
These commands do not have a concept of targets being built, nor do they have any options for selecting them.
When using those commands with --all-features
, any hidden dep:
dependencies that are only enabled via enable-features
will be included.
- This adds additional complexity to
Cargo.toml
potentially making it more difficult to understand. - Users may be easily confused between the difference of
enable-features
andrequired-features
. Hopefully clear and explicit documentation may help avoid some of that confusion. The error messages withrequired-features
may also be extended to mentionenable-features
as an alternative (as well as other situations likecargo install
failing to find a binary). - It may not be clear that features are unified across all targets.
This may particularly come into play where it may not be clear that an optional dependency suddenly becomes available to all the other targets when the
enable-features
causes it to be included. A similar situation arises with dev-dependencies, where a user may get confused when referencing a dev-dependency outside of a#[cfg(test)]
block or module, which causes an error. - This may not have the same clarity as explicit dependencies as proposed in RFC 2887.
- There may be increased confusion and complexity regarding the interaction of various cargo commands and this feature (such as
cargo tree
mentioned above which has no concept of cargo target selection). - There may be confusion about the interaction with
--no-default-features
.--no-default-features
will continue to only affect thedefault
feature. Features enabled byenable-features
will be enabled even with--no-default-features
. This may be confusing and should be mentioned in the documentation.- There are some alternatives to avoiding a target from being built, such as not including it in the CLI options, using
required-features
instead, or disabling it with fields such astest=false
. However, not all of these options may be satisfying.
- There are some alternatives to avoiding a target from being built, such as not including it in the CLI options, using
- This may add significant complexity to Cargo's implementation.
required-features
could be changed to behave the same asenable-features
described in this RFC (possibly over an Edition). However, as outlined in the Relationship withrequired-features
section, there are some use cases where the present behavior ofrequired-features
is desirable. This could also lead to a breaking change for some projects if it started building targets that were previously not included.- Instead of adding a separate field that lists features, a
force-enable-features = true
field could be added to change the behavior ofrequired-features
to have the behavior explained in this RFC. That might be less confusing, but would prevent the ability to have both behaviors at the same time. It would also require entering two lines (instead of one) inCargo.toml
to get the behavior that most users will likely want. - Only the situation where
required-features
generates an error could be changed to implicitly enable the missing features. This would likely makerequired-features
less annoying to work with, but doesn't help for use cases like runningcargo test
where you have specific tests or examples that you want to be automatically included (whererequired-features
simply makes them silently excluded). - A new CLI argument could be added to change the behavior of
required-features
to behave the same asenable-features
, avoiding the need to addenable-features
. This is viable, but the goal of this RFC is to make it as easy as possible to work with the cargo targets without requiring additional CLI arguments.
- Instead of using
enable-features
, developers can be diligent in passing the appropriate--features
options on the command-line when building their projects, possibly usingrequired-features
to ensure they only get built in the correct scenarios. The intent of this RFC is to make that process easier and more seamless. The current UX for usingrequired-features
can be confusing when the required features are not specified. For example,cargo install
fails to inform why it fails (see #11617). - Users can set up aliases which pass in the feature flags they want to enable.
This can help with a development workflow, but requires more documentation and education, and doesn't help with some commands like a remote
cargo install
. - Developers can organize their project in a Cargo Workspace instead of using multiple targets within a single package. This allows customizing dependencies and other settings within each package. Workspaces can add some more overhead for managing multiple packages, but offer a lot more flexibility. Instead of implementing this RFC, we could put more work into making workspaces easier to use, which could benefit a larger audience. However, it is not clear if it is feasible to make workspaces work as effortlessly compared to targets. Also, there is likely more work involved to get workspaces on the same level.
- RFC 2887 proposes being able to add dependency tables directly in a target definition.
It is intended that this RFC using
enable-features
will hopefully be easier to implement, make it easier to reuse a dependency declaration across multiple targets, and allow working with features that are not related to dependencies. - RFC 3020 proposes an enhancement similar to this RFC.
- cargo#1982 is the primary issue requesting the ability to set per-target dependencies, and contains some discussion of the desired use cases.
- Other names may be considered for the field
enable-features
, such asforced-features
,force-enable-features
, etc. - A flag could be added to the target definition to indicate that default features should be disabled when the target is being built.
This would allow having different default features when running a command such as
cargo install
versus using the package's library as a dependency.
NOTE: These could use vetting by people more familiar with these tools. More examples are welcome.
- Swift has the ability to specify dependencies within a target definition (see SE-0226 and Target Dependency). These can also specify explicit dependencies on artifacts of other targets within the package.
- Go can specify dependencies directly in the source of a module.
- Many other tools do not have a similar concept as Cargo targets, or if they have something similar, they do not have a way to specify dependencies or other settings per target.