pubgrub
Public API for the PubGrub version solver.
This module provides the dependency provider interface, an offline provider
for tests and small graphs, and the resolve entrypoint.
Overview
PubGrub finds a set of package versions that satisfy all constraints and, when it cannot, returns a derivation tree that explains the conflict.
You provide:
- a
DependencyProviderthat can pick versions and read dependencies - a total ordering for versions via
ranges.Compare(v)
The solver returns a dict.Dict(p, v) mapping selected packages to versions.
Quick start (offline provider)
import pubgrub
import pubgrub/version
import pubgrub/version_ranges as ranges
pub fn example() {
let compare = version.compare
let provider =
pubgrub.offline_new()
|> pubgrub.offline_add_dependencies("root", version.new(1, 0, 0), [
#("foo", ranges.between(compare, version.new(1, 0, 0), version.new(2, 0, 0))),
])
|> pubgrub.offline_add_dependencies("foo", version.new(1, 0, 0), [])
let dp = pubgrub.offline_provider(provider, compare)
pubgrub.resolve(dp, "root", version.new(1, 0, 0))
}
Provider design
DependencyProvider is intentionally generic. A real provider typically:
- filters available versions by the supplied range
- chooses a preferred version (often highest)
- returns dependencies for that version
prioritize and compare_priority determine which package is selected next.
Use a stable priority function to keep resolutions deterministic.
Pitfalls and tips
- Always pass a total ordering for versions. Inconsistent comparisons can lead to incorrect results or non-termination.
- Error handling:
NoSolutioncarries a derivation tree you can format withpubgrub/report.
Related modules
pubgrub/version_ranges: range operationspubgrub/term: term operationspubgrub/report: conflict reporting
Types
Dependencies for a specific package version.
pub type Dependencies(p, v, m) {
Unavailable(m)
Available(dict.Dict(p, version_ranges.Ranges(v)))
}
Constructors
-
Unavailable(m) -
Available(dict.Dict(p, version_ranges.Ranges(v)))
Interface for querying package versions and their dependencies.
pub type DependencyProvider(p, v, m, err, priority) {
DependencyProvider(
compare: fn(v, v) -> order.Order,
compare_priority: fn(priority, priority) -> order.Order,
prioritize: fn(
p,
version_ranges.Ranges(v),
PackageResolutionStatistics,
) -> priority,
choose_version: fn(p, version_ranges.Ranges(v)) -> Result(
option.Option(v),
err,
),
get_dependencies: fn(p, v) -> Result(
Dependencies(p, v, m),
err,
),
should_cancel: fn() -> Result(Nil, err),
)
}
Constructors
-
DependencyProvider( compare: fn(v, v) -> order.Order, compare_priority: fn(priority, priority) -> order.Order, prioritize: fn( p, version_ranges.Ranges(v), PackageResolutionStatistics, ) -> priority, choose_version: fn(p, version_ranges.Ranges(v)) -> Result( option.Option(v), err, ), get_dependencies: fn(p, v) -> Result(Dependencies(p, v, m), err), should_cancel: fn() -> Result(Nil, err), )
In-memory dependency provider for tests and simple usage.
pub type OfflineDependencyProvider(p, v) {
OfflineDependencyProvider(
dependencies: dict.Dict(
p,
dict.Dict(v, dict.Dict(p, version_ranges.Ranges(v))),
),
)
}
Constructors
-
OfflineDependencyProvider( dependencies: dict.Dict( p, dict.Dict(v, dict.Dict(p, version_ranges.Ranges(v))), ), )
Conflict statistics used by prioritize.
pub type PackageResolutionStatistics {
PackageResolutionStatistics(
unit_propagation_affected: Int,
unit_propagation_culprit: Int,
dependencies_affected: Int,
dependencies_culprit: Int,
)
}
Constructors
-
PackageResolutionStatistics( unit_propagation_affected: Int, unit_propagation_culprit: Int, dependencies_affected: Int, dependencies_culprit: Int, )
Error variants produced by the solver.
pub type PubGrubError(p, v, m, err) {
NoSolution(report.DerivationTree(p, v, m))
ErrorRetrievingDependencies(
package: p,
version: v,
source: err,
)
ErrorChoosingVersion(package: p, source: err)
ErrorInShouldCancel(source: err)
}
Constructors
-
NoSolution(report.DerivationTree(p, v, m)) -
ErrorRetrievingDependencies(package: p, version: v, source: err) -
ErrorChoosingVersion(package: p, source: err) -
ErrorInShouldCancel(source: err)
pub type SemanticVersion =
#(Int, Int, Int)
Values
pub fn conflict_count(stats: PackageResolutionStatistics) -> Int
Total conflict count from a statistics record.
pub fn offline_add_dependencies(
provider: OfflineDependencyProvider(p, v),
package: p,
version: v,
dependencies: List(#(p, version_ranges.Ranges(v))),
) -> OfflineDependencyProvider(p, v)
Add dependencies for a package version to an offline provider.
pub fn offline_new() -> OfflineDependencyProvider(p, v)
Create an empty offline provider.
pub fn offline_packages(
provider: OfflineDependencyProvider(p, v),
) -> List(p)
List packages known by an offline provider.
pub fn offline_provider(
provider: OfflineDependencyProvider(p, v),
compare: fn(v, v) -> order.Order,
) -> DependencyProvider(p, v, String, Nil, #(Int, Int))
Build a dependency provider backed by an offline provider.
pub fn offline_versions(
provider: OfflineDependencyProvider(p, v),
package: p,
) -> option.Option(List(v))
List available versions for a package, if present.
pub fn resolve(
dependency_provider: DependencyProvider(p, v, m, err, priority),
package: p,
version: v,
) -> Result(dict.Dict(p, v), PubGrubError(p, v, m, err))
Resolve a dependency graph starting from a root package and version.
pub fn semantic_version_new(
major: Int,
minor: Int,
patch: Int,
) -> #(Int, Int, Int)
pub fn semantic_version_to_string(
ver: #(Int, Int, Int),
) -> String