class Plan(set): @staticmethod def _parse_plan_file(path, cpp_opts=[]): """process plan through cpp, then parse it and add packages to plan """ processed_plan = cpp.cpp(path, cpp_opts) packages = set() for expr in processed_plan.splitlines(): expr = re.sub(r'#.*', '', expr) expr = expr.strip() if not expr: continue if expr.startswith("!"): package = expr[1:] if package in packages: packages.remove(package) else: package = expr packages.add(package) return packages @classmethod def init_from_file(cls, plan_file_path, cpp_opts=[], pool_path=None): return cls(cls._parse_plan_file(plan_file_path, cpp_opts), pool_path) def __new__(cls, iterable=(), pool_path=None): return set.__new__(cls, iterable) def __init__(self, iterable=(), pool_path=None): set.__init__(self, iterable) if pool_path: from pyproject.pool.pool import Pool self.pool = Pool(pool_path) else: self.pool = None self.packageorigins = PackageOrigins() def _get_new_deps(self, pkg_control, old_deps, depend_fields): def parse_depends(val): if val is None or val.strip() == "": return [] return re.split("\s*,\s*", val.strip()) new_deps = set() raw_depends = [] for field_name in depend_fields: raw_depends += parse_depends(pkg_control.get(field_name)) for raw_depend in raw_depends: if "|" not in raw_depend: new_deps.add(Dependency(raw_depend)) continue alternatives = [ Dependency(alt) for alt in raw_depend.split("|") ] # continue if any of the alternatives are already in resolved or unresolved sets if set(alternatives) & old_deps: continue # add the first alternative that exists in the pool to set of new dependencies for alternative in alternatives: if self.pool.exists(alternative.name): new_deps.add(alternative) break for dep in new_deps: self.packageorigins.add(dep.name, pkg_control.get('Package')) return new_deps @staticmethod def _get_provided(pkg_control): raw_provided = pkg_control.get('Provides') if raw_provided is None or raw_provided.strip() == "": return set() return set(re.split("\s*,\s*", raw_provided.strip())) def dctrls(self): """return plan dependencies control file info""" toquery = set([ Dependency(pkg) for pkg in self ]) packages = PackageGetter(toquery, self.pool) dctrls = {} for dep in toquery: package_path = packages[dep] if package_path is None: raise Error('could not find package', dep.name) dctrls[dep] = debinfo.get_control_fields(package_path) dctrls[dep]['Filename'] = basename(package_path) return dctrls def resolve(self): """resolve plan dependencies recursively -> return spec""" spec = Spec() if not self.pool: return list(self) resolved = set() missing = set() provided = set() def reformat2dep(pkg): if '=' not in pkg: return pkg name, version = pkg.split("=", 1) return "%s (= %s)" % (name, version) unresolved = set([ Dependency(reformat2dep(pkg)) for pkg in self ]) while unresolved: # get newest package versions of unresolved dependencies from the pool # and pray they don't conflict with our dependency restrictions packages = PackageGetter(unresolved, self.pool) new_deps = set() for dep in unresolved: package_path = packages[dep] if not package_path: continue pkg_control = debinfo.get_control_fields(package_path) version = pkg_control['Version'] if not dep.is_version_ok(version): raise Error("dependency '%s' incompatible with newest pool version (%s)" % (dep, version)) spec.add(dep.name, version) resolved.add(dep) new_deps |= self._get_new_deps(pkg_control, resolved | unresolved | new_deps, dep.fields) provided |= self._get_provided(pkg_control) unresolved = new_deps - resolved missing = (missing | packages.missing) - provided if missing: def get_origins(dep): # trace the package origins origins = [] while dep: try: dep = self.packageorigins[dep][0] origins.append(dep) except KeyError: dep = None return origins brokendeps = [] for dep in missing: brokendeps.append("%s (%s)" % (dep, " -> ".join(get_origins(dep)))) raise Error("broken dependencies: " + "\n".join(brokendeps)) return spec