-
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
Feature selection in workspace depends on the set of packages compiled #4463
Comments
So, it has to do with features. Namely, two cargo invocations produce two different libcs:
The only difference is So, I get two different libcs in target:
But I get a single memchr:
The file name is the same for both cargo commands, but the actual contents differs. |
Hm, so this looks like more serious then spurious rebuild! Depending on what |
Minimized example here: https://github.com/matklad/workspace-vs-feaures |
@alexcrichton continuing discussion here, instead of #4469 which is somewhat orthogonal, as you've rightly pointed out!
Yeah, it looks like what we ideally want here is that each final artifact gets the minimal set of features. And this should work even withing a single package: currently, activating feature in Though such fine-grained feature activation will cause more compilation work overall, so using union of featues might be a pragmatic choice, as long as we keep features additive, and it sort of makes sense, because crates in workspace share dependencies anyway. And seems better then definitely some random unrelated target activating features for you depending on the command line flags. |
I think one of the main problems right now is that we're doing feature resolution far too soon, during the crate graph resolution. Instead what we should be doing is assuming all features are activated until we actually start compiling crates. That way if you have multiple targets all requesting different sets of features they'll all get separately compiled copies with the correct set of features. Does that make sense? Or perhaps solving a different problem? |
Yeah, totally, "they'll all get separately compiled copies with the correct set of features" is the perfect solution here, and it could be implemented by moving feature selection after the dependency resolution. But I am really worried about additional work to get separately compiled copies, because it is multiplicative. Let's say you have a workspace with the following layout:
Because A and B require different features from libc, and because libc happens to be at the bottom of the dependency graph, that means that for So it's not that only libc will get duplicated, the whole graph may be duplicated in the worst case. |
If we assume that features are additive (as intended), then the innermost crate could be compiled once with the union of all features. Additive features are a bit of a subtle point though (see #3620). Recompiling is the safest way, though expensive. |
@matklad yeah you're definitely right that the more aggressively we cache the more we end up caching :). @nipunn1313 you're also right that it should be safe for features to be unioned, but they often come with runtime or linkage implications. For example if a workspace has a I basically see this as there's a specification of what Cargo should be doing here. We've got, for example, two crates in a workspace, each which activates various sets of features in shared dependencies. Today Cargo does the "thing that caches too much" if you compile each separately (and also suffers a bug when you switch between projects it recompiles too much). Cargo also does the "union all the features" if you build both crates simultaneously (e.g. I'd advocate that Cargo should try to stick to the "caches too much" solution as it's following the letter of the law of what you wrote down for a workspace. It also means that crates in a workspace don't need to worry too much about interfering with other crates in a workspace. Projects that run into problems of the "too much is cached" nature I'd imagine could then do the investigation to figure out what features are turned on where, and try to get each workspace member to share more dependencies by unifying the features. |
This somewhat resolves my concern about build times, but not entirely. I am worried that it might not be easy to unify features manually, if they are turned on by private transitive dependencies. It would be possible to do by adding this private transitive dependency as an explicit and unused dependency, but this looks accidental. But now I too lean towards fine-grained features solution. |
For what it's worth, we've done that exact trick with the parallel feature
of the gcc crate. It does happen, but the workaround is ok.
…On Wed, Sep 6, 2017 at 12:45 AM Aleksey Kladov ***@***.***> wrote:
Projects that run into problems of the "too much is cached" nature I'd
imagine could then do the investigation to figure out what features are
turned on where, and try to get each workspace member to share more
dependencies by unifying the features.
This somewhat resolves my concern about build times, but not entirely. I
am worried that it might not be easy to unify features manually, if they
are turned on by private transitive dependencies. It would be possible to
do by adding this private transitive dependency as an explicit and unused
dependency, but this looks accidental.
But now I too lean towards fine-grained features solution.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#4463 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABPXoxPIsKCCcH5DEgqtKzPt9ek34uLeks5sfk2EgaJpZM4PLGrK>
.
|
Servo relies on the current behavior to some extent: two "top-level" crates (one executable and one C-compatible static library) depend on a shared library crate but enable different Cargo features. These features are mutually exclusive, enabling the union would not work. Maybe the "right" thing to do here is to have separate workspaces for the different top-level things? Does it make sense for shared (Servo’s build system sets |
I would be in support of What makes this problem so insidious is that there's no way to enforce or even encourage the union property of features. If a project pulls in even one dependency that doesn't obey this property, it could potentially create an incorrect binary. In @SimonSapin's case with Servo, I think Servo is lucky that the feature'd crate (
then I believe that compiling Our project at Dropbox ran into a similar issue with itertools -> libeither, where libeither was compiled with two different features. Lucky for us, libeither's features are union-safe, so the code was correct, but it did create spurious recompiles depending on which sub-crate we were compiling. |
I agree with @nipunn1313 -- I think |
This all sounds like agreement on what should happen. @alexcrichton, what code changes need to happen (on a high level) to get there? |
That's what I was discussing with @alexcrichton at the RustFest impl days, and I have a bunch of refactoring done that I'm still tweaking. Will post a PR ASAP. Do you have a particular dependency/urgency relating to Gecko or Servo on this? |
Nothing urgent. I thought this bug could cause spurious rebuilds after selectively building a crate with |
We've had to fork some deps to unify feature selection to work around this
issue. It's definitely not sustainable for us, but not urgent yet.
…--Nipunn
On Sat, Oct 14, 2017 at 5:27 AM Simon Sapin ***@***.***> wrote:
Nothing urgent. I thought this bug could cause spurious rebuilds after
selectively building a crate with -p, but I couldn’t reproduce. Anyway,
thanks for working on this!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#4463 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABPXo7HL7OuZhSaMgZMtY9Y5IxnY7dHFks5ssKjIgaJpZM4PLGrK>
.
|
@nipunn1313 for my understanding, can you point me at a commit or otherwise elaborate on what problems you've had due to this issue? |
Here's an example of a problem we had to work around In that particular case, either and itertools were both present in our workspace. |
@SimonSapin taking on this issue will require a relatively significant refactoring of Cargo's backend. Right now feature resolution happens during crate graph resolution, but we need to defer it all the way until the very end when we're actually compiling crates. |
…ild' … and 'cargo test', etc. Include Servo and its unit tests, but not Stylo because that would try to compile the style crate with incompatible feature flags: rust-lang/cargo#4463 `workspace.default-members` was added in rust-lang/cargo#4743. Older Cargo versions ignore it.
…ild' … and 'cargo test', etc. Include Servo and its unit tests, but not Stylo because that would try to compile the style crate with incompatible feature flags: rust-lang/cargo#4463 `workspace.default-members` was added in rust-lang/cargo#4743. Older Cargo versions ignore it.
I'm used to working in monorepos, but relatively new to Rust. Figuring out how to set up projects such that features are propagated properly took a bit of time. Perhaps this issue really isn't the problem, but rather there shouldn't be Instead it may be more precise to only have Cargo build one package which in turn is responsible for dependencies and propagating the features in a workspace environment. In other words, Rust in a workspace should act more like how "solutions" work in an IDE like Visual Studio, i.e., a configuration that binds the target triple, feature set, and artifacts to build. I should be able to then |
@kriswuollett if I'm reading into your comments correctly, it sounds like its focused on some specific artifacts within the workspace and wanting to define the desired configuration for building those specific artifacts. At this time, a lot of cargo is centered on building a single artifact set. There are complications with its current model for building multiple at once and I feel like a more holistic view of the problem is needed to figure out, even it should even belong in cargo (vs cargo being more neutral and expecting some other process to handle it). I've written some more on these thoughts at https://epage.github.io/blog/2023/08/are-we-gui-build-yet/ |
@epage, thanks for the comments/link! I fall into the camp of As a related aside, the issue of being usable by a build orchestration tool is important, because if the architecture is never written that way, it may no longer be possible, likely ever, see flutter/flutter#25377, of which otherwise I'm a fan of the platform. Here in this issue I just wanted to put in my 2c about the devex in relation to an IDE, and that the multi-dimensional feeling about configuring targets and features makes me think compiling more than one package at once is nearly technically impossible to get "right". |
This removes `wasmbind` from the default feature set, which stops chrono from implicitly depending upon wasm-bindgen and js-sys. This is helpful for a few reasons: * It reduces the default dependency set by default for non-wasm projects, which shrinks the download size. * Projects like Fuchsia have a policy where 3rd party crates need to be audited. While we don't use wasm-bindgen, we can't opt out of it by setting `default-features = false` because of [feature unification] ends up enabling chrono's default feature. See this [cargo issue] for more details. `wasm-bindgen` is large and complicated, so it's pretty expensive for us to update. Fixes chronotope#1164 [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification [cargo issue]: rust-lang/cargo#4463
This removes `wasmbind` from the default feature set, which stops chrono from implicitly depending upon wasm-bindgen and js-sys. This is helpful for a few reasons: * It reduces the default dependency set by default for non-wasm projects, which shrinks the download size. * Projects like Fuchsia have a policy where 3rd party crates need to be audited. While we don't use wasm-bindgen, we can't opt out of it by setting `default-features = false` because of [feature unification] ends up enabling chrono's default feature. See this [cargo issue] for more details. `wasm-bindgen` is large and complicated, so it's pretty expensive for us to update. Fixes #1164 [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification [cargo issue]: rust-lang/cargo#4463
This doesn't just affect workspaces, it also affects crates with dev-dependencies. (I think it's the same issue, anyway. Please let me know if I should file a new issue instead.) I often work on a project that has
Working on this project often involves both It would be great if there was some flag or so that made (For some time I tried to ensure that the binary itself enables enough features in its dependencies to make the crate graphs identical, but it is quite tricky to figure out what the difference between the builds is and even if I manage to find all the right features at some point, it regularly breaks on |
@RalfJung (have you seen https://crates.io/crates/cargo-hakari that I authored? it automates all this for you, and should mostly prevent this kind of duplicate build) |
I have not seen this before, thanks for the pointer! I don't quite want a separate crate for this, just some extra dependencies on my bin crate, but maybe this can help. |
To be clear, this is the desirable behavior for many people, including me. I do *not* want the
Yes, if it's opt-in, then there's no problem. |
It is definitely desirable for many people to not do feature unification at times, either partially or fully. Hakari comes with several knobs to make that possible: https://docs.rs/cargo-hakari/latest/cargo_hakari/config/index.html#traversal-excludes This is a complicated problem with no easy answers. Any solution in Cargo is going to need a ton of configuration knobs. |
I’ve got a use case that doesn’t appear to have been written up yet. I have some libraries with optional-but-default I suppose one could say this is a case where the language kind of forces features to be non-additive. If we take “additive” to mean “works with the feature everywhere it would work without the feature”, then the |
FYI I've posted rust-lang/rfcs#3692 |
Maintainers notes
cargo hack
plugin will automatically expandcargo check --workspace
(etc) tocargo check -p fail_test && cargo check -p lang_rust && ...
,Reproduction:
Check out this commit: matklad/fall@3022be4
Build some test with
cargo test -p fall_test -p fall_test -p lang_rust -p lang_rust -p lang_json --verbose --no-run
Build other tests with
cargo test --all --verbose --no-run
Run
cargo test -p fall_test -p fall_test -p lang_rust -p lang_rust -p lang_json --verbose --no-run
again and observe thatmemchr
and some other dependencies are recompiled.Run
cargo test --all --verbose --no-run
and observememchr
recompiled again.The verbose flag gives the following commands for
memchr
:Here's the single difference:
Versions (whyyyyy cargo is 0.21 and rustc is 1.20??? This is soo confusing)
The text was updated successfully, but these errors were encountered: