diff --git a/TODO b/TODO index cefd647..8600cc9 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,3 @@ -# TODO: +# Unsatisfable dependencies are not valid candidates, please drop them from upgrade_me (waiting reply from aba) # Add support for old libraries (introducing a new operation: remove single binary) # Add support for udeb - -# DONE: -# Conflicts: work, we should try to solve the problem in a smarter way (without computing the full closed graph) -# Add support for calling britney from command line diff --git a/britney.py b/britney.py index b91e683..798578c 100644 --- a/britney.py +++ b/britney.py @@ -243,6 +243,7 @@ class Britney: self.approvals = self.read_approvals(self.options.tpu) self.hints = self.read_hints(self.options.unstable) self.excuses = [] + self.dependencies = {} def __parse_arguments(self): """Parse the command line arguments @@ -762,7 +763,7 @@ class Britney: return (len(packages) > 0, packages) - def excuse_unsat_deps(self, pkg, src, arch, suite, excuse=None, excluded=[], conflicts=False): + def excuse_unsat_deps(self, pkg, src, arch, suite, excuse, excluded=[], conflicts=False): """Find unsatisfied dependencies for a binary package This method analyzes the dependencies of the binary package specified @@ -779,6 +780,7 @@ class Britney: # local copies for better performances parse_depends = apt_pkg.ParseDepends get_dependency_solvers = self.get_dependency_solvers + strict = not self.options.compatible # analyze the dependency fields (if present) for type in ('Pre-Depends', 'Depends'): @@ -789,11 +791,15 @@ class Britney: # for every block of dependency (which is formed as conjunction of disconjunction) for block, block_txt in zip(parse_depends(binary_u[type_key]), binary_u[type_key].split(',')): # if the block is satisfied in testing, then skip the block - solved, packages = get_dependency_solvers(block, arch, 'testing', excluded, strict=(excuse == None)) - if solved: continue + solved, packages = get_dependency_solvers(block, arch, 'testing', excluded, strict=strict) + if solved: + for p in packages: + if p not in self.binaries[suite][arch][0]: continue + excuse.add_sane_dep(self.binaries[suite][arch][0][p]['source']) + continue # check if the block can be satisfied in unstable, and list the solving packages - solved, packages = get_dependency_solvers(block, arch, suite) + solved, packages = get_dependency_solvers(block, arch, suite, [], strict=strict) packages = [self.binaries[suite][arch][0][p]['source'] for p in packages] # if the dependency can be satisfied by the same source package, skip the block: @@ -803,6 +809,8 @@ class Britney: # if no package can satisfy the dependency, add this information to the excuse if len(packages) == 0: excuse.addhtml("%s/%s unsatisfiable %s: %s" % (pkg, arch, type, block_txt.strip())) + if arch not in self.options.break_arches: excuse.add_unsat_dep(arch) + continue # for the solving packages, update the excuse to add the dependencies for p in packages: @@ -1791,6 +1799,11 @@ class Britney: final result is successful, otherwise (None, None). """ extra = [] + deferred = [] + skipped = [] + mark_passed = False + position = len(packages) + nuninst_comp = self.get_nuninst() # local copies for better performances @@ -1801,12 +1814,34 @@ class Britney: nobreakall_arches = self.options.nobreakall_arches new_arches = self.options.new_arches break_arches = self.options.break_arches + dependencies = self.dependencies + compatible = self.options.compatible output.write("recur: [%s] %s %d/%d\n" % (",".join(self.selected), "", len(packages), len(extra))) # loop on the packages (or better, actions) while packages: pkg = packages.pop(0) + + # this is the marker for the first loop + if not mark_passed and position < 0: + mark_passed = True + packages.extend(deferred) + del deferred + continue + else: position -= 1 + + # defer packages if their dependency has been already skipped + if not compatible and not mark_passed: + defer = False + for p in dependencies.get(pkg, []): + if p in skipped: + deferred.append(pkg) + skipped.append(pkg) + defer = True + break + if defer: continue + output.write("trying: %s\n" % (pkg)) better = True @@ -1890,7 +1925,10 @@ class Britney: output.write("skipped: %s (%d <- %d)\n" % (pkg, len(extra), len(packages))) output.write(" got: %s\n" % (self.eval_nuninst(nuninst, "/" in pkg and nuninst_comp or None))) output.write(" * %s: %s\n" % (arch, ", ".join(sorted([b for b in broken if b not in nuninst_comp[arch]])))) + extra.append(pkg) + if not mark_passed: + skipped.append(pkg) # undo the changes (source) for k in undo['sources'].keys(): @@ -1971,6 +2009,37 @@ class Britney: output.close() self.__log("Test completed!", type="I") + def sort_actions(self): + """Sort actions in a smart way + + This method sorts the list of actions in a smart way. In details, it uses + as base sort the number of days the excuse is old, then reordering packages + so the ones with most reverse dependencies are at the end of the loop. + If an action depends on another one, it is put after it. + """ + upgrade_me = [x.name for x in self.excuses if x.name in self.upgrade_me] + for e in self.excuses: + if e.name not in upgrade_me: continue + # try removes at the end of the loop + elif e.name[0] == '-': + upgrade_me.remove(e.name) + upgrade_me.append(e.name) + # otherwise, put it in a good position checking its dependencies + else: + pos = [] + udeps = [upgrade_me.index(x) for x in e.deps if x in upgrade_me and x != e.name] + if len(udeps) > 0: + pos.append(max(udeps)) + sdeps = [upgrade_me.index(x) for x in e.sane_deps if x in upgrade_me and x != e.name] + if len(sdeps) > 0: + pos.append(min(sdeps)) + if len(pos) == 0: continue + upgrade_me.remove(e.name) + upgrade_me.insert(max(pos)+1, e.name) + self.dependencies[e.name] = e.deps + # replace the list of actions with the new one + self.upgrade_me = upgrade_me + def main(self): """Main method @@ -1980,17 +2049,7 @@ class Britney: if not self.options.actions: self.write_excuses() if not self.options.compatible: - upgrade_me = [x.name for x in self.excuses if x.name in self.upgrade_me] - for e in self.excuses: - if e.name not in upgrade_me: continue - if e.name.startswith("-"): - upgrade_me.remove(e.name) - upgrade_me.append(e.name) - if len(e.deps) > 0: - upgrade_me.remove(e.name) - pos = max([upgrade_me.index(x) for x in e.deps if x in upgrade_me]) - upgrade_me.insert(pos+1, e.name) - self.upgrade_me = upgrade_me + self.sort_actions() else: self.upgrade_me = self.options.actions.split() self.upgrade_testing() diff --git a/excuse.py b/excuse.py index 0e56344..c750547 100644 --- a/excuse.py +++ b/excuse.py @@ -54,7 +54,9 @@ class Excuse: self.invalid_deps = [] self.deps = [] + self.sane_deps = [] self.break_deps = [] + self.unsat_deps = {} self.bugs = [] self.htmlline = [] @@ -87,11 +89,19 @@ class Excuse: """Add a dependency""" if name not in self.deps: self.deps.append(name) + def add_sane_dep(self, name): + """Add a sane dependency""" + if name not in self.sane_deps: self.sane_deps.append(name) + def add_break_dep(self, name, arch): """Add a break dependency""" if (name, arch) not in self.break_deps: self.break_deps.append( (name, arch) ) + def add_unsat_dep(self, arch): + """Add a flag for unsatisfied dependencies""" + self.unsat_deps[arch] = True + def invalidate_dep(self, name): """Invalidate dependency""" if name not in self.invalid_deps: self.invalid_deps.append(name)