Version SAT
research.swtch.com
research.swtch.com
I'll note two things:
1. The backtracking solver isn't that simple. It's got a handful of pretty smart heuristics that we've added over time to help it get stuck.
2. It doesn't get stuck very often. It does happen. In particular, our ecosystem has one package that is very heavily depended on and also frequently ships breaking changes, which is always a recipe for version solver pain.
But pub works fine for probably >99% of invocations of it. Installing dependencies is a gating issue for getting any work done, so if pub was getting stuck often, users would be screaming at us.
Also, Natalie, the other half of the pub team, put together a comparable proof of version solving being equivalent to 3-SAT a year or two ago, but I can't seem to find it now.
Personally, I think the idea of treating breaking versions of packages as distinct and allowing them to co-exist is interesting but I worry about the usability impact of it. It will be very confusing for users if objects flow between those two packages and you get type errors like "Expected type Foo but got Foo." Good error messages can help a lot, though.
I assume you mean NOT get stuck? Or do you mean something else like settling on a solution?
Leaving my mistake to bear my shame publicly.
FWIW we've seen those kinds of type errors in Go already with vendoring, which lets you have multiple copies of the same package. Good errors are essential (and we only get them half right).
Thanks.
A group of community members have been working on a proposal for a package manager for Go. Sam Boyer also wrote an article about their progress here: https://blog.gopheracademy.com/advent-2016/saga-go-dependenc...
In the context of language package managers avoiding NP-completeness is interesting: if you have a global repository then you have to deal with conflicts in some way, however per-project repositories could try to get around limitation #4 (2 different versions of same package allowed to be installed). Whether it is desirable to do so is another matter though, you'd end up with various buggy versions of same package installed, never knowing whether you've fully upgraded to the fixed version...
Treating different major versions as different packages is interesting though, and happens to some extent in system packages already (e.g. both python2 and python3 can coexist)
For emphasis, because I think that's what should ideally happen.
On the other hand, if A and B provide (some sort of) libraries which C imports, then their dependencies may be exposed by appearing in the pool of libraries available to C. This is certainly the case for Haskell, where all of a library's dependencies accumulate in the ghc-pkg database and multiple versions of a library aren't allowed in the same program. As you say, such things are existing problems in external, language-specific tools, and aren't caused by Nix; although Nix may provide a useful abstraction to better manage such problems.
* There may be application-specific problems at runtime, e.g. if A and B try to de/serialise each other's data, but I'd count that as a bug: if A and B are meant to be standalone then they're buggy, as their I/O formats should be more robust; on the other hand, if A and B are meant to be used together then the packaging is buggy: A and B should expose their shared dependency as a parameter, and C should pass in the same version to both.
The reason this is tractable is that Nix packages are written in an expressive language. Most package definitions are actually functions, which accept their dependencies as arguments. Such functions provide flexibility, making it easy to switch out dependencies or perform other changes, and are often passed around using higher-order functions. The closest thing to dependency resolution is to take the fixed-point of a function, which is used to populate each package's dependency arguments using the other packages (which we're also populating the arguments of; this relies on Nix being lazy!).
For example, the `cabal2nix` tool can read a Haskell '.cabal' file and create a Nix function, which accepts dependencies and returns a package. We can turn this into a concrete package by specifying a version of Haskell to use (e.g. `haskell.ghc7103` for GHC 7.10.3, `haskell.ghc801` for GHC 8.0.1, `haskell.ghcjs` for GHCJS, etc.); each contains a higher-order function `callPackage`, which we pass our generated function to, along with any non-default overrides.
For example, we can run the command:
nix-shell -p "(haskell.ghc7103.callPackage ($(cabal2nix /path/to/project.cabal)) { some-dep = my-version-of-some-dep; })"
This will drop us into a bash shell where the Haskell project at /path/to/project.cabal has been built using GHC 7.10.3, with default versions of all dependencies except for "some-dep" which we override to be some other package. If, for example, our Haskell package defines a bunch of executables, they'll be available in this shell's $PATH.There are also higher-order functions like `ghcWithPackages` for defining Haskell environments with pre-populated package databases, `override` which takes the fixed-point of a set of packages (so overriding "foo" ensures that all packages depending on "foo" get our version, transitively), etc.
[1] https://www.continuum.io/blog/developer/new-advances-conda-0
https://blogs.oracle.com/barts/entry/satisfaction https://docs.oracle.com/cd/E36784_01/html/E36856/glulp.html
However, a large drawback to SAT solutions is friendly messaging when no solution is found; just as hard as doing the initial dependency analaysis if not more difficult.
There are also interesting corner cases such as "metapackages" (in Debian terms) and alternatives, etc.
https://docs.oracle.com/cd/E53394_01/html/E54820/dependtypes...
For pkg(5), almost all of the magic is here:
https://java.net/projects/ips/sources/pkg-gate/content/src/m...