-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mutually exclusive features #2980
Comments
I just had this idea, but haven't thought it through yet: To get mutually exclusive features couldn't you just add some piece of code to the crate that is gated on the features that should not be used together and make This could be considered an ugly workaround, but maybe it allows the greatest flexibility. |
@Boddlnagg Suppose the user is working on their project Right now the way features work is that |
Maybe allow specifying feature conflicts in Cargo.toml, such as: [dependencies.awesome]
version = "1.3.5"
features = ["!secure-password", "civet"] Which means this package depends on "awesome" but requires the "secure-password" feature to be disabled. Also, [features]
go-faster = []
secure-password = ["!go-faster", "bcrypt"] means that this package provides features "go-faster" and "secure-password". But if "secure-password" is enabled, "go-faster" must be disabled. |
I've run into this in practice before with the Your use case here seems like it's not well-served by features at all, honestly. cargo features as-implemented are allowed to be required by arbitrary crates in the dependency graph, so you'd have to have some way to completely override that. It's more similar to |
As there hasn't been any activity here in over 6 months I've marked this as stale and if no further activity happens for 7 days I will close it. I'm a bot so this may be in error! If this issue should remain open, could someone (the author, a team member, or any interested party) please comment to that effect? The team would be especially grateful if such a comment included details such as:
Thank you for contributing! (The cargo team is currently evaluating the use of Stale bot, and using #6035 as the tracking issue to gather feedback.) If you're reading this comment from the distant future, fear not if this was closed automatically. If you believe it's still an issue please leave a comment and a team member can reopen this issue. Opening a new issue is also acceptable! |
Yes.
Someone needs to come up with a good design for a better feature system that supports mutually exclusive features among other things.
Convince someone to work on such a design. |
What's wrong with the design suggested by @mat8913? #2980 (comment) It doesn't cover "among other things", but seems to address the issue while being flexible. (Some consideration would need to be given for the --all-features flag, but an error is seems sufficient) |
In inkwell, we currently support 9 different versions of LLVM. This is represented by feature flags which correspond to different versions of our dependency, the Mutually exclusive feature flags would allow us to deploy (also to crates.io) one single release where you specify a feature flag, and cargo builds a crate graph that is able to use different dependency versions depending on the flag. Some previous discussions:
The second example in particular can end up being pretty verbose I think. As mentioned previously, in inkwell we have 9 features that we use exclusively for different LLVM versions. So it would look something like this: [features]
llvm3-6 = ["!llvm3-7", "!llvm3-8", "!llvm3-9", ..., "!llvm8-0"]
llvm3-7 = ["!llvm3-6", "!llvm3-8", "!llvm3-9", ..., "!llvm8-0"]
...
llvm8-0 = ["!llvm3-6", "!llvm3-7", "!llvm3-8", ..., "!llvm7-0"] I suppose this could be simplified a bit, ie if 3-6 has 3-7 excluded, then 3-7 doesn't need to list 3-6 explicitly. But this seems to be too implicit and potentially a maintenance headache. It would be clearer to collect exclusions into named sets like so: [mutually_exclusive_features]
llvm_versions = ["llvm3-6", "llvm3-7", "llvm3-8", ..., "llvm8-0"] That way, we can keep verbosity down and the compiler can reference the set name in error messages. IE "llvm3-6 and llvm3-7 features cannot be combined as defined in the llvm_versions exclusion set" To reformat @mat8913's examples: [features]
go-faster = []
secure-password = ["bcrypt"]
[mutually_exclusive_features]
speed_or_security = ["go-faster", "secure-password"]
[dependencies.awesome]
version = "1.3.5"
features = ["civet"]
mutually_exclusive_features = ["secure-password"] Note: Instead of
Agreed. |
Another way of doing this is to provide "values" for features.
Features could default to true or 1. |
hello guys, I've been waiting this for more than one year, any progress? It's open at: Aug 9, 2016 ~ |
Still waiting on this, it would be very nice to have. :) |
…s will be `true` This fixes the bug that is `cfg!(feature = "production")` always will be evaluated as `true`. For example, the following code in `c_at_e_main` will always returns `"this is production"` even if we run `make build_debug -j RELEASE_CHANNEL=canary`. ```rust fn main() { let message = if cfg!(feature = "production") { "production" } else if cfg!(feature = "canary") { "canary" } else { "others" }; println!("this is {}", message); } ``` This bug is caused accidentally by our build system. We set `default` feature for `c_at_e_main` to run many rust toolchain that are not integrated for our build system easily. But our _release channel_ model is not mutually exclusive. [cargo does not support it](rust-lang/cargo#2980). So when we invoke `cargo build --feature canary`, then cargo will enables both of `production` and `canary` feature because cargo enables `default` feature set implicitly too. Therefore, he above code will be expanded to: ```rust fn main() { let message = if (true) { "production" } else if (true) { "canary" } else { "others" }; println!("This is {}", message); } ``` This causes the bug. This change set `--no-default-features` CLI flags to cargo. By this change, cargo will not enable `default` feature implicitly and the bug will be fixed. This change does not remove `default` feature from `c_at_e_main`'s Cargo.toml to still allow to invoke rust-analyzer without zero-configuration. Note ----- Can we set cargo's default CLI arguments without our build system? ============= No. [`.cargo/config.toml`](https://doc.rust-lang.org/cargo/reference/config.html) has no way to disable default features without passing CLI flags. Can we configure rust-analyzer to disable all default features? ========== Yes, we can config it by vscode's setting. But rust-analyzer does not have a way to editor agnostic settings. - rust-lang/rust-analyzer#11010 There are rust-project.json but it's for non-cargo based project. We would not like to use it. https://rust-analyzer.github.io/manual.html#non-cargo-based-projects
Discussion SummaryUse cases
Other discussions
Related prior art
ProposalsConflicting features[dependencies.awesome]
version = "1.3.5"
features = ["!secure-password", "civet"] Feature values[features]
llvm="9"
flate2="zlib"
winapi_family="desktop_app" Provides[features]
foo = []
bar = []
[features.foo]
provides = ["impl"]
[features.bar]
provides = ["impl"]
|
Did a recent brainstorming discussion on this. A key aspect we narrowed in on is that most of the use cases are really meant to be decided by the final binary and it is not ergonomic to bubble these up to the final binary. I've experienced this myself where We had the idea of "global features". Care abouts
High level
Questions
... I feel like there were more points but I'm not finding it in my notes. |
For vendoring/system libs choice, I think the use case deserves a first-class feature. This is partially solved by being able to override build scripts. The missing part is that build scripts may need to also generate code ( |
Keep in mind that currently feature values are my_feature = "dep:bytemuck" # unintended wrong use
my_feature = ["dep:bytemuck"] # what was intended And this would effectively be the same as passing rustc-cfg from a build script except maybe being more trustworthy? I think provides covers most current and future use cases assuming feature values would be: #[derive(Default)] // default is the current `[]`
struct FeatureDetails {
/// list of crate/feature dependencies
dependencies: Vec<FeatureValue>,
/// Name for mutual exclusion
provides: Option<InternedString>,
// ... future details ...
/// List of features and dependencies disabled when this feature is active
// disable: Vec<FeatureValue>,
} And constructor could take arguments currently passed to |
I've created a Pre-RFC for mutually-exclusive, global features. |
The codex32 test will more thoroughly exercise the algebra, since there we can correct up to 4 errors. The bech32 test on the other hand should work without an allocator (though to exercise this you need to manually edit fuzz/Cargo.toml to disable the alloc feature -- this is rust-lang/cargo#2980 which has been open for 10 years and counting..)
The codex32 test will more thoroughly exercise the algebra, since there we can correct up to 4 errors. The bech32 test on the other hand should work without an allocator (though to exercise this you need to manually edit fuzz/Cargo.toml to disable the alloc feature -- this is rust-lang/cargo#2980 which has been open for 10 years and counting..)
The codex32 test will more thoroughly exercise the algebra, since there we can correct up to 4 errors. The bech32 test on the other hand should work without an allocator (though to exercise this you need to manually edit fuzz/Cargo.toml to disable the alloc feature -- this is rust-lang/cargo#2980 which has been open for 10 years and counting..)
The codex32 test will more thoroughly exercise the algebra, since there we can correct up to 4 errors. The bech32 test on the other hand should work without an allocator (though to exercise this you need to manually edit fuzz/Cargo.toml to disable the alloc feature -- this is rust-lang/cargo#2980 which has been open for 10 years and counting..)
The codex32 test will more thoroughly exercise the algebra, since there we can correct up to 4 errors. The bech32 test on the other hand should work without an allocator (though to exercise this you need to manually edit fuzz/Cargo.toml to disable the alloc feature -- this is rust-lang/cargo#2980 which has been open for 10 years and counting..)
In Windows API there is the notion of a family and partitions. Your choice of family is basically which platform you are targeting and there is a choice between
WINAPI_FAMILY_DESKTOP_APP
WINAPI_FAMILY_PC_APP
WINAPI_FAMILY_PHONE_APP
WINAPI_FAMILY_SYSTEM
andWINAPI_FAMILY_SERVER
. Your choice of family in turn enables certain partitions which enable access to certain functionality. At the moment I cannot use cargo features for this because they are not mutually exclusive, so two downstream dependencies could specify different families which is completely wrong. Even if I use a build script to ensure only one such feature is selected, it would be a nightmare to ensure that every single downstream dependency chooses the same family, threading the choice of family feature through long chains of transitive dependencies that shouldn't have to care about that choice.What I think would work well here is some sort of feature selection that has to choose exactly one option from a closed set, and that choice is controlled by the root crate. All dependencies that are affected by that choice will then need some way to be informed of the choice so they can adjust which functionality to use depending on that choice.
At the moment the only workaround I see is for the user to pass an environment variable while building the project so winapi's build script can read that to choose the family followed by emitting key=value stuff which downstream dependencies can read via build scripts through the
DEP_WINAPI_key=value
environment variables to emit cfgs to control their own code.Also, this can also apply to other choices, such as which version of Windows you want to target, newer versions have more functionality that dependencies might want to be able to take advantage of, but the user might also want them to target an older version instead so it can run on more systems.
The text was updated successfully, but these errors were encountered: