Skip to content

Commit e0c33af

Browse files
committed
Cargo target features
1 parent 3fe3c65 commit e0c33af

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

text/0000-cargo-target-features.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
- Feature Name: `cargo_target_features`
2+
- Start Date: 2023-01-20
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Tracking Issue: [rust-lang/cargo#0000](https://github.com/rust-lang/cargo/issues/0000)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
This adds a new `enable-features` field to [Cargo Targets](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#configuring-a-target) which forces a [feature](https://doc.rust-lang.org/cargo/reference/features.html) to be enabled if the target is being built.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
There are several situations where a non-library target must have a particular feature enabled for it to work correctly.
15+
Although it is possible to manually enable these features on the command-line, this can make it awkward to use.
16+
This RFC adds a mechanism to make it easier to work with these targets.
17+
18+
Some example use cases are:
19+
20+
1. Binaries that need additional dependencies like command-line processing libraries, or logging functionality.
21+
2. Examples illustrating how to use a library with a specific feature enabled.
22+
3. Benchmarks which require a separate benchmarking library, but you don't want to pay the cost of building that library when not running benchmarks.
23+
24+
# Guide-level explanation
25+
[guide-level-explanation]: #guide-level-explanation
26+
27+
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.
28+
For example:
29+
30+
```toml
31+
[package]
32+
name = "myproject"
33+
version = "0.1.0"
34+
edition = "2021"
35+
36+
[[example]]
37+
name = "fetch-http"
38+
enable-features = ["networking"]
39+
40+
[[bin]]
41+
name = "myproject"
42+
enable-features = ["dep:clap"]
43+
44+
[dependencies]
45+
clap = { version = "4.0.26", optional = true }
46+
47+
[features]
48+
networking = []
49+
```
50+
51+
When using `myproject` as a library dependency, by default the `networking` feature and `clap` will not be enabled.
52+
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.
53+
Similarly, `cargo build --example fetch-http` will enable the `networking` feature so that the example can be built.
54+
55+
This field can be specified for any Cargo Target except the `[lib]` target.
56+
57+
> **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.
58+
> The features and dependencies will be enabled for all targets.
59+
60+
# Implementation Details
61+
62+
## Feature resolver and selection
63+
64+
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.
65+
These additional features will be combined with any features selected on the command-line before calling the resolver.
66+
67+
Unfortunately this selection of targets is quite complex.
68+
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).
69+
70+
## Hidden `dep:` dependencies
71+
72+
`enable-features` should allow the use of `dep:` to enable an optional dependency.
73+
The use of `dep:` should behave the same as-if it was specified in the `[features]` table.
74+
That is, it should suppress the creation of the implicit feature of the same name.
75+
This may require some challenging changes to the way the features table is handled,
76+
as the corresponding code has no knowledge about the manifest.
77+
78+
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.
79+
Currently this is implicitly done by assuming `--all-features` is passed.
80+
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.
81+
This may require some changes to differentiate between the use of explicit and implicit `--all-features`.
82+
83+
## Artifact dependencies
84+
85+
When depending on a binary [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies), Cargo should enable any `enable-features` features of that binary.
86+
This may require plumbing that information into the index so that the dependency and feature resolvers can determine that field is being set.
87+
This RFC does not propose a specific plan here, but that it should eventually be supported.
88+
89+
## Relationship with `required-features`
90+
91+
[`required-features`](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-required-features-field) is a very similar field to `enable-features`, but works in a different way.
92+
It essentially says that the target will not be built if the feature is not already enabled.
93+
94+
There are legitimate use cases where `required-features` is more appropriate than `enable-features`.
95+
For example, there may be examples or tests that only work with a specific feature enabled.
96+
However, the project may not want to make those examples or tests automatically build when running `cargo test`.
97+
The feature may be expensive to build, or may require specific components on the system to be installed.
98+
In these situations, `required-features` may be the appropriate way to conditionally include a target.
99+
100+
Documentation will need to emphasize the difference between these seemingly similar options.
101+
102+
## Other cargo command behavior
103+
104+
[`cargo metadata`](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html) and [`cargo tree`](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) will behave as-if they are ignoring the `enable-features` fields.
105+
These commands do not have a concept of targets being built, nor do they have any options for selecting them.
106+
107+
When using those commands with `--all-features`, any hidden `dep:` dependencies that are only enabled via `enable-features` will be included.
108+
109+
# Drawbacks
110+
[drawbacks]: #drawbacks
111+
112+
* This adds additional complexity to `Cargo.toml` potentially making it more difficult to understand.
113+
* Users may be easily confused between the difference of `enable-features` and `required-features`.
114+
Hopefully clear and explicit documentation may help avoid some of that confusion.
115+
The error messages with `required-features` may also be extended to mention `enable-features` as an alternative (as well as other situations like `cargo install` failing to find a binary).
116+
* It may not be clear that features are unified across all targets.
117+
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.
118+
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.
119+
* This may not have the same clarity as explicit dependencies as proposed in [RFC 2887](https://github.com/rust-lang/rfcs/pull/2887).
120+
* 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).
121+
* There may be confusion about the interaction with `--no-default-features`.
122+
`--no-default-features` will continue to only affect the `default` feature.
123+
Features enabled by `enable-features` will be enabled even with `--no-default-features`.
124+
This may be confusing and should be mentioned in the documentation.
125+
* 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 as `test=false`.
126+
However, not all of these options may be satisfying.
127+
* This may add significant complexity to Cargo's implementation.
128+
129+
# Rationale and alternatives
130+
[rationale-and-alternatives]: #rationale-and-alternatives
131+
132+
## Change the `required-features` behavior
133+
134+
* `required-features` could be changed to behave the same as `enable-features` described in this RFC (possibly over an Edition).
135+
However, as outlined in the [Relationship with `required-features`](#relationship-with-required-features) section, there are some use cases where the present behavior of `required-features` is desirable.
136+
This could also lead to a breaking change for some projects if it started building targets that were previously not included.
137+
* Instead of adding a separate field that lists features, a `force-enable-features = true` field could be added to change the behavior of `required-features` to have the behavior explained in this RFC.
138+
That might be less confusing, but would prevent the ability to have both behaviors at the same time.
139+
* Only the situation where `required-features` generates an error could be changed to implicitly enable the missing features.
140+
This would likely make `required-features` less annoying to work with, but doesn't help for use cases like running `cargo test` where you have specific tests or examples that you want to be automatically included (where `required-features` simply makes them silently excluded).
141+
* A new CLI argument could be added to change the behavior of `required-features` to behave the same as `enable-features`, avoiding the need to add `enable-features`.
142+
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.
143+
144+
## Alternate workflows
145+
146+
* Instead of using `enable-features`, developers can be diligent in passing the appropriate `--features` options on the command-line when building their projects, possibly using `required-features` to ensure they only get built in the correct scenarios.
147+
The intent of this RFC is to make that process easier and more seamless.
148+
* Users can set up [aliases](https://doc.rust-lang.org/cargo/reference/config.html#alias) which pass in the feature flags they want to enable.
149+
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`.
150+
* Developers can organize their project in a [Cargo Workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html) instead of using multiple targets within a single package.
151+
This allows customizing dependencies and other settings within each package.
152+
Workspaces can add some more overhead for managing multiple packages, but offer a lot more flexibility.
153+
Instead of implementing this RFC, we could put more work into making workspaces easier to use, which could benefit a larger audience.
154+
However, it is not clear if it is feasible to make workspaces work as effortlessly compared to targets.
155+
Also, there is likely more work involved to get workspaces on the same level.
156+
157+
## Alternate designs
158+
159+
* [RFC 2887](https://github.com/rust-lang/rfcs/pull/2887) proposes being able to add dependency tables directly in a target definition.
160+
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.
161+
* [RFC 3020](https://github.com/rust-lang/rfcs/pull/3020) proposes an enhancement similar to this RFC.
162+
163+
## Other alternative considerations
164+
165+
* [cargo#1982](https://github.com/rust-lang/cargo/issues/1982) is the primary issue requesting the ability to set per-target dependencies, and contains some discussion of the desired use cases.
166+
* Other names may be considered for the field `enable-features`, such as `forced-features`, `force-enable-features`, etc.
167+
168+
# Prior art
169+
[prior-art]: #prior-art
170+
171+
> NOTE: These could use vetting by people more familiar with these tools.
172+
> More examples are welcome.
173+
174+
* Swift has the ability to specify dependencies within a target definition (see [SE-0226](https://github.com/apple/swift-evolution/blob/main/proposals/0226-package-manager-target-based-dep-resolution.md) and [Target Dependency](https://docs.swift.org/package-manager/PackageDescription/PackageDescription.html#target-dependency)).
175+
These can also specify explicit dependencies on artifacts of other targets within the package.
176+
* Go can specify dependencies directly in the source of a module.
177+
* 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.

0 commit comments

Comments
 (0)