mirror of
				https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
				synced 2025-11-04 10:34:05 +00:00 
			
		
		
		
	Merge trunk up to 2014-05-24
This commit is contained in:
		
						commit
						b3562dbfe8
					
				
							
								
								
									
										6
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								INSTALL
									
									
									
									
									
								
							@ -4,6 +4,6 @@ INSTALL for britney v2.0
 | 
			
		||||
Requirements:
 | 
			
		||||
-------------
 | 
			
		||||
 | 
			
		||||
  * Python 2.7                  aptitude install python2.7
 | 
			
		||||
  * Python APT/DPKG bindings    aptitude install python2.7-apt
 | 
			
		||||
 | 
			
		||||
  * Python 2.7                  aptitude install python
 | 
			
		||||
  * Python APT/DPKG bindings    aptitude install python-apt
 | 
			
		||||
  * Python YAML library         aptitude install python-yaml
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,9 @@ PARTIAL_UNSTABLE  = yes
 | 
			
		||||
# Output
 | 
			
		||||
NONINST_STATUS    = data/%(SERIES)/non-installable-status
 | 
			
		||||
EXCUSES_OUTPUT    = output/%(SERIES)/excuses.html
 | 
			
		||||
EXCUSES_YAML_OUTPUT = output/%(SERIES)/excuses.yaml
 | 
			
		||||
UPGRADE_OUTPUT    = output/%(SERIES)/output.txt
 | 
			
		||||
HEIDI_OUTPUT      = output/%(SERIES)/HeidiResult
 | 
			
		||||
DELTA_OUTPUT      = output/%(SERIES)/Delta
 | 
			
		||||
 | 
			
		||||
# List of release architectures
 | 
			
		||||
ARCHITECTURES     = amd64 arm64 armhf i386 powerpc ppc64el
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										186
									
								
								britney.py
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								britney.py
									
									
									
									
									
								
							@ -218,7 +218,9 @@ from hints import HintCollection
 | 
			
		||||
from britney_util import (old_libraries_format, same_source, undo_changes,
 | 
			
		||||
                          register_reverses, compute_reverse_tree,
 | 
			
		||||
                          read_nuninst, write_nuninst, write_heidi,
 | 
			
		||||
                          eval_uninst, newly_uninst, make_migrationitem)
 | 
			
		||||
                          eval_uninst, newly_uninst, make_migrationitem,
 | 
			
		||||
                          write_excuses, write_heidi_delta, write_controlfiles,
 | 
			
		||||
                          old_libraries, ensuredir)
 | 
			
		||||
from consts import (VERSION, SECTION, BINARIES, MAINTAINER, FAKESRC,
 | 
			
		||||
                   SOURCE, SOURCEVER, ARCHITECTURE, DEPENDS, CONFLICTS,
 | 
			
		||||
                   PROVIDES, RDEPENDS, RCONFLICTS, MULTIARCH, ESSENTIAL)
 | 
			
		||||
@ -227,10 +229,6 @@ from autopkgtest import AutoPackageTest, ADT_PASS, ADT_EXCUSES_LABELS
 | 
			
		||||
__author__ = 'Fabio Tranchitella and the Debian Release Team'
 | 
			
		||||
__version__ = '2.0'
 | 
			
		||||
 | 
			
		||||
def ensuredir(directory):
 | 
			
		||||
    if not os.path.isdir(directory):
 | 
			
		||||
        os.makedirs(directory)
 | 
			
		||||
 | 
			
		||||
class Britney(object):
 | 
			
		||||
    """Britney, the Debian testing updater script
 | 
			
		||||
    
 | 
			
		||||
@ -263,6 +261,8 @@ class Britney(object):
 | 
			
		||||
        apt_pkg.init()
 | 
			
		||||
        self.sources = {}
 | 
			
		||||
        self.binaries = {}
 | 
			
		||||
        self.all_selected = []
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.hints = self.read_hints(self.options.hintsdir)
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
@ -411,6 +411,9 @@ class Britney(object):
 | 
			
		||||
                 not getattr(self.options, k.lower()):
 | 
			
		||||
                setattr(self.options, k.lower(), v)
 | 
			
		||||
 | 
			
		||||
        if not hasattr(self.options, "heidi_delta_output"):
 | 
			
		||||
            self.options.heidi_delta_output = self.options.heidi_output + "Delta"
 | 
			
		||||
 | 
			
		||||
        # Sort the architecture list
 | 
			
		||||
        allarches = sorted(self.options.architectures.split())
 | 
			
		||||
        arches = [x for x in allarches if x in self.options.nobreakall_arches.split()]
 | 
			
		||||
@ -992,89 +995,6 @@ class Britney(object):
 | 
			
		||||
        return blocks
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def write_delta(self, filename):
 | 
			
		||||
        """Write the output delta
 | 
			
		||||
 | 
			
		||||
        This method writes the packages to be upgraded, in the form:
 | 
			
		||||
        <src-name> <src-version>
 | 
			
		||||
        or (if the source is to be removed):
 | 
			
		||||
        <src-name>
 | 
			
		||||
 | 
			
		||||
        The order corresponds to that shown in update_output.
 | 
			
		||||
        """
 | 
			
		||||
        self.__log("Writing delta to %s" % filename)
 | 
			
		||||
        ensuredir(os.path.dirname(filename))
 | 
			
		||||
        f = open(filename, "w")
 | 
			
		||||
 | 
			
		||||
        sources = self.sources['testing']
 | 
			
		||||
        for name in self.all_selected:
 | 
			
		||||
            if "/" in name:
 | 
			
		||||
                pkg_name, arch = name.split('/', 1)
 | 
			
		||||
                if pkg_name in sources:
 | 
			
		||||
                    f.write('%s %s\n' % (name, sources[pkg_name][VERSION]))
 | 
			
		||||
                else:
 | 
			
		||||
                    f.write('%s\n' % name)
 | 
			
		||||
            else:
 | 
			
		||||
                if name in sources:
 | 
			
		||||
                    f.write('%s %s\n' % (name, sources[name][VERSION]))
 | 
			
		||||
                else:
 | 
			
		||||
                    f.write('%s\n' % name)
 | 
			
		||||
 | 
			
		||||
        f.close()
 | 
			
		||||
 | 
			
		||||
    def write_controlfiles(self, basedir, suite):
 | 
			
		||||
        """Write the control files
 | 
			
		||||
 | 
			
		||||
        This method writes the control files for the binary packages of all
 | 
			
		||||
        the architectures and for the source packages.
 | 
			
		||||
        """
 | 
			
		||||
        sources = self.sources[suite]
 | 
			
		||||
 | 
			
		||||
        self.__log("Writing new %s control files to %s" % (suite, basedir))
 | 
			
		||||
        ensuredir(basedir)
 | 
			
		||||
        for arch in self.options.architectures:
 | 
			
		||||
            filename = os.path.join(basedir, 'Packages_%s' % arch)
 | 
			
		||||
            f = open(filename, 'w')
 | 
			
		||||
            binaries = self.binaries[suite][arch][0]
 | 
			
		||||
            for pkg in binaries:
 | 
			
		||||
                output = "Package: %s\n" % pkg
 | 
			
		||||
                for key, k in ((SECTION, 'Section'), (ARCHITECTURE, 'Architecture'), (MULTIARCH, 'Multi-Arch'), (SOURCE, 'Source'), (VERSION, 'Version'), 
 | 
			
		||||
                          (DEPENDS, 'Depends'), (PROVIDES, 'Provides'), (CONFLICTS, 'Conflicts'), (ESSENTIAL, 'Essential')):
 | 
			
		||||
                    if not binaries[pkg][key]: continue
 | 
			
		||||
                    if key == SOURCE:
 | 
			
		||||
                        if binaries[pkg][SOURCE] == pkg:
 | 
			
		||||
                            if binaries[pkg][SOURCEVER] != binaries[pkg][VERSION]:
 | 
			
		||||
                                source = binaries[pkg][SOURCE] + " (" + binaries[pkg][SOURCEVER] + ")"
 | 
			
		||||
                            else: continue
 | 
			
		||||
                        else:
 | 
			
		||||
                            if binaries[pkg][SOURCEVER] != binaries[pkg][VERSION]:
 | 
			
		||||
                                source = binaries[pkg][SOURCE] + " (" + binaries[pkg][SOURCEVER] + ")"
 | 
			
		||||
                            else:
 | 
			
		||||
                                source = binaries[pkg][SOURCE]
 | 
			
		||||
                        output += (k + ": " + source + "\n")
 | 
			
		||||
                        if sources[binaries[pkg][SOURCE]][MAINTAINER]:
 | 
			
		||||
                            output += ("Maintainer: " + sources[binaries[pkg][SOURCE]][MAINTAINER] + "\n")
 | 
			
		||||
                    elif key == PROVIDES:
 | 
			
		||||
                        if len(binaries[pkg][key]) > 0:
 | 
			
		||||
                            output += (k + ": " + ", ".join(binaries[pkg][key]) + "\n")
 | 
			
		||||
                    elif key == ESSENTIAL:
 | 
			
		||||
                        if binaries[pkg][key]:
 | 
			
		||||
                            output += (k + ": " + " yes\n")
 | 
			
		||||
                    else:
 | 
			
		||||
                        output += (k + ": " + binaries[pkg][key] + "\n")
 | 
			
		||||
                f.write(output + "\n")
 | 
			
		||||
            f.close()
 | 
			
		||||
 | 
			
		||||
        filename = os.path.join(basedir, 'Sources')
 | 
			
		||||
        f = open(filename, 'w')
 | 
			
		||||
        for src in sources:
 | 
			
		||||
            output = "Package: %s\n" % src
 | 
			
		||||
            for key, k in ((VERSION, 'Version'), (SECTION, 'Section'), (MAINTAINER, 'Maintainer')):
 | 
			
		||||
                if not sources[src][key]: continue
 | 
			
		||||
                output += (k + ": " + sources[src][key] + "\n")
 | 
			
		||||
            f.write(output + "\n")
 | 
			
		||||
        f.close()
 | 
			
		||||
 | 
			
		||||
    # Utility methods for package analysis
 | 
			
		||||
    # ------------------------------------
 | 
			
		||||
 | 
			
		||||
@ -1168,6 +1088,7 @@ class Britney(object):
 | 
			
		||||
            # if no package can satisfy the dependency, add this information to the excuse
 | 
			
		||||
            if len(packages) == 0:
 | 
			
		||||
                excuse.addhtml("%s/%s unsatisfiable Depends: %s" % (pkg, arch, block_txt.strip()))
 | 
			
		||||
                excuse.addreason("depends");
 | 
			
		||||
                all_satisfiable = False
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
@ -1203,6 +1124,8 @@ class Britney(object):
 | 
			
		||||
        # otherwise, add a new excuse for its removal and return True
 | 
			
		||||
        src = self.sources['testing'][pkg]
 | 
			
		||||
        excuse = Excuse("-" + pkg)
 | 
			
		||||
        excuse.addhtml("Package not in unstable, will try to remove")
 | 
			
		||||
        excuse.addreason("remove")
 | 
			
		||||
        excuse.set_distribution(self.options.distribution)
 | 
			
		||||
        excuse.set_vers(src[VERSION], None)
 | 
			
		||||
        src[MAINTAINER] and excuse.set_maint(src[MAINTAINER].strip())
 | 
			
		||||
@ -1213,6 +1136,7 @@ class Britney(object):
 | 
			
		||||
            excuse.addhtml("Not touching package, as requested by %s (contact #ubuntu-release "
 | 
			
		||||
                "if update is needed)" % hint.user)
 | 
			
		||||
            excuse.addhtml("Not considered")
 | 
			
		||||
            excuse.addreason("block")
 | 
			
		||||
            self.excuses.append(excuse)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
@ -1253,6 +1177,7 @@ class Britney(object):
 | 
			
		||||
            excuse.addhtml("Removal request by %s" % (hint.user))
 | 
			
		||||
            excuse.addhtml("Trying to remove package, not update it")
 | 
			
		||||
            excuse.addhtml("Not considered")
 | 
			
		||||
            excuse.addreason("remove")
 | 
			
		||||
            self.excuses.append(excuse)
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
@ -1409,6 +1334,7 @@ class Britney(object):
 | 
			
		||||
        if source_t and apt_pkg.version_compare(source_u[VERSION], source_t[VERSION]) < 0:
 | 
			
		||||
            excuse.addhtml("ALERT: %s is newer in testing (%s %s)" % (src, source_t[VERSION], source_u[VERSION]))
 | 
			
		||||
            self.excuses.append(excuse)
 | 
			
		||||
            excuse.addreason("newerintesting");
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # check if the source package really exists or if it is a fake one
 | 
			
		||||
@ -1431,6 +1357,7 @@ class Britney(object):
 | 
			
		||||
               same_source(source_u[VERSION], item.version):
 | 
			
		||||
                excuse.addhtml("Removal request by %s" % (item.user))
 | 
			
		||||
                excuse.addhtml("Trying to remove package, not update it")
 | 
			
		||||
                excuse.addreason("remove")
 | 
			
		||||
                update_candidate = False
 | 
			
		||||
                run_autopkgtest = False
 | 
			
		||||
 | 
			
		||||
@ -1470,14 +1397,17 @@ class Britney(object):
 | 
			
		||||
                if suite == 'unstable' or block_cmd == 'block-udeb':
 | 
			
		||||
                    excuse.addhtml("Not touching package due to %s request by %s (contact #ubuntu-release if update is needed)" %
 | 
			
		||||
                                   (block_cmd, blocked[block_cmd].user))
 | 
			
		||||
                    excuse.addreason("block")
 | 
			
		||||
                else:
 | 
			
		||||
                    excuse.addhtml("NEEDS APPROVAL BY RM")
 | 
			
		||||
                    excuse.addreason("block")
 | 
			
		||||
                update_candidate = False
 | 
			
		||||
 | 
			
		||||
        if src in self.blocks:
 | 
			
		||||
            for user_block in self.blocks[src]:
 | 
			
		||||
                excuse.addhtml("Not touching package as requested in <a href=\"https://launchpad.net/bugs/%s\">bug %s</a> on %s" %
 | 
			
		||||
                               (user_block[0], user_block[0], time.asctime(time.gmtime(user_block[1]))))
 | 
			
		||||
                excuse.addreason("block")
 | 
			
		||||
                update_candidate = False
 | 
			
		||||
 | 
			
		||||
        # if the suite is unstable, then we have to check the urgency and the minimum days of
 | 
			
		||||
@ -1508,6 +1438,7 @@ class Britney(object):
 | 
			
		||||
                else:
 | 
			
		||||
                    update_candidate = False
 | 
			
		||||
                    run_autopkgtest = False
 | 
			
		||||
                    excuse.addreason("age")
 | 
			
		||||
 | 
			
		||||
        if suite in ['pu', 'tpu']:
 | 
			
		||||
            # o-o-d(ish) checks for (t-)p-u
 | 
			
		||||
@ -1542,6 +1473,8 @@ class Britney(object):
 | 
			
		||||
                    update_candidate = False
 | 
			
		||||
                    if arch in self.options.adt_arches.split():
 | 
			
		||||
                        run_autopkgtest = False
 | 
			
		||||
                    excuse.addreason("arch")
 | 
			
		||||
                    excuse.addreason("arch-%s" % arch)
 | 
			
		||||
 | 
			
		||||
                excuse.addhtml(text)
 | 
			
		||||
 | 
			
		||||
@ -1605,16 +1538,20 @@ class Britney(object):
 | 
			
		||||
                    update_candidate = False
 | 
			
		||||
                    if arch in self.options.adt_arches.split():
 | 
			
		||||
                        run_autopkgtest = False
 | 
			
		||||
                    excuse.addreason("arch")
 | 
			
		||||
                    excuse.addreason("arch-%s" % arch)
 | 
			
		||||
 | 
			
		||||
                excuse.addhtml(text)
 | 
			
		||||
 | 
			
		||||
        # if the source package has no binaries, set update_candidate to False to block the update
 | 
			
		||||
        if len(self.sources[suite][src][BINARIES]) == 0:
 | 
			
		||||
            excuse.addhtml("%s has no binaries on any arch" % src)
 | 
			
		||||
            excuse.addreason("no-binaries")
 | 
			
		||||
            update_candidate = False
 | 
			
		||||
            run_autopkgtest = False
 | 
			
		||||
        elif not built_anywhere:
 | 
			
		||||
            excuse.addhtml("%s has no up-to-date binaries on any arch" % src)
 | 
			
		||||
            excuse.addreason("no-binaries")
 | 
			
		||||
            update_candidate = False
 | 
			
		||||
            run_autopkgtest = False
 | 
			
		||||
 | 
			
		||||
@ -1639,6 +1576,8 @@ class Britney(object):
 | 
			
		||||
                new_bugs = sorted(set(bugs_u).difference(bugs_t))
 | 
			
		||||
                old_bugs = sorted(set(bugs_t).difference(bugs_u))
 | 
			
		||||
 | 
			
		||||
                excuse.setbugs(old_bugs,new_bugs)
 | 
			
		||||
 | 
			
		||||
                if len(new_bugs) > 0:
 | 
			
		||||
                    excuse.addhtml("%s (%s) <a href=\"http://bugs.debian.org/cgi-bin/pkgreport.cgi?" \
 | 
			
		||||
                        "which=pkg&data=%s&sev-inc=critical&sev-inc=grave&sev-inc=serious\" " \
 | 
			
		||||
@ -1647,6 +1586,7 @@ class Britney(object):
 | 
			
		||||
                        ["<a href=\"http://bugs.debian.org/%s\">#%s</a>" % (urllib.quote(a), a) for a in new_bugs])))
 | 
			
		||||
                    update_candidate = False
 | 
			
		||||
                    run_autopkgtest = False
 | 
			
		||||
                    excuse.addreason("buggy")
 | 
			
		||||
 | 
			
		||||
                if len(old_bugs) > 0:
 | 
			
		||||
                    excuse.addhtml("Updating %s fixes old bugs: %s" % (pkg, ", ".join(
 | 
			
		||||
@ -1661,6 +1601,7 @@ class Britney(object):
 | 
			
		||||
            excuse.dontinvalidate = True
 | 
			
		||||
        if not update_candidate and forces:
 | 
			
		||||
            excuse.addhtml("Should ignore, but forced by %s" % (forces[0].user))
 | 
			
		||||
            excuse.force()
 | 
			
		||||
            update_candidate = True
 | 
			
		||||
            run_autopkgtest = True
 | 
			
		||||
 | 
			
		||||
@ -1669,6 +1610,7 @@ class Britney(object):
 | 
			
		||||
            excuse.is_valid = True
 | 
			
		||||
        # else it won't be considered
 | 
			
		||||
        else:
 | 
			
		||||
            # TODO
 | 
			
		||||
            excuse.addhtml("Not considered")
 | 
			
		||||
        excuse.run_autopkgtest = run_autopkgtest
 | 
			
		||||
 | 
			
		||||
@ -1728,6 +1670,7 @@ class Britney(object):
 | 
			
		||||
                    invalid.append(valid.pop(p))
 | 
			
		||||
                    exclookup[x].addhtml("Invalidated by dependency")
 | 
			
		||||
                    exclookup[x].addhtml("Not considered")
 | 
			
		||||
                    exclookup[x].addreason("depends")
 | 
			
		||||
                    exclookup[x].is_valid = False
 | 
			
		||||
            i = i + 1
 | 
			
		||||
 
 | 
			
		||||
@ -1807,6 +1750,7 @@ class Britney(object):
 | 
			
		||||
            excuse.set_vers(tsrcv, None)
 | 
			
		||||
            excuse.addhtml("Removal request by %s" % (item.user))
 | 
			
		||||
            excuse.addhtml("Package is broken, will try to remove")
 | 
			
		||||
            excuse.addreason("remove")
 | 
			
		||||
            self.excuses.append(excuse)
 | 
			
		||||
 | 
			
		||||
        # sort the excuses by daysold and name
 | 
			
		||||
@ -1889,6 +1833,7 @@ class Britney(object):
 | 
			
		||||
                        upgrade_me.remove(e.name)
 | 
			
		||||
                        unconsidered.append(e.name)
 | 
			
		||||
                        e.addhtml("Not considered")
 | 
			
		||||
                        e.addreason("autopkgtest")
 | 
			
		||||
                        e.is_valid = False
 | 
			
		||||
 | 
			
		||||
        # invalidate impossible excuses
 | 
			
		||||
@ -1923,6 +1868,7 @@ class Britney(object):
 | 
			
		||||
                        ok = True
 | 
			
		||||
                if not ok:
 | 
			
		||||
                    e.addhtml("Impossible dependency: %s -> %s" % (e.name, d))
 | 
			
		||||
                    e.addreason("depends")
 | 
			
		||||
        self.invalidate_excuses(upgrade_me, unconsidered)
 | 
			
		||||
 | 
			
		||||
        # sort the list of candidates
 | 
			
		||||
@ -1931,18 +1877,12 @@ class Britney(object):
 | 
			
		||||
        # write excuses to the output file
 | 
			
		||||
        if not self.options.dry_run:
 | 
			
		||||
            self.__log("> Writing Excuses to %s" % self.options.excuses_output, type="I")
 | 
			
		||||
            ensuredir(os.path.dirname(self.options.excuses_output))
 | 
			
		||||
            f = open(self.options.excuses_output, 'w')
 | 
			
		||||
            f.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n")
 | 
			
		||||
            f.write("<html><head><title>excuses...</title>")
 | 
			
		||||
            f.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"></head><body>\n")
 | 
			
		||||
            f.write("<p>Generated: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "</p>\n")
 | 
			
		||||
            f.write("<p>See the <a href=\"https://wiki.ubuntu.com/ProposedMigration\">documentation</a> for help interpreting this page.</p>\n")
 | 
			
		||||
            f.write("<ul>\n")
 | 
			
		||||
            for e in self.excuses:
 | 
			
		||||
                f.write("<li>%s" % e.html())
 | 
			
		||||
            f.write("</ul></body></html>\n")
 | 
			
		||||
            f.close()
 | 
			
		||||
            write_excuses(self.excuses, self.options.excuses_output,
 | 
			
		||||
                          output_format="legacy-html")
 | 
			
		||||
            if hasattr(self.options, 'excuses_yaml_output'):
 | 
			
		||||
                self.__log("> Writing YAML Excuses to %s" % self.options.excuses_yaml_output, type="I")
 | 
			
		||||
                write_excuses(self.excuses, self.options.excuses_yaml_output,
 | 
			
		||||
                          output_format="yaml")
 | 
			
		||||
 | 
			
		||||
        self.__log("Update Excuses generation completed", type="I")
 | 
			
		||||
 | 
			
		||||
@ -2541,7 +2481,7 @@ class Britney(object):
 | 
			
		||||
                                              newly_uninst(nuninst_start, nuninst_end)))
 | 
			
		||||
            self.output_write("SUCCESS (%d/%d)\n" % (len(actions or self.upgrade_me), len(extra)))
 | 
			
		||||
            self.nuninst_orig = nuninst_end
 | 
			
		||||
            self.all_selected += [x.uvname for x in selected]
 | 
			
		||||
            self.all_selected += selected
 | 
			
		||||
            if not actions:
 | 
			
		||||
                if recurse:
 | 
			
		||||
                    self.upgrade_me = sorted(extra)
 | 
			
		||||
@ -2643,14 +2583,14 @@ class Britney(object):
 | 
			
		||||
                self.do_all(actions=removals)
 | 
			
		||||
                                                                                                                                     
 | 
			
		||||
        # smooth updates
 | 
			
		||||
        if len(self.options.smooth_updates) > 0:
 | 
			
		||||
        if self.options.smooth_updates:
 | 
			
		||||
            self.__log("> Removing old packages left in testing from smooth updates", type="I")
 | 
			
		||||
            removals = self.old_libraries()
 | 
			
		||||
            if len(removals) > 0:
 | 
			
		||||
            removals = old_libraries(self.sources, self.binaries)
 | 
			
		||||
            if removals:
 | 
			
		||||
                self.output_write("Removing packages left in testing for smooth updates (%d):\n%s" % \
 | 
			
		||||
                    (len(removals), old_libraries_format(removals)))
 | 
			
		||||
                self.do_all(actions=removals)
 | 
			
		||||
                removals = self.old_libraries()
 | 
			
		||||
                removals = old_libraries(self.sources, self.binaries)
 | 
			
		||||
        else:
 | 
			
		||||
            removals = ()
 | 
			
		||||
 | 
			
		||||
@ -2661,7 +2601,10 @@ class Britney(object):
 | 
			
		||||
        if not self.options.dry_run:
 | 
			
		||||
            # re-write control files
 | 
			
		||||
            if self.options.control_files:
 | 
			
		||||
                self.write_controlfiles(self.options.testing, 'testing')
 | 
			
		||||
                self.__log("Writing new testing control files to %s" %
 | 
			
		||||
                           self.options.testing)
 | 
			
		||||
                write_controlfiles(self.sources, self.binaries,
 | 
			
		||||
                                   'testing', self.options.testing)
 | 
			
		||||
 | 
			
		||||
            # write dates
 | 
			
		||||
            try:
 | 
			
		||||
@ -2674,9 +2617,10 @@ class Britney(object):
 | 
			
		||||
            write_heidi(self.options.heidi_output, self.sources["testing"],
 | 
			
		||||
                        self.binaries["testing"])
 | 
			
		||||
 | 
			
		||||
            # write Delta
 | 
			
		||||
            if hasattr(self.options, 'delta_output'):
 | 
			
		||||
                self.write_delta(self.options.delta_output)
 | 
			
		||||
            self.__log("Writing delta to %s" % self.options.heidi_delta_output)
 | 
			
		||||
            write_heidi_delta(self.options.heidi_delta_output,
 | 
			
		||||
                              self.all_selected)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        self.printuninstchange()
 | 
			
		||||
        self.__log("Test completed!", type="I")
 | 
			
		||||
@ -2908,27 +2852,6 @@ class Britney(object):
 | 
			
		||||
                if i not in to_skip:
 | 
			
		||||
                    self.do_hint("easy", "autohinter", [ MigrationItem("%s/%s" % (x[0], x[1])) for x in l[i] ])
 | 
			
		||||
 | 
			
		||||
    def old_libraries(self, same_source=same_source):
 | 
			
		||||
        """Detect old libraries left in testing for smooth transitions
 | 
			
		||||
 | 
			
		||||
        This method detects old libraries which are in testing but no longer
 | 
			
		||||
        built from the source package: they are still there because other
 | 
			
		||||
        packages still depend on them, but they should be removed as soon
 | 
			
		||||
        as possible.
 | 
			
		||||
 | 
			
		||||
        same_source is an opt to avoid "load global".
 | 
			
		||||
        """
 | 
			
		||||
        sources = self.sources['testing']
 | 
			
		||||
        testing = self.binaries['testing']
 | 
			
		||||
        unstable = self.binaries['unstable']
 | 
			
		||||
        removals = []
 | 
			
		||||
        for arch in self.options.architectures:
 | 
			
		||||
            for pkg_name in testing[arch][0]:
 | 
			
		||||
                pkg = testing[arch][0][pkg_name]
 | 
			
		||||
                if pkg_name not in unstable[arch][0] and \
 | 
			
		||||
                   not same_source(sources[pkg[SOURCE]][VERSION], pkg[SOURCEVER]):
 | 
			
		||||
                    removals.append(MigrationItem("-" + pkg_name + "/" + arch + "/" + pkg[SOURCEVER]))
 | 
			
		||||
        return removals
 | 
			
		||||
 | 
			
		||||
    def nuninst_arch_report(self, nuninst, arch):
 | 
			
		||||
        """Print a report of uninstallable packages for one architecture."""
 | 
			
		||||
@ -2990,9 +2913,6 @@ class Britney(object):
 | 
			
		||||
 | 
			
		||||
        If nuninst_arch is not None then it also updated in the same
 | 
			
		||||
        way as broken is.
 | 
			
		||||
 | 
			
		||||
        current_pkg is the package currently being tried, mainly used
 | 
			
		||||
        to print where an AIEEE is coming from.
 | 
			
		||||
        """
 | 
			
		||||
        r = self._inst_tester.is_installable(pkg_name, pkg_version, pkg_arch)
 | 
			
		||||
        if not r:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										172
									
								
								britney_util.py
									
									
									
									
									
								
							
							
						
						
									
										172
									
								
								britney_util.py
									
									
									
									
									
								
							@ -23,17 +23,27 @@
 | 
			
		||||
 | 
			
		||||
import apt_pkg
 | 
			
		||||
from functools import partial
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from itertools import chain, ifilter, ifilterfalse, izip, repeat
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
import time
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
from migrationitem import MigrationItem, UnversionnedMigrationItem
 | 
			
		||||
 | 
			
		||||
from consts import (VERSION, BINARIES, PROVIDES, DEPENDS, CONFLICTS,
 | 
			
		||||
                    RDEPENDS, RCONFLICTS, ARCHITECTURE, SECTION,
 | 
			
		||||
                    SOURCE, SOURCEVER)
 | 
			
		||||
                    SOURCE, SOURCEVER, MAINTAINER, MULTIARCH,
 | 
			
		||||
                    ESSENTIAL)
 | 
			
		||||
 | 
			
		||||
binnmu_re = re.compile(r'^(.*)\+b\d+$')
 | 
			
		||||
 | 
			
		||||
def ensuredir(directory):
 | 
			
		||||
    if not os.path.isdir(directory):
 | 
			
		||||
        os.makedirs(directory)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def same_source(sv1, sv2, binnmu_re=binnmu_re):
 | 
			
		||||
    """Check if two version numbers are built from the same source
 | 
			
		||||
 | 
			
		||||
@ -404,6 +414,34 @@ def write_heidi(filename, sources_t, packages_t,
 | 
			
		||||
            srcsec = src[SECTION] or 'unknown'
 | 
			
		||||
            f.write('%s %s source %s\n' % (src_name, srcv, srcsec))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_heidi_delta(filename, all_selected):
 | 
			
		||||
    """Write the output delta
 | 
			
		||||
 | 
			
		||||
    This method writes the packages to be upgraded, in the form:
 | 
			
		||||
    <src-name> <src-version>
 | 
			
		||||
    or (if the source is to be removed):
 | 
			
		||||
    -<src-name> <src-version>
 | 
			
		||||
 | 
			
		||||
    The order corresponds to that shown in update_output.
 | 
			
		||||
    """
 | 
			
		||||
    with open(filename, "w") as fd:
 | 
			
		||||
 | 
			
		||||
        fd.write("#HeidiDelta\n")
 | 
			
		||||
 | 
			
		||||
        for item in all_selected:
 | 
			
		||||
            prefix = ""
 | 
			
		||||
 | 
			
		||||
            if item.is_removal:
 | 
			
		||||
                prefix = "-"
 | 
			
		||||
 | 
			
		||||
            if item.architecture == 'source':
 | 
			
		||||
                fd.write('%s%s %s\n' % (prefix, item.package, item.version))
 | 
			
		||||
            else:
 | 
			
		||||
                fd.write('%s%s %s %s\n' % (prefix, item.package,
 | 
			
		||||
                                           item.version, item.architecture))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def make_migrationitem(package, sources, VERSION=VERSION):
 | 
			
		||||
    """Convert a textual package specification to a MigrationItem
 | 
			
		||||
    
 | 
			
		||||
@ -413,3 +451,135 @@ def make_migrationitem(package, sources, VERSION=VERSION):
 | 
			
		||||
    
 | 
			
		||||
    item = UnversionnedMigrationItem(package)
 | 
			
		||||
    return MigrationItem("%s/%s" % (item.uvname, sources[item.suite][item.package][VERSION]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_excuses(excuses, dest_file, output_format="yaml"):
 | 
			
		||||
    """Write the excuses to dest_file
 | 
			
		||||
 | 
			
		||||
    Writes a list of excuses in a specified output_format to the
 | 
			
		||||
    path denoted by dest_file.  The output_format can either be "yaml"
 | 
			
		||||
    or "legacy-html".
 | 
			
		||||
    """
 | 
			
		||||
    if output_format == "yaml":
 | 
			
		||||
        ensuredir(os.path.dirname(dest_file))
 | 
			
		||||
        with open(dest_file, 'w') as f:
 | 
			
		||||
            excuselist = []
 | 
			
		||||
            for e in excuses:
 | 
			
		||||
                excuselist.append(e.excusedata())
 | 
			
		||||
            excusesdata = {}
 | 
			
		||||
            excusesdata["sources"] = excuselist
 | 
			
		||||
            excusesdata["generated-date"] = datetime.utcnow()
 | 
			
		||||
            f.write(yaml.dump(excusesdata, default_flow_style=False, allow_unicode=True))
 | 
			
		||||
    elif output_format == "legacy-html":
 | 
			
		||||
        ensuredir(os.path.dirname(dest_file))
 | 
			
		||||
        with open(dest_file, 'w') as f:
 | 
			
		||||
            f.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n")
 | 
			
		||||
            f.write("<html><head><title>excuses...</title>")
 | 
			
		||||
            f.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"></head><body>\n")
 | 
			
		||||
            f.write("<p>Generated: " + time.strftime("%Y.%m.%d %H:%M:%S %z", time.gmtime(time.time())) + "</p>\n")
 | 
			
		||||
            f.write("<p>See the <a href=\"https://wiki.ubuntu.com/ProposedMigration\">documentation</a> for help interpreting this page.</p>\n")
 | 
			
		||||
            f.write("<ul>\n")
 | 
			
		||||
            for e in excuses:
 | 
			
		||||
                f.write("<li>%s" % e.html())
 | 
			
		||||
            f.write("</ul></body></html>\n")
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValueError('Output format must be either "yaml or "legacy-html"')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_sources(sources_s, filename):
 | 
			
		||||
    """Write a sources file from Britney's state for a given suite
 | 
			
		||||
 | 
			
		||||
    Britney discards fields she does not care about, so the resulting
 | 
			
		||||
    file omitts a lot of regular fields.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    key_pairs = ((VERSION, 'Version'), (SECTION, 'Section'),
 | 
			
		||||
                 (MAINTAINER, 'Maintainer'))
 | 
			
		||||
 | 
			
		||||
    with open(filename, 'w') as f:
 | 
			
		||||
        for src in sources_s:
 | 
			
		||||
           src_data = sources_s[src]
 | 
			
		||||
           output = "Package: %s\n" % src
 | 
			
		||||
           output += "\n".join(k + ": "+ src_data[key]
 | 
			
		||||
                               for key, k in key_pairs if src_data[key])
 | 
			
		||||
           f.write(output + "\n\n")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def write_controlfiles(sources, packages, suite, basedir):
 | 
			
		||||
    """Write the control files
 | 
			
		||||
 | 
			
		||||
    This method writes the control files for the binary packages of all
 | 
			
		||||
    the architectures and for the source packages.  Note that Britney
 | 
			
		||||
    discards a lot of fields that she does not care about.  Therefore,
 | 
			
		||||
    these files may omit a lot of regular fields.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    sources_s = sources[suite]
 | 
			
		||||
    packages_s = packages[suite]
 | 
			
		||||
 | 
			
		||||
    key_pairs = ((SECTION, 'Section'), (ARCHITECTURE, 'Architecture'),
 | 
			
		||||
                 (MULTIARCH, 'Multi-Arch'), (SOURCE, 'Source'),
 | 
			
		||||
                 (VERSION, 'Version'), (DEPENDS, 'Depends'),
 | 
			
		||||
                 (PROVIDES, 'Provides'), (CONFLICTS, 'Conflicts'),
 | 
			
		||||
                 (ESSENTIAL, 'Essential'))
 | 
			
		||||
 | 
			
		||||
    ensuredir(basedir)
 | 
			
		||||
    for arch in packages_s:
 | 
			
		||||
        filename = os.path.join(basedir, 'Packages_%s' % arch)
 | 
			
		||||
        binaries = packages_s[arch][0]
 | 
			
		||||
        with open(filename, 'w') as f:
 | 
			
		||||
            for pkg in binaries:
 | 
			
		||||
                output = "Package: %s\n" % pkg
 | 
			
		||||
                bin_data = binaries[pkg]
 | 
			
		||||
                for key, k in key_pairs:
 | 
			
		||||
                    if not bin_data[key]: continue
 | 
			
		||||
                    if key == SOURCE:
 | 
			
		||||
                        src = bin_data[SOURCE]
 | 
			
		||||
                        if sources_s[src][MAINTAINER]:
 | 
			
		||||
                            output += ("Maintainer: " + sources_s[src][MAINTAINER] + "\n")
 | 
			
		||||
 | 
			
		||||
                        if bin_data[SOURCE] == pkg:
 | 
			
		||||
                            if bin_data[SOURCEVER] != bin_data[VERSION]:
 | 
			
		||||
                                source = src + " (" + bin_data[SOURCEVER] + ")"
 | 
			
		||||
                            else: continue
 | 
			
		||||
                        else:
 | 
			
		||||
                            if bin_data[SOURCEVER] != bin_data[VERSION]:
 | 
			
		||||
                                source = src + " (" + bin_data[SOURCEVER] + ")"
 | 
			
		||||
                            else:
 | 
			
		||||
                                source = src
 | 
			
		||||
                        output += (k + ": " + source + "\n")
 | 
			
		||||
                    elif key == PROVIDES:
 | 
			
		||||
                        if bin_data[key]:
 | 
			
		||||
                            output += (k + ": " + ", ".join(bin_data[key]) + "\n")
 | 
			
		||||
                    elif key == ESSENTIAL:
 | 
			
		||||
                        if bin_data[key]:
 | 
			
		||||
                            output += (k + ": " + " yes\n")
 | 
			
		||||
                    else:
 | 
			
		||||
                        output += (k + ": " + bin_data[key] + "\n")
 | 
			
		||||
                f.write(output + "\n")
 | 
			
		||||
 | 
			
		||||
    write_sources(sources_s, os.path.join(basedir, 'Sources'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def old_libraries(sources, packages, same_source=same_source):
 | 
			
		||||
    """Detect old libraries left in testing for smooth transitions
 | 
			
		||||
 | 
			
		||||
    This method detects old libraries which are in testing but no
 | 
			
		||||
    longer built from the source package: they are still there because
 | 
			
		||||
    other packages still depend on them, but they should be removed as
 | 
			
		||||
    soon as possible.
 | 
			
		||||
 | 
			
		||||
    same_source is an optimisation to avoid "load global".
 | 
			
		||||
    """
 | 
			
		||||
    sources_t = sources['testing']
 | 
			
		||||
    testing = packages['testing']
 | 
			
		||||
    unstable = packages['unstable']
 | 
			
		||||
    removals = []
 | 
			
		||||
    for arch in testing:
 | 
			
		||||
        for pkg_name in testing[arch][0]:
 | 
			
		||||
            pkg = testing[arch][0][pkg_name]
 | 
			
		||||
            if pkg_name not in unstable[arch][0] and \
 | 
			
		||||
                    not same_source(sources_t[pkg[SOURCE]][VERSION], pkg[SOURCEVER]):
 | 
			
		||||
                migration = "-" + "/".join((pkg_name, arch, pkg[SOURCEVER]))
 | 
			
		||||
                removals.append(MigrationItem(migration))
 | 
			
		||||
    return removals
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										81
									
								
								excuse.py
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								excuse.py
									
									
									
									
									
								
							@ -50,6 +50,7 @@ class Excuse(object):
 | 
			
		||||
        self.section = None
 | 
			
		||||
        self._is_valid = False
 | 
			
		||||
        self._dontinvalidate = False
 | 
			
		||||
        self.forced = False
 | 
			
		||||
        self.run_autopkgtest = False
 | 
			
		||||
        self.distribution = "ubuntu"
 | 
			
		||||
 | 
			
		||||
@ -58,6 +59,9 @@ class Excuse(object):
 | 
			
		||||
        self.sane_deps = []
 | 
			
		||||
        self.break_deps = []
 | 
			
		||||
        self.bugs = []
 | 
			
		||||
        self.newbugs = set()
 | 
			
		||||
        self.oldbugs = set()
 | 
			
		||||
        self.reason = {}
 | 
			
		||||
        self.htmlline = []
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
@ -120,6 +124,10 @@ class Excuse(object):
 | 
			
		||||
        self.daysold = daysold
 | 
			
		||||
        self.mindays = mindays
 | 
			
		||||
 | 
			
		||||
    def force(self):
 | 
			
		||||
        """Add force hint"""
 | 
			
		||||
        self.forced = True
 | 
			
		||||
 | 
			
		||||
    def addhtml(self, note):
 | 
			
		||||
        """Add a note in HTML"""
 | 
			
		||||
        self.htmlline.append(note)
 | 
			
		||||
@ -171,3 +179,76 @@ class Excuse(object):
 | 
			
		||||
            res += "<li>Valid candidate\n"
 | 
			
		||||
        res = res + "</ul>\n"
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def setbugs(self, oldbugs, newbugs):
 | 
			
		||||
        """"Set the list of old and new bugs"""
 | 
			
		||||
        self.newbugs.update(newbugs)
 | 
			
		||||
        self.oldbugs.update(oldbugs)
 | 
			
		||||
 | 
			
		||||
    def addreason(self, reason):
 | 
			
		||||
        """"adding reason"""
 | 
			
		||||
        self.reason[reason] = 1
 | 
			
		||||
 | 
			
		||||
    # TODO merge with html()
 | 
			
		||||
    def text(self):
 | 
			
		||||
        """Render the excuse in text"""
 | 
			
		||||
        res = []
 | 
			
		||||
        res.append("%s (%s to %s)" % \
 | 
			
		||||
            (self.name, self.ver[0], self.ver[1]))
 | 
			
		||||
        if self.maint:
 | 
			
		||||
            maint = self.maint
 | 
			
		||||
            # ugly hack to work around strange encoding in pyyaml
 | 
			
		||||
            # should go away with pyyaml in python 3
 | 
			
		||||
            try:
 | 
			
		||||
                maint.decode('ascii')
 | 
			
		||||
            except UnicodeDecodeError:
 | 
			
		||||
                maint = unicode(self.maint,'utf-8')
 | 
			
		||||
            res.append("Maintainer: %s" % maint)
 | 
			
		||||
        if self.section and string.find(self.section, "/") > -1:
 | 
			
		||||
            res.append("Section: %s" % (self.section))
 | 
			
		||||
        if self.daysold != None:
 | 
			
		||||
            if self.mindays == 0:
 | 
			
		||||
                res.append("%d days old" % self.daysold)
 | 
			
		||||
            elif self.daysold < self.mindays:
 | 
			
		||||
                res.append(("Too young, only %d of %d days old" %
 | 
			
		||||
                (self.daysold, self.mindays)))
 | 
			
		||||
            else:
 | 
			
		||||
                res.append(("%d days old (needed %d days)" %
 | 
			
		||||
                (self.daysold, self.mindays)))
 | 
			
		||||
        for x in self.htmlline:
 | 
			
		||||
            res.append("" + x + "")
 | 
			
		||||
        lastdep = ""
 | 
			
		||||
        for x in sorted(self.deps, lambda x,y: cmp(x.split('/')[0], y.split('/')[0])):
 | 
			
		||||
            dep = x.split('/')[0]
 | 
			
		||||
            if dep == lastdep: continue
 | 
			
		||||
            lastdep = dep
 | 
			
		||||
            if x in self.invalid_deps:
 | 
			
		||||
                res.append("Depends: %s %s (not considered)" % (self.name, dep))
 | 
			
		||||
            else:
 | 
			
		||||
                res.append("Depends: %s %s" % (self.name, dep))
 | 
			
		||||
        for (n,a) in self.break_deps:
 | 
			
		||||
            if n not in self.deps:
 | 
			
		||||
                res.append("Ignoring %s depends: %s" % (a, n))
 | 
			
		||||
        if self.is_valid:
 | 
			
		||||
            res.append("Valid candidate")
 | 
			
		||||
        return res
 | 
			
		||||
 | 
			
		||||
    def excusedata(self):
 | 
			
		||||
        """Render the excuse in as key-value data"""
 | 
			
		||||
        excusedata = {}
 | 
			
		||||
        excusedata["excuses"] = self.text()
 | 
			
		||||
        excusedata["source"] = self.name
 | 
			
		||||
        excusedata["old-version"] = self.ver[0]
 | 
			
		||||
        excusedata["new-version"] = self.ver[1]
 | 
			
		||||
        excusedata["age"] = self.daysold
 | 
			
		||||
        excusedata["age-needed"] = self.mindays
 | 
			
		||||
        excusedata["new-bugs"] = sorted(self.newbugs)
 | 
			
		||||
        excusedata["old-bugs"] = sorted(self.oldbugs)
 | 
			
		||||
        if self.forced:
 | 
			
		||||
            excusedata["forced-reason"] = self.reason.keys()
 | 
			
		||||
            excusedata["reason"] = []
 | 
			
		||||
        else:
 | 
			
		||||
            excusedata["reason"] = self.reason.keys()
 | 
			
		||||
        excusedata["is-candidate"] = self.is_valid
 | 
			
		||||
        return excusedata
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								hints.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								hints.py
									
									
									
									
									
								
							@ -33,7 +33,10 @@ class HintCollection(object):
 | 
			
		||||
               ]
 | 
			
		||||
 | 
			
		||||
    def add_hint(self, hint, user):
 | 
			
		||||
        self._hints.append(Hint(hint, user))
 | 
			
		||||
        try:
 | 
			
		||||
            self._hints.append(Hint(hint, user))
 | 
			
		||||
        except AssertionError:
 | 
			
		||||
            print "Ignoring broken hint %r from %s" % (hint, user)
 | 
			
		||||
 | 
			
		||||
class Hint(object):
 | 
			
		||||
    NO_VERSION = [ 'block', 'block-all', 'block-udeb' ]
 | 
			
		||||
 | 
			
		||||
@ -280,10 +280,6 @@ class InstallabilityTester(object):
 | 
			
		||||
                # is smaller than testing (so presumably faster)
 | 
			
		||||
                remain = choice - never - cbroken
 | 
			
		||||
 | 
			
		||||
                if not remain:
 | 
			
		||||
                    # all alternatives would violate the conflicts => package is not installable
 | 
			
		||||
                    return None
 | 
			
		||||
 | 
			
		||||
                if len(remain) > 1 and not remain.isdisjoint(safe_set):
 | 
			
		||||
                    first = None
 | 
			
		||||
                    for r in ifilter(safe_set.__contains__, remain):
 | 
			
		||||
@ -305,6 +301,12 @@ class InstallabilityTester(object):
 | 
			
		||||
                    check.update(remain)
 | 
			
		||||
                    musts.update(remain)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if not remain:
 | 
			
		||||
                    # all alternatives would violate the conflicts or are uninstallable
 | 
			
		||||
                    # => package is not installable
 | 
			
		||||
                    return None
 | 
			
		||||
 | 
			
		||||
                # The choice is still deferred
 | 
			
		||||
                rebuild.add(frozenset(remain))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user