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