-
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
dev-dependencies on workspace member cause cyclic dependency issues #14167
Comments
Is it the same issue for crates that export proc_macro with other crates ? I have a main crate In [package]
name = "foo-derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full"] }
[dev-dependencies]
foo = { path = "../foo" }
trybuild = "1" Is it inherently wrong to do that ? Beside an error (appears twice in a row) in the output of
Everything seems to work well. |
It is not a wrong thing to do that, it's just r-a struggling with it (so yes, you are hitting this here). If everything works fine you can safely ignore the error. |
Handle dev-dependency cycles cc #14167 This should fix cycles errors mostly (it fixes the one on rome/tools at least, but not on rustc. Though there it might just be because of rustc workspace being rustc workspace). Unfortunately this will effectively duplicate all crates currently, since if we want to be completely correct we'd need to set the test cfg for all dev dependencies and the transitive dependencies of those, something I worry we should try to avoid.
Handle dev-dependency cycles cc #14167 This should fix cycles errors mostly (it fixes the one on rome/tools at least, but not on rustc. Though there it might just be because of rustc workspace being rustc workspace). Unfortunately this will effectively duplicate all crates currently, since if we want to be completely correct we'd need to set the test cfg for all dev dependencies and the transitive dependencies of those, something I worry we should try to avoid.
So duplicating the crates causes a bunch of issues, especially for traits and their implementations, as we can possibly have types in scope that implement the original trait, but have the duplicated on in scope. And a lot of other issues similar to that. So now I'm wondering, the reason for why we want to prevent cycles is obviously graph traversal which makes me wonder if wen can lower dev dependencies to some kind of "weak edge" that is skipped when traversing the graph except for name resolution. |
Hmm, so that approach runs into the problem that we'll have a cycle in the defmap creation ... |
Yeah. 🤔 How exactly do cyclic dev dependencies work with Cargo? I'm not sure why it doesn't run into the same problem? |
Right, that should occur there as well then 🤔. Also would be interesting to hear how the jetbrains plugin deals with this. @Undin @vlad20012 I hope you don't mind the ping, but if you could shed some light on how intellij-rust deals with workspace dev dependency cycles that would be appreciated :) (assuming it deals with them at all) @matklad maybe you have some more ideas here as well |
So the core thing here is that there are packages and crates. We physically can not have cyclic dependencies between creates, cause they need to be compiled in topological order. I believe cargo has some logic to prevent cyclic dependencies between packages (eg, if you literally add a cyclic dep, cargo will bail out), but that can be circumvented via a dev dependency. Adding a dev-dep on oneslef is forbidden (an explicit check in cargo), but adding a self-dev-dep via a third package works. I wonder if we can just, like, ban this in cargo in an edition boundary? Like, this 100% works today, and people are relying on that, but semantics are confusing (you basically have two copies of every trait/type in scope), and compilation-time hit is big. My gut feeling is that we should add an off-by-default flag, which models cargo semantics precisely, by essentially duplicating the crate graph (every crate gets variants with and without —-test). This we can use as a testbed to iron out any issues with the single file belonging to more than one crate. Yes, that’s going to be hugely confusing, because there’s going to be two copies of every crate and what not, but that’s what is actually physically happening. With that flag in pace, we can have a better error message here saying “hey, we noticed you have cyclic dependencies, would you like to fix your code, or to check this checkbox?” |
Perhaps more importantly, you also literally have two independent copies of the crate in the final binary |
We just carefully remove such |
Related to rust-lang/rust#79381 |
rust-analyzer does not play well with circular dependencies. See rust-lang/rust-analyzer#14167 for comments from many rust-analyzer maintainers around this issue. Fundamentally, Cargo has (fragile) support for dev-dependencies having circular dependencies. This works because Cargo makes a distinction between the integration tests and the actual contract code. This allows us to not have circular dependencies when building our code, even though they may exist without an implementation akin to the Cargo build system. Since rust-analyzer (and likely other tools in the Rust ecosystem) don't fare well with circular dependencies, we lift the relevant code up to separate crates (which only contain tests) to ensure there is no cyclic dependencies. In the future, we could take a look at removing white-whale-testing, since it does not provide much value (and likely will lead to a higher chance of circular deps).
fix: Skip problematic cyclic dev-dependencies Implements a workaround for #14167, notably it does not implement the ideas surfaced in the issue, but takes a simpler to implement approach (and one that is more consistent). Effectively, all this does is discard dev-dependency edges that go from a workspace library target to another workspace library target. This means, using a dev-dependency to another workspace member inside unit tests will always fail to resolve for r-a now, (instead of being order dependent and causing problems elsewhere) while things will work out fine in integration tests, benches, examples etc. This effectively acknowledges package cycles to be okay, but crate graph cycles to be invalid: Quoting #14167 (comment) > Though, if you have “package cycle” in integration tests, you’d have “crate cycle” in unit test. We disallow the latter here, while continuing to support the former (What's missing is to supress diagnostics for such unit tests, though not doing so might be a good deterrent, making devs avoid the pattern altogether)
Hi, just found this issue as I was trying to figure out why I was seeing an
In our case, we actually do rely on this kind of cycle in https://github.com/rust3ds/ctru-rs, but maybe there's a way we could work around it? Here's how our (trimmed for clarity) dependency tree looks:
There are two cases here:
#[cfg(test)]
extern crate shim_3ds; In both cases, we use From what I can tell, this is the kind of use case that is being considered as "not really useful", is that right? Is there maybe some other way we can achieve the same thing that would play nicer with r-a, or something? I guess something like |
No, this was out of scope for my analysis, mostly because custom test frameworks are even weirder and already not supported by rust-analyzer. This may be a legitimate use case but it's legitimate because it's via custom test frameworks.
This seems less good, since you end up with multiple pairs of types. This is probably better served by slicing the deptree a bit more. |
Hmm, do you mean like if we tried to re-export the types or something, we'd end up with two different versions of the crate's types/functions? In our case that's fine, because we don't actually call/reference any of those transitively via As a small workaround without requiring full support from r-a, I wonder if our particular situation could be helped out by allowing dependencies up to the point that a cycle is created, rather than eliminating the dependency branch entirely? Without knowing too much about the implementation, it seems like r-a is disabling this edge (marked
Would it be possible instead to ignore this one instead? It would allow for more symbols to be resolved, and in our crate just silence the one error we have for
It probably is possible for us to restructure our crates to avoid this cycle, but just wondering if that would be a more general "partial solution" for cases similar to ours where Cargo does allow a cycle like that. |
I think there are a lot of benefits of doing that anyway. OTOH I don't see a huge benefit of making this rather niche case work. |
This issue is spread across several others, so let's make one big one out of it to better track it.
#2414 (comment)
Issues in question:
#8330
#2414
#12407
#9574
#11410
The text was updated successfully, but these errors were encountered: