-
Notifications
You must be signed in to change notification settings - Fork 5
Section on public/private dependencies #4
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
Conversation
|
I think the interesting case is:
|
And which of these are public/private? When running this with everything public: index.add_deps("root", (1, 0, 0), &[("a", Public, ..)]);
index.add_deps("root", (1, 0, 0), &[("b", Public, ..)]);
index.add_deps("a", (1, 0, 0), &[("x", Public, (1, 0, 0)..(1, 0, 1))]);
index.add_deps("a", (1, 0, 0), &[("c", Public, ..)]);
index.add_deps("b", (1, 0, 0), &[("x", Public, (2, 0, 0)..(2, 0, 1))]);
index.add_deps("b", (1, 0, 0), &[("c", Public, ..)]);
index.add_deps("c", (1, 0, 0), &[("x", Public, ..)]);
index.add_deps::<R>("x", (1, 0, 0), &[]);
index.add_deps::<R>("x", (2, 0, 0), &[]);The solver fails with the following error message: Because b$root@1.0.0 seeded depends on x$root@1.0.0 seeded 2.0.0 and a$root@1.0.0 seeded 1.0.0 depends on x$root@1.0.0 seeded 1.0.0, b$root@1.0.0 seeded ∗, a$root@1.0.0 seeded 1.0.0 are incompatible.
And because root$root@1.0.0 seeded 1.0.0 depends on a$root@1.0.0 seeded and root$root@1.0.0 seeded 1.0.0 depends on b$root@1.0.0 seeded, root$root@1.0.0 seeded 1.0.0 is forbidden.So in less strict language, it says: "Because b depends on x @ 2 and a depends on x @ 1, b and a are incompatible. And because root depends on both a and b, root is forbidden". |
|
If I make the Because b$root@1.0.0 seeded depends on x$root@1.0.0 seeded 2.0.0 and a$root@1.0.0 seeded 1.0.0 depends on x$root@1.0.0 seeded 1.0.0, b$root@1.0.0 seeded ∗, a$root@1.0.0 seeded 1.0.0 are incompatible.
And because root$root@1.0.0 seeded 1.0.0 depends on a$root@1.0.0 seeded and root$root@1.0.0 seeded 1.0.0 depends on b$root@1.0.0 seeded, root$root@1.0.0 seeded 1.0.0 is forbidden.Traduction: "Because b depends on x @ 2 and a depends on x @ 1, b and a are incompatible. And because root depends on both a and b, root is forbidden" |
Oops, yes I forgot to say that. All private, except the
|
|
It seems to find the following solution, is that what you were expecting? let mut index = Index::new();
index.add_deps("root", (1, 0, 0), &[("a", Private, ..)]);
index.add_deps("root", (1, 0, 0), &[("b", Private, ..)]);
index.add_deps("a", (1, 0, 0), &[("x", Private, (1, 0, 0)..(1, 0, 1))]);
index.add_deps("a", (1, 0, 0), &[("c", Private, ..)]);
index.add_deps("b", (1, 0, 0), &[("x", Private, (2, 0, 0)..(2, 0, 1))]);
index.add_deps("b", (1, 0, 0), &[("c", Private, ..)]);
index.add_deps("c", (1, 0, 0), &[("x", Public, ..)]);
index.add_deps::<R>("x", (1, 0, 0), &[]);
index.add_deps::<R>("x", (2, 0, 0), &[]);
let solution = resolve(&index, "root$root@1.0.0 seeded", (1, 0, 0));
assert_map_eq(
&solution.unwrap(),
&select(&[
("root$root@1.0.0 final", (1, 0, 0)),
("a$root@1.0.0 final", (1, 0, 0)),
("b$root@1.0.0 final", (1, 0, 0)),
("c$a@1.0.0 final", (1, 0, 0)),
("c$b@1.0.0 final", (1, 0, 0)),
("x$a@1.0.0 final", (1, 0, 0)),
("x$b@1.0.0 final", (2, 0, 0)),
]),
); |
|
So that is a solution. It implies that Of course, the cost of |
Isn't it already the case with cargo today? I mean if the two versions of "x" span different major buckets I thought cargo was ok with this? Of course if they were within the same major bucket, cargo would not have a solution, while this would still has one. To me this can change by simply imposing the bucket constraint in addition to the public/private constraint. Meaning, instead of each |
|
I think the technique from this section is useful, and I am very happy you are documenting it! (Whether or not it is useful to Cargo.)
Cargo ends up putting |
ohhh right. I mixed the fact that there can be two versions of x with the fact that there can be two compilations (with different deps) for the same version. And now I remember that resolver v2 is especially useful for that situation arise quite often with cargo workspaces with different set of features. |
|
One interesting thing with the technique described in the public/private section of the guide, is that it will tell you the version you need for the subgraph of each private dependency. So for example, with that dependency graph (equal signs are private deps): ==f1
/
r---a1---b1---c1---d1
\ \
==e1 ==g1
\
--h1---i1===d2it will tell you the solution is: ("root$root@1.0.0", (1, 0, 0)),
("a$root@1.0.0", (1, 0, 0)),
("b$root@1.0.0$a@1.0.0", (1, 0, 0)),
("c$root@1.0.0$a@1.0.0$b@1.0.0", (1, 0, 0)),
("d$root@1.0.0$a@1.0.0$b@1.0.0$c@1.0.0", (1, 0, 0)),
("e$a@1.0.0", (1, 0, 0)),
("f$b@1.0.0", (1, 0, 0)),
("g$c@1.0.0", (1, 0, 0)),
("h$a@1.0.0", (1, 0, 0)),
("i$a@1.0.0", (1, 0, 0)),
("d$i@1.0.0", (2, 0, 0)),So for the subgraph of "root", you need "a", "b", "c", and "d" at versions 1. For the subgraph of "a" you need "e" at version 1, ... for the subgraph "i" you need "d" at version 2. |
I would tend to believe that the intent was not to duplicate deps. Because when we think about it, it's like saying that there can be only one version per major bucket, and here we have two for "c". Well, two times the same version, but two versions anyway, and I suppose that's what the "max one version per major bucket" rule is trying to avoid? |
With the current seed markers implementation, it is not possible to prevent duplicate versions in two different public subgraphs, as if we wanted to prevent a duplicate C@1 in you example, one for each version of X. Or at least, I have tried to think of ways to add this restriction and I can't think of one that would work. It's possible to prevent different versions (as in one per bucket), but not the same ones. That is because the conversion from
since the two are contradictory. But something would be possible if instead of generating
And with the dependency ranges approach, it would be instead:
So now instead of requiring "b$seed1" and "b$seed2" at the exact same version 1, we require both in range 1..2. We could then pick version 1 for "b$seed1". Then when asking about its dependencies, instead of saying it has no dependencies, it could say it is incompatible with each of the other existing seed variants for that same version. I can see two problems though.
Otherwise, we need an immutable way of expressing those incompatibilities. I think that could also be done via different virtual versions generated on the fly by the provider. For each package version, we create a unique "package@v" package. This package would exist in one virtual version per seed. I think that would be backtracking-proof and not even require terms. |
|
I think this section is now ready for review. It does not cover exactly the public/private feature of the rust RFC but is interesting on its own. |
|
Sorry for the delayed review. This is definitely interesting and worth documenting in the guide! |
|
The "seed" method is sound, but overly conservative. The public dependency graph does can have multiple roots per connected component, and each of those roots induces via transitive closure something that must be coherent (single versions), but where those single-root closures only partially overlap, they need be only partially coherent. Concretely the quasi-diamond: is fine, even if a and b are resolved to get the "same" d. |
|
I don't understand the "partial" overlap argument, could you reformulate? The quasi-diamond described above does not sound fine to me since --d
/
==b---x1
a<
==c---x2
\
--d@Ericson2314 I did not mention it explicitly in the rust RFC issue but this section does not correspond exactly to the behavior of the public/private described for cargo. This is more of a "pure" public/private scheme. Adjustments are possible, and some are discussed in messages above like this one to adapt it to some of cargo specifics. |
|
Sorry, my diamond was wrong, but more indirection should fix it: the "views" that must be consistent are |
|
I don't see the problem. We will end up with 2 independent copies of |
|
Well ti's the fate of OK, so now I mentioned
Each of those views must be assigned consistent versions, and those views overlap. Solving that, we must have one globally unique |
|
The underlying "Conflict-driven clause learning" I think can handle this fine, but trying to smash this into PubGrub as is strikes me as a awkward. It's a non-trivial upfront investment, but I feel like the right approach is this:
|
|
I think I understand your point but I'm not sure having the minimal amount of clauses is that much efficient when building these clauses is more expensive (traversing the graph). It might be, then great, but another thing is I don't have any experience in SMT solver theory or CDCL theory except what I had to understand while building pubgrub-rs and writing its guide. Though if somebody implements a CDCL crate and shows us how to replace parts of pubgrub with it, while having comparable or better performances that would be very welcome! |
|
By the way, from what I gathered, pugrub uses CDNL instead of CDCL. CDCL builds a solution to satisfy a conjunctive normal form (conjunction of clauses) while CDNL builds a solution to unsatisfy a disjunctive normal form (disjunction of nogoods). In addition, pugrub is a lazy CDNL algorithm since the disjunction of nogoods (incompatibilities) is built on the fly with the solution. |
|
That is an interesting case! I think I will need to understand much better how The talk of SMT theorys in the abstract tends to make my head spin. But I think I am agreeing with you if I say, a good solution for some of the harder resolvers will involve some kind of lower level API to the pubgrub algorithm, one based on "Incompatibilitys" directly instead of using dependencies. That is not surprising to me the point of the "Advanced usage and limitations" section of the guide is to explore what changes to the API may be needed to best accommodate all users. |
|
@Eh2406 I have update a bit the explanation related to seed markers. Could you re-read the entire section https://github.com/pubgrub-rs/guide/blob/public-private/src/limitations/public_private.md and let me know if things are clearer or not? |
|
This looks good to me, lets merge it! |
|
@mpizenberg my concern was more soundness and completeness than efficiency. For that last example (assuming I didn't mess it up again :)) what should happen with the current algorithm? |
So I've just added two tests to see how the algorithm reacts. In the first test I made everything depends on the same version of "f", and in the second test, "d" and "e" depend on different versions of "f". /// first test
///
/// --f --f
/// / /
/// --b===d---x1
/// a<
/// --c===e---x2
/// \ \
/// --f --f
/// second test
///
/// --f* --f1
/// / /
/// --b======d
/// a<
/// --c======e
/// \ \
/// --f* --f2When the same version of "f" is required, it happily finds a solution. However, if "d" depends on "f1" and "e" depends on "f2" then the solver fails with the following error message: Because f$b@1.0.0 1.0.0 depends on f$b@1.0.0 constraint 1.0.0 and f$a@1.0.0$b@1.0.0 [ 0.0.0, 1.0.0 [ [ 1.0.1, ∞ [ depends on f$b@1.0.0 constraint 2.0.0, f$a@1.0.0$b@1.0.0 [ 0.0.0, 1.0.0 [ [ 1.0.1, ∞ [, f$b@1.0.0 1.0.0 are incompatible.
And because d$b@1.0.0 depends on f$b@1.0.0 1.0.0, f$a@1.0.0$b@1.0.0 [ 0.0.0, 1.0.0 [ [ 1.0.1, ∞ [, d$b@1.0.0 ∗ are incompatible.
And because b$a@1.0.0 1.0.0 depends on f$a@1.0.0$b@1.0.0 and b$a@1.0.0 1.0.0 depends on d$b@1.0.0, b$a@1.0.0 1.0.0 depends on f$a@1.0.0$b@1.0.0 1.0.0.
And because f$a@1.0.0$b@1.0.0 1.0.0 depends on f$a@1.0.0 constraint 1.0.0 and f$a@1.0.0$c@1.0.0 2.0.0 depends on f$a@1.0.0 constraint 2.0.0, b$a@1.0.0 ∗, f$a@1.0.0$c@1.0.0 2.0.0 are incompatible. (1)
Because f$c@1.0.0 2.0.0 depends on f$c@1.0.0 constraint 2.0.0 and f$a@1.0.0$c@1.0.0 [ 0.0.0, 2.0.0 [ [ 2.0.1, ∞ [ depends on f$c@1.0.0 constraint 1.0.0, f$c@1.0.0 2.0.0, f$a@1.0.0$c@1.0.0 [ 0.0.0, 2.0.0 [ [ 2.0.1, ∞ [ are incompatible.
And because e$c@1.0.0 depends on f$c@1.0.0 2.0.0, e$c@1.0.0 ∗, f$a@1.0.0$c@1.0.0 [ 0.0.0, 2.0.0 [ [ 2.0.1, ∞ [ are incompatible.
And because c$a@1.0.0 1.0.0 depends on e$c@1.0.0 and c$a@1.0.0 1.0.0 depends on f$a@1.0.0$c@1.0.0, c$a@1.0.0 1.0.0 depends on f$a@1.0.0$c@1.0.0 2.0.0.
And because b$a@1.0.0 ∗, f$a@1.0.0$c@1.0.0 2.0.0 are incompatible (1), b$a@1.0.0 ∗, c$a@1.0.0 1.0.0 are incompatible.
And because a$a@1.0.0 1.0.0 depends on c$a@1.0.0 and a$a@1.0.0 1.0.0 depends on b$a@1.0.0, a$a@1.0.0 1.0.0 is forbidden.If I rewrite this error message in a slightly more readable versions, that gives: Because f$b @ 1.0.0 is marked with the b seed @ 1.0.0 and f$a$b for all versions except 1.0.0 is marked with the b seed @ 2.0.0, f$b @ 1.0.0 is incompatible with all versions except 1.0.0 of f$a$b.
And because d$b depends on f$b @ 1.0.0, d$b is also incompatible with all except 1.0.0 of f$a$b.
And because b$a depends on both f$a$b and d$b, b$a depends on f$a$b @ 1.0.0.
And because f$a$b @ 1.0.0 is marked with the a seed @ 1.0.0 and f$a$c 2.0.0 is marked by the a seed @ 2.0.0, b$a and f$a$c @ 2.0.0 are incompatible (1)
Because f$c @ 2.0.0 is marked with the c seed @ 2.0.0 and f$a$c for all versions except 2.0.0 is marked with the c seed @ 1.0.0, f$c @ 2.0.0 is incompatible with all versions except 2.0.0 of f$a$c.
And because e$c depends on f$c @ 2.0.0, e$c is also incompatible with all except 2.0.0 of f$a$c.
And because c$a depends on both e$c and f$a$c, c$a depends on f$a$c @ 2.0.0.
And because b$a and f$a$c 2.0.0 are incompatible (1), b$a and c$a are incompatible.
And because a$a depends on both c$a and b$a, a$a is forbidden. |
|
Thanks! I'm surprised the first one works out after all. I guess don't wait to merge on my account :). I'll try to go play around with it. |
|
Ah, so I see there was already The current algorithm has views that are |
|
I think the current algorithm is correct for It's only |
|
Sorry @Ericson2314 I don't agree with you. According to the semantics of public/private dependencies as presented in the guide section, it is normal that the first test passes. Just as it is normal that the
There always is a parent between two views, so please be more specific. Is that a direct parent you mean? I strongly believe the algorithm is correct. |
|
Wow, I am really sorry and embarrassed for repeatedly making these simple basic mistakes in this thread. You're right about the tests, I misread the |
No description provided.