-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Give users control over feature unification #3692
base: master
Are you sure you want to change the base?
Conversation
Thank you so much for turning the RustConf discussions into RFC prose so quickly! |
I'm going to go ahead and start the asynchronous process of checking for consensus or concerns on this idea. People should feel free to read and consider at their leisure, and we can always discuss it further; just want to give a place for folks who have had a chance to review it to register either consensus or concerns or both. @rfcbot merge |
Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
Co-authored-by: Josh Triplett <[email protected]>
Co-authored-by: Josh Triplett <[email protected]>
3. Resolve features | ||
4. Filter for selected packages | ||
|
||
**Features will be evaluated for each package in isolation** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main situation I encounter this with is a workspace that has a single package, but the different cargo targets have different dependencies and that affects feature resolution in a way that cargo build
vs cargo test
leads to rebuilds. Will this RFC help there? If yes, then the text should be clarified, because it seems to talk entirely about packages but not about targets within a package. If not -- what would it take to help in that situation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for calling this out! I had considered this but forgot it and didn't want to hold it up until I could remember.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sunshowers says cargo-hakari
will unify normal and dev-dependencies. Platforms are not unified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sunshowers says that is also unifies the features that workspace member features enable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some cases with unifying dev-dependencies include
fail
being used for dev-dependencies but not production- Tests enabling
std
but not wanting them for normal build - Some use case related to private keys where the
impl Clone
is only provided on debug builds and not release builds
There are likely other cases of this same sort of problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Miri's case it's dev-dependencies enabling different features for libc
or some other fundamental crate, which means that even after cargo test
, doing cargo build
does an almost complete re-build of Miri -- that's waiting time I'd rather avoid. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've summarized this under "Future possibilities"
I'm not a team member of any of the relevant teams here at all, but I nevertheless want to raise some concerns. The proposed text is very light on details what should happen regarding feature unification between host and target crates, especially for shared dependencies of them. The last time the cargo team performed changed on how feature unification worked with the 2021 edition they broke existing crates in an incompatible way. (The argumentation for that essentially was: It's required for other use-cases to work, which is really not that great if you part of the number of crates that broke due to this.) As additional observation: The That written: It would be surely helpful to have some sort of control over how feature unification works, but maybe that should not be done at a workspace level, but rather be up to control of who puts there the actual dependency? That would allow me as a crate author of some inter-dependent crates to correctly control how cargo treats features for these interconnected crates. |
Co-authored-by: Juniper Tyree <[email protected]>
Thanks for your responses.
If that's so widespread it shouldn't be too hard to provide some exact numbers that say: "We measure a speedup of x in this cases. You can reproduce this by doing y." I do not doubt that this is the case, it would just make the discussion much more transparent than: Someone says this might speedup xyz.
I wouldn't go as far as confidently stating that this will not cause issues in practice, as at least I see real uses cases that might have a setup as shown in the "broken" example, although I agree that these are likely not common. That's the reason why I repeatably wrote that I don't think the RFC should be cancelled because of that, it only needs to make sure that the communication around the feature is careful. In my opinion it needs to at least communicate:
Again: On a fundamental level I need to disagree here. A breaking change is a breaking change, no matter how useful it is or not. If you believe that it is really that useful and it is worth a breaking change you should be at least honest and say: Let's bump the major version. My main point here is: It's up to the RFC author to provide reasonable evidence that this is not a breaking change. Given the provided example and the communication from the author I must say that I cannot see that evidence yet.
Yes, my concern around this is not only around this particular change. As written before: I'm not requesting that this isn't accepted. My main concern here is around the ability of the cargo team to decide whether or not something is a breaking change, given the history of rust-lang/cargo#14415 (which was a breaking change, with a very similar scope). Given how the team ignored my concerns back then I just want to make sure that this time this issue:
|
Those are figures I got from a sizeable workspace with no hack, a different hack manager and something similar to hackari: https://crates.io/crates/cargo-hackerman#user-content-hackerman-vs-no-hack-vs-single-hack-crate Example is somewhat artificial, but it shows 2x difference in total compilation time because dependencies are recompiled multiple times with different sets of features. Lack of feature unification hurts workspaces that use nix to compile dependencies even worse - I saw cases where a single crate was compiled 20-25 times, with total compilation time increasing from 10-15 minutes to 1-2 hours on a CI machine. No exact measurements, but the order of magnitude is correct. |
It will take a bit of time to measure this, but the speedup is absolutely real and is certainly not a "might". But before I invest my time into this, I would like an assurance from you that you will respect the effort that I put in.
No, this is not a breaking change. The sense of breaking that matters here is a strict sense, and this is very clearly non-breaking. Existing workspaces will continue to compile with no changes—it is explicitly opt-in. Since it is not a breaking change in the strict sense, it can be evaluated as an engineering decision based on the merits. This change would make life better for the vast majority of people who opt into it, but unfortunately result in more work for some. That is just part of life as an engineer. |
You are free to do whatever you want. After all I'm not a member of the cargo team, so strictly speaking my comments here are "not relevant". I merely point out that the RFC only mentions that there are speedups, but does not provide numbers or evidence for that. Coming from a scientific background that's just bad practice as you "claim" something without providing at least some evidence. Please note: I do not doubt the speedups, it's just bad practice to make such claims without having numbers.
I'm sorry, but I need to disagree here again. You are in so far correct that this is no breaking change for the workspace owner itself as they can opt into this feature. The matter is not so clear for any crate that is a dependency of said workspace, as their authors do not opt into something as far as I can read out of the RFC and their code can break due to this setting. Given that I find it not useful that you (and others) claim this is clearly not breaking as you opt in to this behavior. I might be wrong here, but in this case again: Please point out for the Now you might still argue that this is not a breaking change for other reasons, e.g. because it's already broken in different, but similar situations. That's a different argument to made. I honestly do not see a complete evaluation of that argument yet.
Again it seems like you just claim things here by saying it's more work for other people (in this case me). Let's put it the other way around: What exactly can I as diesel author do to fix this situation so that this breakage does not happen? As outlined in rust-lang/cargo#14415 it's not possible to fix that in diesel due to fundamental limitations in how feature unification currently works. If you disagree with that I'm looking forward to see your solution. Finally to take up the argument: The On a more fundamental side: I spend some more time thinking about whether or not that could be useful for my use cases. While doing that I noticed another rather fundamental point that I do not see addressed or discussed yet. I wouldn't call this necessarily a blocker, but I personally would see that as serve limitation of the proposed feature. For our internal code we have a large workspace that includes client and server code, to share some types between both. The server code depends on diesel with various database backends enabled, which in turn pulls in various native libraries. The client code also uses diesel, but for different use cases and only with the sqlite backend enabled. Now the big argument for this feature is: It speeds up compile times, which I do not question in its own. You cite CI run times here as critical point. Now consider the described workspace, for test runs we do care about compile times as we want to have test results as quickly as possible, so you would enable the |
This is RFC is not for workspace-level config ( |
This relevant text in the RFC is:
In case that wasn't clear, it's describing a situation where a workspace contains a large number of crates, that aren't meant to be published on their own, but are sub-components of a big monolithic application. During development, it's fairly common for an individual developer to be editing only a small number of crates, and that developer may hop between workspace crate directories running e.g. This situation has led to various "hacks" (see the RFC for links) that are a bit unsatisfying and consume developer time that might be put to better use. There is no setting today, and no carefully written command, that allows this developer to say "build just crate X, while using the feature unification that would have been used, if I were building the whole application". Also note that this situation doesn't appear in other build systems, which leads to pressure to move away from Cargo toward systems that always build components the same way. |
Really, the user is asking "build package X as if its dependency of Y". I've added this to the Alternatives and why we aren't proposing that at this time and instead going with this. |
I don't understand. What is In my workspace there are a number of top-level packages/binaries that collectively contribute to the dependency feature set, so I don't really want to name one of them specifically. |
I'm also not sure what is meant by "The workspace setting breaks down if there are more than one "application" in a workspace". That could describe my workspace: lots of output binaries, though I guess if you squint you could just call them one application collectively. Perhaps the language could be adjusted to say something like "the workspace setting may not be compatible with a workspace containing multiple applications, if those applications require require different dependency feature sets"? |
You left off an important qualifier
This is talking to the use case of "move to a subcomponent and build" and not "let me optimize my build and force two applications to the same feature set. |
I think that sentence could be improved then. It reads to me like "This setting is broken if you have more than one application. It's more broken if you need disjoint feature sets". |
Under the "build package X as if its a dependency of Y", that would be "build package X as if its a dependency of Y, Z". This is the more generalized form that more closely maps to the problem and gives people the flexibility to adopt to their specific needs. I was thinking about this as I considered ways to give people flexibility within the RFC's design, like defining package groups or having individual packages opt-in or out of workspace unification. Going down those routes is complicated because its trying to generalize but in a way that doesn't map the users actual problem. Keep in mind, I put this up as an alternative. I just wanted to highlight what problem we are modeling. |
In the RFC, the listed motivation is not to artificially unifying features when building applications for the sake of build optimization but of keeping the same build configuration as you build different parts of the same application. From that perspective, it is breaking down because applications are affecting each other when before they wouldn't. For example, your build could break by moving an application to another workspace because you accidentally relied on the features it activated. You could get unexpected behavior from features that don't show up in the While this RFC doesn't preclude using this to unify unrelated feature graphs, that is really a topic of the RFC for the future possibilities when we discuss unifying host/target, normal/dev dependencies, etc. As the updated text calls out, all we are doing is allowing users to do what they can today but with less overhead. |
This is not a breaking change, period. Cargo adding an opt-in feature that is incompatible with some dependencies is not a breaking change by any reasonable definition.
The act of using a dependency is a decision made by the maintainers of a workspace. If a maintainer uses a dependency it is their problem to ensure compatibility. The buck stops with the maintainer.
Maintainers can do all sorts of things that are not compatible with their dependencies. Trivially, they can use a dependency's API incorrectly. More substantially, they can use a Similarly, they can opt into this feature that may cause their use of a particular dependency to fail. Then the onus is on them to address it.
This is controlled at the top level, not within dependencies, so there's no behavior for a dependency to opt into. If a workspace opts into this, then ensuring that
This opt-in change does not break code of dependencies, any more than Cargo adding support for a target platform which is incompatible with that dependency would. In your case, given that the issue occurs with [dependencies]
diesel = { version = "=2.2.2", features = ["postgres"] }
[build-dependencies]
diesel = { version = "=2.2.1", features = ["sqlite"] } You can tell your users to unify their feature sets manually. Does this not solve the problem? [dependencies]
diesel = { version = "=2.2.2", features = ["postgres", "sqlite"] }
[build-dependencies]
diesel = { version = "=2.2.1", features = ["postgres", "sqlite"] } One of the future work items in this RFC (and something supported by hakari today) is about automatically performing target/host feature set unification (essentially undoing that component of resolver v2) which would also address your issue.
No, you cannot -- if you're in the middle of a refactor, you'll be forced to get the entire workspace to compile rather than the component you're working on.
Your concern is valid, but it is handled by passing in the appropriate
Having to pass in a command-line flag during every invocation of
|
I'm sorry, but I'm really disappointed how much you seem to care about my concerns. I do not feel that you even try to response to what I'm asking for. Instead I read the same not meaningful responses again and again. I honestly had no such bad interaction with any of the other rust teams yet.
You (and epage) claim that again and again, but as pointed out above my code stops compiling without that I opt into something. How else than "breaking" would you call this? It worked before and stopped working after this change out of the perspective of the dependency maintainer. This RFC changes some fundamental assumptions how feature unification works again and again in incompatible way. And to repeat that again (as you all seem to ignore that): I can fully understand that there are valid reasons for doing that, but you need to be really aware that this is a complicated topic and "It's opt-in so it's no breaking change" is really not a good argument here. I would really wish you either find a better argument or a better solution for the same impact or at least make clear in the RFC itself that there will be breakage and that the implementation must deal with it. See the suggestions made before if you want concrete solutions.
There are still a set of rules that are considered to be supported or not by cargo. For example it is commonly expected that features are additive. Mutually exclusive features are not supported. I cannot remember that this set of rules changed in an incompatible way before (outside the already breaking More importantly I would argue that these are all different matters:
That break their crate, not the dependency. If it can break the dependency I would consider that a bug there.
That feature exists on it's own. As it was introduced it did not change the behavior of an existing feature like it happens here. Additionally it was not promoted as something that can help speeding up compile times, which in turn makes it more likely that people harass dependency maintainers about this. It's explicit around the feature naming + description and documentation that this feature can break something. I cannot see something like that in this RFC. (Again: That's one of the things I've suggested several times to do to address my concerns!)
That's something that was possible since rust 1.0. Importantly it does not introduce a new way to break things!
There is an explicit error/warning here that this is not supported if the dependency correctly declares in MSRV in their
That sounds nice in theory, but won't work in practice. The users will still complain in the dependencies repo that something is broken in that crate and request a fix there. Especially for features that are promoted to drastically improve compile times.
No it doesn't as the user needs to do something. I'm looking for a solution that can fix the problem in diesel itself. At that point I might as well put into the documentation that this kind of setup is unsupported and they should go to complain to the cargo team if they want to see it supported. I would like to avoid that, but we might up in a situation where this is the best possible solution for me.
If it's up on the table to have this at some point I suggest that you include it in the first version of the RFC. After all the RFC process is for defining a meaningful set of features. Given the provided examples, it seems like this extension is necessary for a minimal working feature set. And if that's too much work, well then it seems like the whole feature is too much work.
That's not true for local workflows as you have an existing target directory there. Yes cargo will build a dependency again if it doesn't have a cache with the same set of feature flags, but that happens exactly once for each feature set. After that the cache still exists, even if you build a crate that uses the same dependency with a different feature set in the mean time.
That's correct, I missed that. Thanks for pointing out.
You surely can use aliases for that so that no one needs to remember the correct flag. You certainly can also ensure that people use the aliases by making it much easier to use them instead of the normal
I would like to see an example for that, because you always can specify the required features from the other crates, often even via command line flags. If that's not possible it can be made possible by adding the relevant sub dependencies to the crates Cargo.toml which shouldn't change the default build, but would enable specifying features for that subfeature. Sure I agree that this requires some work to get it working, but it's not impossible as claimed. |
As a long-time user of cargo and more recently (past 8 months) cargo-hakari, I find the ongoing subthread along the lines of "can't users just do X and Y to get the same speedups today" confusing to say the least. Is that a serious position or still the "devil's advocate" thing it apparently started as? I would consider myself to have above-average experience in dissecting Cargo builds and reshaping the dependency graph to get more favorable behavior. I can understand and act on the long-distance interactions that |
At least from my side it's only half-serious, but I consider that proposal as similar useful as everything that I heard from the cargo team as response to my real concerns. That written: I don't believe that it is that much trouble to do a basic feature unification on your own. The simplest way to do that would be to put all your dependencies in your workspace |
That works for all features activated at the top level but not by those activated by dependencies. You'd need your entire set of dependencies shared between all packages as well. |
You can always add these sub dependencies as explicit dependencies as well. (As written before: I just say: That's possible to do, not the most easy thing to do.) |
In my experience, workspace members directly requesting different feature flags from dependencies is rarely the issue. When they do occur, it's often intentional and shouldn't be unified in every build of the workspace members (e.g., when some libraries are no_std). Transitive dependency edges cause more and worse problems, especially for foundational crates like syn or log or serde that have a ton of reverse dependencies, because all of those are rebuilt once per feature flag combination across all crates they depend on. As @epage says, addressing that requires spelling out dependencies on crates you otherwise wouldn't mention in your Cargo.toml files. This is the part that cargo-hakari automates and centralizes in a single place. Manually doing that is not a good use of mine or anyone else's time. It's also not a good use of my time to wait for Cargo to build a bunch of crates a fifth time because I just discovered a way to enable yet another combination of features for some central dependency. The distinction between from-scratch and incremental builds becomes blurry when you repeatedly experience incremental builds not being very incremental for no good reason. Reducing such surprises is one reason why I've become a big fan of workspace-wide feature unification.
Well, please don't take that out on me by giving me only-half-serious advice on how to manage my workspaces. I hope the above explanations are useful or at least interesting for the part of you that's serious about this. |
By hand this is very fragile once we get into hundreds of dependencies. Unless I'm mistaken commands like |
The RFC proposes opt-in behavior that changes how feature unification works. The exact way feature unification works is not frozen in amber, and saying that every opt-in change to it is a breaking change is not a reasonable view.
The definition of a breaking change is not "any change that imposes any kind of burden on anyone". Non-breaking changes impose burdens on people all the time. As I said, it's just a normal part of life as an engineer. As a directly pertinent example: when weak features came out I needed to update hakari to handle them. I had to spend several hours coming up with a good data structure and an algorithm for that purpose. I didn't want to spend my time doing that; it certainly wasn't my favorite thing in the world. But as a professional I recognize the value in weak features, and I don't complain about the burden. I accept that that's part of the deal with maintaining a way to do Cargo graph traversals. The needs of the many outweigh the needs of the few.
So to confirm, if the user does manually unify features, this is no longer an issue? The user already needs to do something to opt into this behavior. It is not unreasonable to ask them to do this other thing.
That would be nice, but given that there is a relatively straightforward workaround, surely that's good enough.
All you need to do is to say "if you're in this situation, unify your feature sets". You already have to do that to enable
One of the key benefits of Cargo is to provide a uniform interface across many different projects. Every project coming up with its own aliases is going to make the world significantly worse.
Yes it is. It certainly is a lot of trouble to keep this up-to-date.
Yes, you can, due to transitive dependencies. Saying that you cannot indicates to me that you have not spent enough time with this problem. Please spend more time with this problem. Try out hakari on one of your own projects. Experiment with it, and note the upsides and downsides. It might help you understand why I'm so excited about it.
Sounds miserable, and ripe for automation to handle. As someone with a lot of experience writing developer tools, this would never meet my bar for an acceptable workaround.
Another indicator that you may need to spend more time thinking about this problem. The number of variants is absolutely massive. For example,
|
As far as I can tell diesel was partially broken by resolver v2 (cargo#14415) in 2021 (rust#88903). I'm sympathetic to the pain that has caused, and would support the argument that more should be done to fix it or reduce its impact. But I agree with the consensus that this RFC doesn't really change that situation: at worst it adds another method to reveal that existing problem. |
First of all: Again as you seem to ignore that all the time: I still not saying that this RFC is a bad idea and shouldn't happen. I'm merely asking for addressing the raised concern, which again is mainly on a social level. That social concern is mostly caused by the resolver="2" change a few years back, that was technically not allowed and shouldn't have happened in that way. The technical part is interesting to discuss and I really would like to see that fixed first, but it is not blocking from my point of view. Addressing the social part of the concern can be as simple as adding a statement to the RFC that says the implementation needs to make sure to warn about these situations and make sure to not put that work onto the affected crate maintainers. Maybe even include that you don't want to repeat the
Can you point to an RFC or any official team decision that says that feature unification is not considered to be stable? If not I would reasonable expect that this is covered by rust stability guarantee. If it's considered to be not stable I really wonder how any crate would be able to guarantee anything about its api.
I would assume that you parse the Cargo.toml files for that? If that's the case, it's not comparable as that format always had the possibility to be extended in some way.
That is a really dangerous argument. You shouldn't use that in any discussion at all in my opinion.
My main point is that it adds a new way to break things. The old ways are unfortunate but cannot be changed. Ideally we shouldn't add new ways before we solved the old issue at all.
That does not help in some situations as you often do not want to have both features enabled as the both pull in native requirements. That's something I've already written before a few times.
Interestingly that seems to work well for me for rather large projects with ~700 dependencies, so either you all are changing dependencies much more often that I do or my workflow is otherwise different.
|
I said "frozen in amber", i.e. you can't even opt into different behavior. The behavior is stable, it's just not frozen in amber.
It is stable. An opt-in flag changing behavior does not impact stability.
No, the data comes from
As a engineer I've always worked under this maxim, and I will keep making this argument for the rest of my career. Sometimes the cost of a large improvement for most people is some extra burdens on a few. That's part of living in society.
I rebase at least once a day, often 3-4x a day, because I work on fast-moving collaborative projects where several of us work on the same component -- and we all want to pull in each other's fixes. And no, it's not once by default -- each time you run a
No, this is not a reasonable thing to expect people to do.
But after this sequence of operations:
It is often the case that |
This adds
resolver.feature-unification
to.cargo/config.toml
to allow workspace unfication (cargo-workspace-hack) or per-package unification (cargo hack
).Rendered