-
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
Optional dependency when a feature is *not* enabled. #1839
Comments
I'd also like to have this. In midir, there are multiple backends (ALSA and/or JACK on Linux, CoreMIDI and/or JACK on OSX, WinMM on Windows), and my plan is to have a My first idea was what is requested above, i.e. to simply disable An alternative to this is to have a default feature (e.g. So far I was not able to find a workaround that works without defining platform-dependent dependencies/features in dependent crates, but I may have overlooked something. |
With the
Currently, such a section is treated as enabled regardless of feature status, which honestly seems like a bug to me. |
Same case for me: [target.'cfg(not(feature = "std"))'.dependencies]
heapless = "0.2.7"
[features]
default = ["std"]
std = [] I only want the Is the current behaviour a bug or is it intended? |
This doesn't seem to be a valid solution any more. According to here, https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies
[dependencies]
foo = { version = "1.0", optional = true }
bar = { version = "1.0", optional = true }
[features]
fancy-feature = ["foo", "bar"] |
How it was done =============== First step was to replace the references to the `std` crate by `core` or `alloc` when possible. Which was always the case, except for the `Error` trait which isn't available in `core` on stable[1]. Another change, that may impact the performance a bit, was to replace the usage of Hash{Map,Set} by the B-Tree variant, since the hash-based one aren't available in core (due to a lack of a secure random number generator). There should be no visible impact on default build, since we're still using hashtable when `std` is enabled (which is the default behavior). Maybe I should give a shot to `hashbrown` to bring back the hashtable version into `no-std` as well. The last change was a bit more annoying (and surprising): most of the math functions aren't available in core[2]. So I've implemented a fallback using `libm` when `std` isn't enabled. Note that due to Cargo limitation[3] `libm` is always pulled as a dependency, but it won't be linked unless necessary thanks to conditional compilation. Known limitations ================ Cannot use the `geo` feature without `std`, mainly because the `geo` crate itself isn't `no-std` (as well as a bunch of its dependencies I guess). -------- [1]: rust-lang/rust#103765 [2]: rust-lang/rfcs#2505 [3]: rust-lang/cargo#1839 Closes: #19
How it was done =============== First step was to replace the references to the `std` crate by `core` or `alloc` when possible. Which was always the case, except for the `Error` trait which isn't available in `core` on stable[1]. Another change, that may impact the performance a bit, was to replace the usage of Hash{Map,Set} by the B-Tree variant, since the hash-based one aren't available in core (due to a lack of a secure random number generator). There should be no visible impact on default build, since we're still using hashtable when `std` is enabled (which is the default behavior). Maybe I should give a shot to `hashbrown` to bring back the hashtable version into `no-std` as well. The last change was a bit more annoying (and surprising): most of the math functions aren't available in core[2]. So I've implemented a fallback using `libm` when `std` isn't enabled. Note that due to Cargo limitation[3] `libm` is always pulled as a dependency, but it won't be linked unless necessary thanks to conditional compilation. Known limitations ================ Cannot use the `geo` feature without `std`, mainly because the `geo` crate itself isn't `no-std` (as well as a bunch of its dependencies I guess). -------- [1]: rust-lang/rust#103765 [2]: rust-lang/rfcs#2505 [3]: rust-lang/cargo#1839 Closes: #19
Any workaround? |
For the original use case, For the surface reading of this issue, this is a special case of #2980 (see also https://internals.rust-lang.org/t/pre-rfc-mutually-excusive-global-features/19618). |
@epage I disagree that global mutually exclusive features solve this because this is neither global nor mutually-exclusive. There is a case when a library depends on a more powerful option to build. And yes, I do co-maintain such crate. Forcing users to set a global feature would be annoying. So I think either this one or #14188 should be reopened. And while maybe global mutually exclusive features could have a special case to resolve this I think it'd be just too complex. |
While I didn't notice that this was closed when I closed #14188, particularly that the reason it was closed was for the specific users need and not for the broader topic, if I open this, I'd either be re-closing it or proposing it to be closed, looking for a second on the Cargo team, because of the discussion in that This Development Cycle in Cargo section I linked to. |
No problem, such things happen.
Why is that a problem?
Do I understand correctly that your argument against it is pretty much what the document you linked says? Specifically:
First, I don't see how it would run into a cycle. Once a dependency is added it's not removed (cycles in crates themselves are already forbidden) so in the worst case scenario all features of all crates would get enabled. And there's not an infinite amount of features or crates. Therefore the loop must terminate once there are no more features to enable. Regarding algorithmic complexity, the loop would repeat at most as many times as there are dependencies that are using this. And while multiple crates would benefit from this I don't think that there are so many of them in most projects. I'd be surprised if it had to loop more than a few times on an average project. That loop will take much less time than recompiling a whole crate that's not needed or having a human figure out which of the exclusive features of a crate needs to be turned on. I can't comment on implementation complexity as I haven't seen the resolution algorithm yet. What I can say is that I believe this feature to be quite valuable. It has potential to reduce compile times which is the topic everyone complains about and does so without making something else annoying. Thus the complexity might be worth it. Just in case: if some part of my comment sounds like a strawman or otherwise dishonest I really don't mean to. I had hard time understanding your comment (I feel a bit tired) and it's quite possible I messed up something. |
Just calling out that I closed your general issue for one closed for more specific reasons, so it wasn't a full replacement of it as-is.
Take the example from #14188 [package]
name = "foo"
[features]
extra = ["dep:powerful"]
"!extra" = ["dep:minimal"]
[dev-dependencies]
foo = { path = ".", features = ["extra"] } Note: this is a legal cycle. We don't know what dependencies exist on This is a simple case where we directly depend on
I can't speak to that. @Eh2406 would be the best person to.
The current resolve is a mess and is nearly feature frozen. We've made minor tweaks here or there but have been avoiding major ones. Work on a replacement resolver has picked back up again lately which improves the situation by giving us less control through abstracting and generalizing the resolver which makes it easier to reason about each of the pieces. I'm assuming the new resolver is still a ways out and @Eh2406 would also be the one to speak to in terms of implementation complexity. |
Ah, OK, so looks like that one should be reopened?
Yes, to process it we need to repeat the processing multiple times. When building a non-dev build
Oh, OK, understood that it may take a while. But it'd be great to have it available one day. |
None of this is new ground and, last the last time this was discussed, the general consensus I got from the conversation was "this wasn't happening". |
First off sorry for the slow response here. This is been in my inbox to reply to for... Way too long. Every time I figure out something clear to say I realize it's a massive oversimplification, and decided to try again the next day. This is one of the fascinating consequences of living in a
I absolutely feel the sentiment. Simple statements end up just being strawman, and technically correct statements end up to in the weeds to be understandable as a whole. We will all endeavor to be forgiving and charitable and understanding, so that we can continue to talk about such fascinating topics. A problem with an imperative outer loop is one of error messages. If I depend on The quality of the error message is a direct analog for the rigor of the internal algorithms. If there's a bug in the loop that skips over versions it should not, then a complete error message will have a logical fallacy and it. This logical fallacy can be spotted by users, or even checked by tests. If the error messages are not complete, it is incredibly difficult to even identify the bug occurred. To change the subject little bit a more fundamental question I have about the proposed outer loop is... Say we have hundreds of versions of [package]
name = "foo"
[dependencies]
minimal = "=1.0.0" // `=` versions are rare but "things that cause conflicts" are not, and this is an easy example to construct and explain.
[features]
extra = ["dep:powerful"]
"!extra" = ["dep:minimal"] in my [package]
name = "root"
[dependencies]
minimal = "*"
foo = "*" so if I understand the proposed loop correctly it first does the resolution that ignores all We could:
I am sorry if I left out important details of my understanding, or miss important details of your understanding. Similarly, it is possible I will wake up tomorrow morning with a clear understanding of something that seems intractable at the moment. |
@Eh2406 thanks for thoughtful response! Firstly I don't really know what you mean by I don't see any issue picking a different version and just have the crate twice in case of a conflict. Yes, it defeats the original purpose and a warning could be warranted but at least it works. This mechanism should be mainly used for private dependencies anyway. |
Yes, if we see
Many parts of cargo are built around the long-standing assumption that we do not build more than one semver compatible version of each crate. If this feature requires expanding the build model... that's a very different conversation. |
Oh, that's weird, I thought it can compile any combination of crates and it just chooses not to. OK, you have good point to which I don't have a solution, at least not now. |
Some libraries can run on stable Rust with degraded performance (e.g. without using
NonZero
) or limited capabilities (some APIs disabled). The emerging practice seems to be having anunstable
ornightly
Cargo feature to enable user to opt into using unstable Rust features when they are available.I’m publishing rust-rc as a work around for
std::rc::Weak
not being stable yet. At the moment, there doesn’t seem to be a way to have this crate as a dependency by default, but not when theunstable
feature is enabled. Could this be added to Cargo?Note that reversing the default and having a
stable
feature only moves the problem to dependencies that can/should only be used on unstable Rust.The text was updated successfully, but these errors were encountered: