132 lines
3.4 KiB
Python
132 lines
3.4 KiB
Python
from collections import namedtuple
|
|
|
|
from packaging.specifiers import SpecifierSet
|
|
from packaging.version import Version
|
|
|
|
import resolvelib
|
|
|
|
index = """
|
|
first 1.0.0
|
|
second == 1.0.0
|
|
first 2.0.0
|
|
second == 2.0.0
|
|
third == 1.0.0
|
|
first 3.0.0
|
|
second == 3.0.0
|
|
third == 2.0.0
|
|
second 1.0.0
|
|
third == 1.0.0
|
|
second 2.0.0
|
|
third == 2.0.0
|
|
second 3.0.0
|
|
third == 3.0.0
|
|
third 1.0.0
|
|
third 2.0.0
|
|
third 3.0.0
|
|
"""
|
|
|
|
|
|
class Requirement(namedtuple("Requirement", "name specifier")): # noqa
|
|
def __repr__(self):
|
|
return "<Requirement({name}{specifier})>".format(
|
|
name=self.name, specifier=self.specifier
|
|
)
|
|
|
|
|
|
class Candidate(namedtuple("Candidate", "name version")): # noqa
|
|
def __repr__(self):
|
|
return "<{name}=={version}>".format(
|
|
name=self.name, version=self.version
|
|
)
|
|
|
|
|
|
def splitstrip(s, parts):
|
|
return [item.strip() for item in s.strip().split(maxsplit=parts - 1)]
|
|
|
|
|
|
def read_spec(lines):
|
|
candidates = {}
|
|
latest = None
|
|
for line in lines:
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if not line.startswith(" "):
|
|
name, version = splitstrip(line, 2)
|
|
version = Version(version)
|
|
latest = Candidate(name, version)
|
|
candidates[latest] = set()
|
|
else:
|
|
if latest is None:
|
|
raise RuntimeError(
|
|
"Spec has dependencies before first candidate"
|
|
)
|
|
name, specifier = splitstrip(line, 2)
|
|
specifier = SpecifierSet(specifier)
|
|
candidates[latest].add(Requirement(name, specifier))
|
|
return candidates
|
|
|
|
|
|
class Provider(resolvelib.AbstractProvider):
|
|
def __init__(self, index):
|
|
self.candidates = read_spec(index)
|
|
|
|
def identify(self, requirement_or_candidate):
|
|
return requirement_or_candidate.name
|
|
|
|
def get_preference(self, identifier, resolutions, candidates, information):
|
|
return sum(1 for _ in candidates[identifier])
|
|
|
|
def find_matches(self, identifier, requirements, incompatibilities):
|
|
name = identifier
|
|
return sorted(
|
|
c
|
|
for c in self.candidates
|
|
if all(self.is_satisfied_by(r, c) for r in requirements[name])
|
|
and all(c.version != i.version for i in incompatibilities[name])
|
|
)
|
|
|
|
def is_satisfied_by(self, requirement, candidate):
|
|
return (
|
|
candidate.name == requirement.name
|
|
and candidate.version in requirement.specifier
|
|
)
|
|
|
|
def get_dependencies(self, candidate):
|
|
return self.candidates[candidate]
|
|
|
|
|
|
class Reporter(resolvelib.BaseReporter):
|
|
def starting(self):
|
|
print("starting()")
|
|
|
|
def starting_round(self, index):
|
|
print(f"starting_round({index})")
|
|
|
|
def ending_round(self, index, state):
|
|
print(f"ending_round({index}, ...)")
|
|
|
|
def ending(self, state):
|
|
print("ending(...)")
|
|
|
|
def adding_requirement(self, requirement, parent):
|
|
print(f" adding_requirement({requirement}, {parent})")
|
|
|
|
def backtracking(self, candidate):
|
|
print(f" backtracking({candidate})")
|
|
|
|
def pinning(self, candidate):
|
|
print(f" pinning({candidate})")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from pprint import pprint
|
|
|
|
provider = Provider(index.splitlines())
|
|
reporter = Reporter()
|
|
resolver = resolvelib.Resolver(provider, reporter)
|
|
|
|
root_reqs = [Requirement("first", SpecifierSet())]
|
|
result = resolver.resolve(root_reqs)
|
|
|
|
pprint(result.mapping)
|