-
Notifications
You must be signed in to change notification settings - Fork 696
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
Per-component dependency solving #4087
Comments
Here are my thoughts, so far. At a minimum, the solver should keep track of dependencies between packages' components. (I think that it currently tracks dependencies from components to packages.) Then it can stop requiring dependencies of components that aren't needed, and ensure that required components are buildable. The solver should still ensure that all components within a package are consistent (have the same version and flag assignments), unless they have different qualifiers. Maybe we can add a flag or use private dependencies to relax that restriction later. |
@grayjay, thanks for making this ticket. Your plan seems eminently reasonable. Go for it! |
Here is a first pass at a design: Summary
Tree structure
Variables
Index conversion
Dependency graph
Custom setup validation steps
Component dependency validation steps
Enabling tests/benchmarks
Internal dependencies
More questions
/cc @kosmikus @ezyang @Ericson2314 @dcoutts @edsko @hvr @23Skidoo |
Thanks, this looks great.
Per-component occurs if and only if:
The canonical source of truth is
But note that to fix things like #3732 we'll have to relax how we do the constraints here. I guess this is covered by what you say later: "If we
At the moment, we won't know anything about executables, but we don't have to, because new-build will never have those as "installed" packages. Internal libraries pose a special problem, because they show up in the installed package index (I recently committed a hack to handle this case). With our current featureset, we are allowed to assume that any internal library in the installed package index can only be reached by a corresponding "public" library from the package (so the "correct" way to handle internal libraries is to bundle them all up together with the public library into one 'package node'). The hacked code doesn't actually do this but that's what should be done morally.
Perhaps there should be a distinction made between "build targets" (what am I going to build this round) and "solver targets" (please work hard to dep solve so that X is buildable.) The latter is stable and shouldn't change even if you decide to build different things. This should solve the next bullet point too. |
Thanks for the feedback!
I see that I had this part backwards. These rules simplify things, because the solver doesn't need to choose whether to use per-component build for each package; there is only one choice. In that case, I think that we don't need to add the "setup script features" variables, for now. I had also incorrectly assumed that every package that ignores dependencies of unbuildable components also supports per-component build. Since the two attributes are not linked, I think we should handle #3881 separately.
I'm not sure that we should make cabal find a solution for the example at the top of #3732, which only constrains Per-component solving would still allow more flexibility in less extreme cases, even if it always applied all build-depends constraints. Take this example:
Say that Qualified goals would only help by allowing components that were independently solvable to be solvable together with different qualifiers (possibly using different versions of the package containing the components).
Okay, so cabal will behave as if those package instances provided libraries but not executables.
I'm not sure I understand this part. Can we always reach all of the internal libraries if we have the main library from the installed package index? I think that is all the solver needs to do.
That distinction makes sense. I still don't know how we should choose those two sets of targets, though. For "solver targets", maybe we should just give the solver a list of packages to solve for and apply a preference to build all components within each of those packages. Is there any situation where we would want to give the solver a subset of a package's components as targets? |
re
IMO a better way to avoid duplication is to make it more explicit that multiple components ought to share common dependencies/constraints via the likes of #2832, and then we could unlock/enable per-component solving also in the aforementioned case when a high enough |
Yeah, but I think the converse is true (per-component = correct handling of non-buildable components.)
Not necessarily. For example, we may have registered an internal library which was only linked by the test suite, but not by the actual public library of a package. @dcoutts would probably say that this just means we shouldn't have registered it but it seems better to me to not assume any relationship without tracing the dependency relations.
Yeah I'm not sure what the default should be. What we have right now is best effort which seems to work decently well, so maybe every component would have a designation like, "never", "best-effort", "always", with everything defaulting to best-effort. |
…askell#4161). The solver already detected cycles involving more than one package, but it allowed dependencies between components within a package. This commit treats a dependency between a package's setup script and library as a cycle in order to allow the solver to backtrack and try to break the cycle. A more thorough solution would involve tracking all dependencies between components, as in haskell#4087. This commit also fixes the internal error in issue haskell#4980.
…askell#4161). The solver already detected cycles involving more than one package, but it allowed dependencies between components within a package. This commit treats a dependency between a package's setup script and library as a cycle in order to allow the solver to backtrack and try to break the cycle. A more thorough solution would involve tracking all dependencies between components, as in haskell#4087. This commit also fixes the internal error in issue haskell#4980.
This commit generalizes the fix for issue haskell#4781 (e86f838) by tracking dependencies on components instead of dependencies on executables. Associating each dependency with a component also moves towards the design for component-based dependency solving described in issue haskell#4087.
This commit generalizes the fix for issue haskell#4781 (e86f838) by tracking dependencies on components instead of dependencies on executables. That means that the solver always checks whether a package contains a library before using it to satisfy a build-depends dependency. If a version of a package doesn't contain a library, the solver can try other versions. Associating each dependency with a component also moves towards the design for component-based dependency solving described in issue haskell#4087.
This commit generalizes the fix for issue #4781 (e86f838) by tracking dependencies on components instead of dependencies on executables. That means that the solver always checks whether a package contains a library before using it to satisfy a build-depends dependency. If a version of a package doesn't contain a library, the solver can try other versions. Associating each dependency with a component also moves towards the design for component-based dependency solving described in issue #4087.
After #4884 and #5304, the solver stores the two components that are involved in each dependency. The dependent component is a |
This commit generalizes the fix for issue #4781 (e86f838) by tracking dependencies on components instead of dependencies on executables. That means that the solver always checks whether a package contains a library before using it to satisfy a build-depends dependency. If a version of a package doesn't contain a library, the solver can try other versions. Associating each dependency with a component also moves towards the design for component-based dependency solving described in issue #4087. (cherry picked from commit 6efb5e2)
Lack of this feature required to split of test-suites and benchmarks from IMO, not having this in Is there anything one can do to make this happen sooner, than later. |
I would be very happy to teach Cabal cross immediately if only this were done, too. |
I'm less familiar with the change that would be required to allow cycles between packages, but I think it could be implemented separately from the other parts of this feature, like the changes to flag variables. Unless I'm missing something, it would mainly require changes to cycle detection and the conversion from the solver's dependency graph to the one used outside of the solver:
I'm not sure how much code outside of the solver will also need to be changed to maintain the dependencies between components and ensure that components are built in the correct order. I'm also not sure if there will be other issues with having cycles between packages in the solver, such as infinite loops. I don't think I'll have time to work on this feature in the near future, though I could try to answer questions or review code if anyone else wants to implement it. |
I think the idea is that the cycles really and actually go away once we track on the level of components. No special-cases are needed. I'm not sure what @phadej was thinking but I believe we need private dependencies for changes |
I meant that I was wondering whether there is a use case for other types of cycles now and whether we should disallow them in order to simplify install plans. We can always remove the restriction later, but it would be very hard to move in the opposite direction.
I'm not sure I understand. I think that private dependencies is a separate feature that doesn't require per-component dependency solving. The approach taken in #3422 is like a special case of private dependencies. I think that per-component solving would only help solve the test suite cycle issue if we used it to change the solver's cycle detection to use components instead of packages. It would cause rebuilds of the test framework, though. |
I agree let's not overcommit to allowing things we will want to band later. I'm not fan of "Postel's law"'s over-application with these matters :).
OK glad we agree on that.
Right if #3422 is a special case of cycles and private dependencies, I rather grow it into "private dependencies in general" rather than "cycles in general".
Totally agreed. @phadej's changes to containers (make a package under a different name in other cabal project) both avoid the cycles and avoid the extra test rebuilds. I wanted to point out that per-component therefore is not enough on its own to obviate the renamed package---need the private dependencies part to avoid the rebuilds too or there's a usability regression. |
I just ran into this while trying to make |
@isovector None of the changes to cycle detection have been implemented yet, so we could definitely use a hand! I gave a summary of what I think needs to be done to only update the cycle detection in #4087 (comment). I'm not sure if it would require more changes outside of the solver, though. |
I don't think there is a clear design for component-based solving yet, so I wanted to create an issue where we can discuss it. #1575 addresses circular dependencies in test suites but is moving towards a different solution.
Related issues:
#779, #3978, #3492 (comment) - solver doesn't reject configurations that require unbuildable/non-existent components
#1575 - cabal can't handle cycles between packages that are not cycles between components
#2725, #5413 - cabal requires dependencies of components that aren't being built
#3662 - more per-component support
#3263 - fine-grained dependencies
The text was updated successfully, but these errors were encountered: