From ae15ac739e8a6e02faf051148d3353d02cc458c9 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Thu, 8 May 2008 19:18:20 +0000 Subject: [PATCH] Merged from trunk 10208:10241 | 10210 | Cache results for Task.__hash__() calls. | | zmedico | | | 10212 | Use find -path -or -name to match basenames of files in | | zmedico | INSTALL_MASK as suggested by solar in bug #219286, comment | | | #8. | | 10214 | Display satisfied blockers in green and show a small "b" | | zmedico | instead of a big "B" (similar to "f" for satisfied fetch | | | restrictions). | | 10216 | For consistency with the merge list display, show "block" | | zmedico | instead of "blocker" in the summary. | | 10218 | Make satisfied blockers "darkblue" by default. | | zmedico | | | 10220 | In install_mask(), discard stderr messages from the 'find' | | zmedico | command since some tokens from INSTALL_MASK can trigger lots | | | of warnings and errors that are irrelevant for our purposes. | | 10222 | Fix spelling of "SATISFIED". Thanks to Arfrever. | | zmedico | | | 10224 | Tolerate InvalidDependString exceptions when checking | | zmedico | visibility of installed packages. | | 10225 | Instead of doing automatic uninstalls in advance, install | | zmedico | conflicting packages first and then do the uninstall | | | afterwards. This requires special handling for file | | | collisions occur, but it's preferred because it ensures that | | | package files remain installed in a usable state whenever | | | possible. When file collisions occur between conflicting | | | packages, the contents entries for those files are removed | | | from the packages that are scheduled for uninstallation. | | | This prevents uninstallation operations from removing | | | overlapping files that have been claimed by conflicting | | | packages. | | 10229 | Fix findInstalledBlockers() to check for blockers in both | | zmedico | directions. | | 10231 | Don't use try/finally to close atomic_ofstream since we | | zmedico | don't want to call close() on this stream if an error | | | occurs. | | 10233 | Bug #220689 - Fix package selection logic so that it doesn't | | zmedico | trigger the code path from bug 219369 in some unwanted | | | cases. | | 10235 | Bug #220775 - Source isolated-functions.sh before trying to | | zmedico | call vecho. | | 10237 | Bug #220341 - USE=multislot can make an installed package | | zmedico | appear as if it doesn't satisfy a slot dependency. | | | Rebuilding the ebuild won't do any good as long as | | | USE=multislot is enabled since the newly built package still | | | won't have the expected slot. Therefore, assume that such | | | SLOT dependencies are already satisfied rather than forcing | | | a rebuild. | | 10239 | Don't save "uninstall" tasks in the resume list since | | zmedico | they'll be regenerated by dependency calculations upon | | | resume. | | 10241 | Remove unnecessary BlockerDB and BlockerCache | | zmedico | _installed_pkgs attributes. | svn path=/main/branches/prefix/; revision=10244 --- bin/dodoc | 1 + bin/misc-functions.sh | 3 +- .../task_scheduling.docbook | 12 +- man/color.map.5 | 6 + pym/_emerge/__init__.py | 296 ++++++++++++++---- pym/portage/__init__.py | 9 +- pym/portage/dbapi/vartree.py | 52 ++- pym/portage/output.py | 2 + 8 files changed, 310 insertions(+), 71 deletions(-) diff --git a/bin/dodoc b/bin/dodoc index db2f7baaf..11cb110f0 100755 --- a/bin/dodoc +++ b/bin/dodoc @@ -4,6 +4,7 @@ # $Id$ if [ $# -lt 1 ] ; then + source "${PORTAGE_BIN_PATH:-/usr/lib/portage/bin}"/isolated-functions.sh vecho "${0##*/}: at least one argument needed" 1>&2 exit 1 fi diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh index 9f498b804..5950a9245 100644 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -518,7 +518,8 @@ install_mask() { rm -Rf "${root}"/${no_inst} >&/dev/null # we also need to handle globs (*.a, *.h, etc) - find "${root}" -path "${no_inst}" -exec rm -fR {} \; >/dev/null + find "${root}" \( -path "${no_inst}" -or -name "${no_inst}" \) \ + -exec rm -fR {} \; >/dev/null 2>&1 done # set everything back the way we found it set +o noglob diff --git a/doc/dependency_resolution/task_scheduling.docbook b/doc/dependency_resolution/task_scheduling.docbook index f879980cd..01953128e 100644 --- a/doc/dependency_resolution/task_scheduling.docbook +++ b/doc/dependency_resolution/task_scheduling.docbook @@ -21,7 +21,7 @@ In order to avoid a conflict, a package may need to be uninstalled - in advance, rather than through replacement. The following constraints + rather than replaced. The following constraints protect inappropriate packages from being chosen for automatic uninstallation: @@ -46,6 +46,16 @@ + + In order to ensure that package files remain installed in a usable state + whenever possible, uninstallation operations are not executed + until after all associated conflicting packages have been installed. + When file collisions occur between conflicting packages, the contents + entries for those files are removed from the packages + that are scheduled for uninstallation. This prevents + uninstallation operations from removing overlapping files that + have been claimed by conflicting packages. + Circular Dependencies diff --git a/man/color.map.5 b/man/color.map.5 index 8d2bfeeaf..39f76a4cb 100644 --- a/man/color.map.5 +++ b/man/color.map.5 @@ -30,6 +30,12 @@ Defines color used for informational words. \fBMERGE_LIST_PROGRESS\fR = \fI"yellow"\fR Defines color used for numbers indicating merge progress. .TP +\fBPKG_BLOCKER\fR = \fI"red"\fR +Defines color used for unsatisfied blockers. +.TP +\fBPKG_BLOCKER_SATISFIED\fR = \fI"darkblue"\fR +Defines color used for satisfied blockers. +.TP \fBPKG_MERGE\fR = \fI"darkgreen"\fR Defines color used for packages planned to be merged. .TP diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index 993de914e..fbb3ea57f 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -738,7 +738,6 @@ class search(object): result = "" return result - class RootConfig(object): """This is used internally by depgraph to track information about a particular $ROOT.""" @@ -1241,7 +1240,7 @@ def show_masked_packages(masked_packages): return have_eapi_mask class Task(SlotObject): - __slots__ = ("_hash_key",) + __slots__ = ("_hash_key", "_hash_value") def _get_hash_key(self): hash_key = getattr(self, "_hash_key", None) @@ -1256,7 +1255,10 @@ class Task(SlotObject): return self._get_hash_key() != other def __hash__(self): - return hash(self._get_hash_key()) + hash_value = getattr(self, "_hash_value", None) + if hash_value is None: + self._hash_value = hash(self._get_hash_key()) + return self._hash_value def __len__(self): return len(self._get_hash_key()) @@ -1291,13 +1293,22 @@ class Package(Task): __slots__ = ("built", "cpv", "depth", "installed", "metadata", "onlydeps", "operation", "root", "type_name", - "cp", "cpv_slot", "pv_split", "slot_atom") + "category", "cp", "cpv_slot", "pf", "pv_split", "slot_atom") + + metadata_keys = [ + "CHOST", "COUNTER", "DEPEND", "EAPI", "IUSE", "KEYWORDS", + "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND", + "repository", "RESTRICT", "SLOT", "USE"] + def __init__(self, **kwargs): Task.__init__(self, **kwargs) self.cp = portage.cpv_getkey(self.cpv) self.slot_atom = "%s:%s" % (self.cp, self.metadata["SLOT"]) self.cpv_slot = "%s:%s" % (self.cpv, self.metadata["SLOT"]) - self.pv_split = portage.catpkgsplit(self.cpv)[1:] + cpv_parts = portage.catpkgsplit(self.cpv) + self.category = cpv_parts[0] + self.pv_split = cpv_parts[1:] + self.pf = self.cpv.replace(self.category + "/", "", 1) def _get_hash_key(self): hash_key = getattr(self, "_hash_key", None) @@ -1384,13 +1395,15 @@ class BlockerCache(DictMixin): 2) the old-style virtuals have changed """ class BlockerData(object): + + __slots__ = ("__weakref__", "atoms", "counter") + def __init__(self, counter, atoms): self.counter = counter self.atoms = atoms def __init__(self, myroot, vardb): self._vardb = vardb - self._installed_pkgs = set(vardb.cpv_all()) self._virtuals = vardb.settings.getvirtuals() self._cache_filename = os.path.join(myroot, portage.CACHE_PATH.lstrip(os.path.sep), "vdb_blockers.pickle") @@ -1525,6 +1538,107 @@ class BlockerCache(DictMixin): an AttributeError.""" return list(self) +class BlockerDB(object): + + def __init__(self, vartree, portdb): + self._vartree = vartree + self._portdb = portdb + self._blocker_cache = \ + BlockerCache(self._vartree.root, vartree.dbapi) + self._dep_check_trees = { self._vartree.root : { + "porttree" : self._vartree, + "vartree" : self._vartree, + }} + + def findInstalledBlockers(self, new_pkg): + blocker_cache = self._blocker_cache + dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"] + dep_check_trees = self._dep_check_trees + settings = self._vartree.settings + stale_cache = set(blocker_cache) + fake_vartree = \ + FakeVartree(self._vartree, + self._portdb, Package.metadata_keys, {}) + vardb = fake_vartree.dbapi + installed_pkgs = list(vardb) + + for inst_pkg in installed_pkgs: + stale_cache.discard(inst_pkg.cpv) + cached_blockers = blocker_cache.get(inst_pkg.cpv) + if cached_blockers is not None and \ + cached_blockers.counter != long(inst_pkg.metadata["COUNTER"]): + cached_blockers = None + if cached_blockers is not None: + blocker_atoms = cached_blockers.atoms + else: + myuse = inst_pkg.metadata["USE"].split() + # Use aux_get() to trigger FakeVartree global + # updates on *DEPEND when appropriate. + depstr = " ".join(vardb.aux_get(inst_pkg.cpv, dep_keys)) + try: + portage.dep._dep_check_strict = False + success, atoms = portage.dep_check(depstr, + vardb, settings, myuse=myuse, + trees=dep_check_trees, myroot=inst_pkg.root) + finally: + portage.dep._dep_check_strict = True + if not success: + pkg_location = os.path.join(inst_pkg.root, + portage.VDB_PATH, inst_pkg.category, inst_pkg.pf) + portage.writemsg("!!! %s/*DEPEND: %s\n" % \ + (pkg_location, atoms), noiselevel=-1) + continue + + blocker_atoms = [atom for atom in atoms \ + if atom.startswith("!")] + blocker_atoms.sort() + counter = long(inst_pkg.metadata["COUNTER"]) + blocker_cache[inst_pkg.cpv] = \ + blocker_cache.BlockerData(counter, blocker_atoms) + for cpv in stale_cache: + del blocker_cache[cpv] + blocker_cache.flush() + + blocker_parents = digraph() + blocker_atoms = [] + for pkg in installed_pkgs: + for blocker_atom in self._blocker_cache[pkg.cpv].atoms: + blocker_atom = blocker_atom[1:] + blocker_atoms.append(blocker_atom) + blocker_parents.add(blocker_atom, pkg) + + blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms) + blocking_pkgs = set() + for atom in blocker_atoms.iterAtomsForPackage(new_pkg): + blocking_pkgs.update(blocker_parents.parent_nodes(atom)) + + # Check for blockers in the other direction. + myuse = new_pkg.metadata["USE"].split() + depstr = " ".join(new_pkg.metadata[k] for k in dep_keys) + try: + portage.dep._dep_check_strict = False + success, atoms = portage.dep_check(depstr, + vardb, settings, myuse=myuse, + trees=dep_check_trees, myroot=new_pkg.root) + finally: + portage.dep._dep_check_strict = True + if not success: + # We should never get this far with invalid deps. + show_invalid_depstring_notice(new_pkg, depstr, atoms) + assert False + + blocker_atoms = [atom[1:] for atom in atoms \ + if atom.startswith("!")] + blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms) + for inst_pkg in installed_pkgs: + try: + blocker_atoms.iterAtomsForPackage(inst_pkg).next() + except (portage.exception.InvalidDependString, StopIteration): + continue + blocking_pkgs.add(inst_pkg) + + return blocking_pkgs + def show_invalid_depstring_notice(parent_node, depstring, error_msg): from formatter import AbstractFormatter, DumbWriter @@ -1678,10 +1792,7 @@ class depgraph(object): "binary":"bintree", "installed":"vartree"} - _mydbapi_keys = [ - "CHOST", "COUNTER", "DEPEND", "EAPI", "IUSE", "KEYWORDS", - "LICENSE", "PDEPEND", "PROVIDE", "RDEPEND", - "repository", "RESTRICT", "SLOT", "USE"] + _mydbapi_keys = Package.metadata_keys _dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"] @@ -2845,6 +2956,18 @@ class depgraph(object): cpv_list = db.xmatch("match-all", atom) else: cpv_list = db.match(atom) + + # USE=multislot can make an installed package appear as if + # it doesn't satisfy a slot dependency. Rebuilding the ebuild + # won't do any good as long as USE=multislot is enabled since + # the newly built package still won't have the expected slot. + # Therefore, assume that such SLOT dependencies are already + # satisfied rather than forcing a rebuild. + if installed and not cpv_list and matched_packages \ + and vardb.cpv_exists(matched_packages[-1].cpv) and \ + portage.dep.dep_getslot(atom): + cpv_list = [matched_packages[-1].cpv] + if not cpv_list: continue pkg_status = "merge" @@ -2906,7 +3029,8 @@ class depgraph(object): if not visible(pkgsettings, pkg): continue except portage.exception.InvalidDependString: - continue + if not installed: + continue # Enable upgrade or downgrade to a version # with visible KEYWORDS when the installed @@ -3011,16 +3135,6 @@ class depgraph(object): cur_use, cur_iuse) if reinstall_for_flags: reinstall = True - if not installed: - must_reinstall = empty or \ - (myarg and not selective) - if not reinstall_for_flags and \ - not must_reinstall and \ - cpv in vardb.match(atom): - # If the installed version is masked, it may - # be necessary to look at lower versions, - # in case there is a visible downgrade. - continue if not built: myeb = cpv matched_packages.append(pkg) @@ -3505,6 +3619,9 @@ class depgraph(object): return -1 myblocker_uninstalls = self._blocker_uninstalls.copy() retlist=[] + # Contains uninstall tasks that have been scheduled to + # occur after overlapping blockers have been installed. + scheduled_uninstalls = set() # Contains any Uninstall tasks that have been ignored # in order to avoid the circular deps code path. These # correspond to blocker conflicts that could not be @@ -3719,10 +3836,16 @@ class depgraph(object): selected_nodes = list(selected_nodes) selected_nodes.sort(cmp_circular_bias) + if not selected_nodes and scheduled_uninstalls: + selected_nodes = set() + for node in scheduled_uninstalls: + if not mygraph.child_nodes(node): + selected_nodes.add(node) + scheduled_uninstalls.difference_update(selected_nodes) + if not selected_nodes and not myblocker_uninstalls.is_empty(): # An Uninstall task needs to be executed in order to # avoid conflict if possible. - min_parent_deps = None uninst_task = None for task in myblocker_uninstalls.leaf_nodes(): @@ -3840,7 +3963,20 @@ class depgraph(object): uninst_task = task if uninst_task is not None: - selected_nodes = [uninst_task] + # The uninstall is performed only after blocking + # packages have been merged on top of it. File + # collisions between blocking packages are detected + # and removed from the list of files to be uninstalled. + scheduled_uninstalls.add(uninst_task) + parent_nodes = mygraph.parent_nodes(uninst_task) + + # Reverse the parent -> uninstall edges since we want + # to do the uninstall after blocking packages have + # been merged on top of it. + mygraph.remove(uninst_task) + for blocked_pkg in parent_nodes: + mygraph.add(blocked_pkg, uninst_task, + priority=BlockerDepPriority.instance) else: # None of the Uninstall tasks are acceptable, so # the corresponding blockers are unresolvable. @@ -3857,12 +3993,12 @@ class depgraph(object): ignored_uninstall_tasks.add(node) break - # After dropping an Uninstall task, reset - # the state variables for leaf node selection and - # continue trying to select leaf nodes. - prefer_asap = True - accept_root_node = False - continue + # After dropping an Uninstall task, reset + # the state variables for leaf node selection and + # continue trying to select leaf nodes. + prefer_asap = True + accept_root_node = False + continue if not selected_nodes: self._circular_deps_for_display = mygraph @@ -4023,6 +4159,8 @@ class depgraph(object): verbosity = ("--quiet" in self.myopts and 1 or \ "--verbose" in self.myopts and 3 or 2) favorites_set = InternalPackageSet(favorites) + oneshot = "--oneshot" in self.myopts or \ + "--onlydeps" in self.myopts changelogs=[] p=[] blockers = [] @@ -4251,24 +4389,35 @@ class depgraph(object): fetch=" " indent = " " * depth - if x[0]=="blocks": - addl=""+red("B")+" "+fetch+" " + if isinstance(x, Blocker): + if x.satisfied: + blocker_style = "PKG_BLOCKER_SATISFIED" + addl = "%s %s " % (colorize(blocker_style, "b"), fetch) + else: + blocker_style = "PKG_BLOCKER" + addl = "%s %s " % (colorize(blocker_style, "B"), fetch) if ordered: counters.blocks += 1 + if x.satisfied: + counters.blocks_satisfied += 1 resolved = portage.key_expand( pkg_key, mydb=vardb, settings=pkgsettings) if "--columns" in self.myopts and "--quiet" in self.myopts: - addl = addl + " " + red(resolved) + addl += " " + colorize(blocker_style, resolved) else: - addl = "[blocks " + addl + "] " + indent + red(resolved) + addl = "[%s %s] %s%s" % \ + (colorize(blocker_style, "blocks"), + addl, indent, colorize(blocker_style, resolved)) block_parents = self._blocker_parents.parent_nodes(x) block_parents = set([pnode[2] for pnode in block_parents]) block_parents = ", ".join(block_parents) if resolved!=x[2]: - addl += bad(" (\"%s\" is blocking %s)") % \ + addl += colorize(blocker_style, + " (\"%s\" is blocking %s)") % \ (pkg_key, block_parents) else: - addl += bad(" (is blocking %s)") % block_parents + addl += colorize(blocker_style, + " (is blocking %s)") % block_parents if isinstance(x, Blocker) and x.satisfied: p.append(addl) else: @@ -4573,7 +4722,8 @@ class depgraph(object): try: pkg_system = system_set.findAtomForPackage(pkg_key, metadata) pkg_world = world_set.findAtomForPackage(pkg_key, metadata) - if not pkg_world and myroot == self.target_root and \ + if not (oneshot or pkg_world) and \ + myroot == self.target_root and \ favorites_set.findAtomForPackage(pkg_key, metadata): # Maybe it will be added to world now. if create_world_atom(pkg_key, metadata, @@ -4936,7 +5086,7 @@ class depgraph(object): pkg_type, myroot, pkg_key, action = x if pkg_type not in self.pkg_tree_map: continue - if action not in ("merge", "uninstall"): + if action != "merge": continue mydb = trees[myroot][self.pkg_tree_map[pkg_type]].dbapi try: @@ -5319,6 +5469,7 @@ class PackageCounters(object): self.reinst = 0 self.uninst = 0 self.blocks = 0 + self.blocks_satisfied = 0 self.totalsize = 0 self.restrict_fetch = 0 self.restrict_fetch_satisfied = 0 @@ -5354,10 +5505,6 @@ class PackageCounters(object): details.append("%s uninstall" % self.uninst) if self.uninst > 1: details[-1] += "s" - if self.blocks > 0: - details.append("%s block" % self.blocks) - if self.blocks > 1: - details[-1] += "s" myoutput.append(", ".join(details)) if total_installs != 0: myoutput.append(")") @@ -5370,6 +5517,14 @@ class PackageCounters(object): if self.restrict_fetch_satisfied < self.restrict_fetch: myoutput.append(bad(" (%s unsatisfied)") % \ (self.restrict_fetch - self.restrict_fetch_satisfied)) + if self.blocks > 0: + myoutput.append("\nConflict: %s block" % \ + self.blocks) + if self.blocks > 1: + myoutput.append("s") + if self.blocks_satisfied < self.blocks: + myoutput.append(bad(" (%s unsatisfied)") % \ + (self.blocks - self.blocks_satisfied)) return "".join(myoutput) class MergeTask(object): @@ -5383,12 +5538,35 @@ class MergeTask(object): if settings.get("PORTAGE_DEBUG", "") == "1": self.edebug = 1 self.pkgsettings = {} + self._blocker_db = {} for root in trees: self.pkgsettings[root] = portage.config( clone=trees[root]["vartree"].settings) + self._blocker_db[root] = BlockerDB( + trees[root]["vartree"], + trees[root]["porttree"].dbapi) self.curval = 0 self._spawned_pids = [] - self._uninstall_queue = [] + + def _find_blockers(self, new_pkg): + for opt in ("--buildpkgonly", "--nodeps", + "--fetchonly", "--fetch-all-uri", "--pretend"): + if opt in self.myopts: + return None + + blocker_dblinks = [] + for blocking_pkg in self._blocker_db[ + new_pkg.root].findInstalledBlockers(new_pkg): + if new_pkg.slot_atom == blocking_pkg.slot_atom: + continue + if new_pkg.cpv == blocking_pkg.cpv: + continue + blocker_dblinks.append(portage.dblink( + blocking_pkg.category, blocking_pkg.pf, blocking_pkg.root, + self.pkgsettings[blocking_pkg.root], treetype="vartree", + vartree=self.trees[blocking_pkg.root]["vartree"])) + + return blocker_dblinks def merge(self, mylist, favorites, mtimedb): try: @@ -5418,17 +5596,6 @@ class MergeTask(object): pass spawned_pids.remove(pid) - def _dequeue_uninstall_tasks(self, mtimedb): - if not self._uninstall_queue: - return - for uninst_task in self._uninstall_queue: - root_config = self.trees[uninst_task.root]["root_config"] - unmerge(root_config, self.myopts, "unmerge", - [uninst_task.cpv], mtimedb["ldpath"], clean_world=0) - del mtimedb["resume"]["mergelist"][0] - mtimedb.commit() - del self._uninstall_queue[:] - def _merge(self, mylist, favorites, mtimedb): from portage.elog import elog_process from portage.elog.filtering import filter_mergephases @@ -5481,7 +5648,7 @@ class MergeTask(object): world_set = root_config.sets["world"] mtimedb["resume"]["mergelist"] = [list(x) for x in mylist \ - if isinstance(x, Package)] + if isinstance(x, Package) and x.operation == "merge"] mtimedb.commit() mymergelist = mylist @@ -5569,7 +5736,8 @@ class MergeTask(object): metadata = pkg.metadata if pkg.installed: if not (buildpkgonly or fetchonly or pretend): - self._uninstall_queue.append(pkg) + unmerge(root_config, self.myopts, "unmerge", + [pkg.cpv], mtimedb["ldpath"], clean_world=0) continue if x[0]=="blocks": @@ -5670,20 +5838,22 @@ class MergeTask(object): return retval bintree = self.trees[myroot]["bintree"] bintree.inject(pkg_key, filename=binpkg_tmpfile) - self._dequeue_uninstall_tasks(mtimedb) + if "--buildpkgonly" not in self.myopts: msg = " === (%s of %s) Merging (%s::%s)" % \ (mergecount, len(mymergelist), pkg_key, y) short_msg = "emerge: (%s of %s) %s Merge" % \ (mergecount, len(mymergelist), pkg_key) emergelog(xterm_titles, msg, short_msg=short_msg) + retval = portage.merge(pkgsettings["CATEGORY"], pkgsettings["PF"], pkgsettings["D"], os.path.join(pkgsettings["PORTAGE_BUILDDIR"], "build-info"), myroot, pkgsettings, myebuild=pkgsettings["EBUILD"], mytree="porttree", mydbapi=portdb, - vartree=vartree, prev_mtimes=ldpath_mtimes) + vartree=vartree, prev_mtimes=ldpath_mtimes, + blockers=self._find_blockers(pkg)) if retval != os.EX_OK: return retval elif "noclean" not in pkgsettings.features: @@ -5702,14 +5872,15 @@ class MergeTask(object): prev_mtimes=ldpath_mtimes) if retval != os.EX_OK: return retval - self._dequeue_uninstall_tasks(mtimedb) + retval = portage.merge(pkgsettings["CATEGORY"], pkgsettings["PF"], pkgsettings["D"], os.path.join(pkgsettings["PORTAGE_BUILDDIR"], "build-info"), myroot, pkgsettings, myebuild=pkgsettings["EBUILD"], mytree="porttree", mydbapi=portdb, - vartree=vartree, prev_mtimes=ldpath_mtimes) + vartree=vartree, prev_mtimes=ldpath_mtimes, + blockers=self._find_blockers(pkg)) if retval != os.EX_OK: return retval finally: @@ -5731,7 +5902,6 @@ class MergeTask(object): portage.locks.unlockdir(catdir_lock) elif x[0]=="binary": - self._dequeue_uninstall_tasks(mtimedb) #merge the tbz2 mytbz2 = self.trees[myroot]["bintree"].getname(pkg_key) if "--getbinpkg" in self.myopts: @@ -5787,7 +5957,8 @@ class MergeTask(object): retval = portage.pkgmerge(mytbz2, x[1], pkgsettings, mydbapi=bindb, vartree=self.trees[myroot]["vartree"], - prev_mtimes=ldpath_mtimes) + prev_mtimes=ldpath_mtimes, + blockers=self._find_blockers(pkg)) if retval != os.EX_OK: return retval #need to check for errors @@ -7951,6 +8122,7 @@ def action_build(settings, trees, mtimedb, fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts ask = "--ask" in myopts nodeps = "--nodeps" in myopts + oneshot = "--oneshot" in myopts or "--onlydeps" in myopts tree = "--tree" in myopts if nodeps and tree: tree = False @@ -8122,7 +8294,7 @@ def action_build(settings, trees, mtimedb, mergecount += 1 if mergecount==0: - if "--noreplace" in myopts and favorites: + if "--noreplace" in myopts and not oneshot and favorites: print for x in favorites: print " %s %s" % (good("*"), x) diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 765a2dbc0..82f866277 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -5364,13 +5364,13 @@ def movefile(src,dest,newmtime=None,sstat=None,mysettings=None): return newmtime def merge(mycat, mypkg, pkgloc, infloc, myroot, mysettings, myebuild=None, - mytree=None, mydbapi=None, vartree=None, prev_mtimes=None): + mytree=None, mydbapi=None, vartree=None, prev_mtimes=None, blockers=None): if not os.access(myroot + EPREFIX_LSTRIP, os.W_OK): writemsg("Permission denied: access('%s', W_OK)\n" % (myroot + EPREFIX_LSTRIP), noiselevel=-1) return errno.EACCES mylink = dblink(mycat, mypkg, myroot, mysettings, treetype=mytree, - vartree=vartree) + vartree=vartree, blockers=blockers) return mylink.merge(pkgloc, infloc, myroot, myebuild, mydbapi=mydbapi, prev_mtimes=prev_mtimes) @@ -6235,7 +6235,8 @@ class FetchlistDict(UserDict.DictMixin): """Returns keys for all packages within pkgdir""" return self.portdb.cp_list(self.cp, mytree=self.mytree) -def pkgmerge(mytbz2, myroot, mysettings, mydbapi=None, vartree=None, prev_mtimes=None): +def pkgmerge(mytbz2, myroot, mysettings, mydbapi=None, + vartree=None, prev_mtimes=None, blockers=None): """will merge a .tbz2 file, returning a list of runtime dependencies that must be satisfied, or None if there was a merge error. This code assumes the package exists.""" @@ -6365,7 +6366,7 @@ def pkgmerge(mytbz2, myroot, mysettings, mydbapi=None, vartree=None, prev_mtimes pkgloc = os.path.join(builddir, "image") mylink = dblink(mycat, mypkg, myroot, mysettings, vartree=vartree, - treetype="bintree") + treetype="bintree", blockers=blockers) retval = mylink.merge(pkgloc, infloc, myroot, myebuild, cleanup=0, mydbapi=mydbapi, prev_mtimes=prev_mtimes) did_merge_phase = True diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index b6158c054..6a9a448fe 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -1019,7 +1019,7 @@ class dblink(object): } def __init__(self, cat, pkg, myroot, mysettings, treetype=None, - vartree=None): + vartree=None, blockers=None): """ Creates a DBlink object for a given CPV. The given CPV may not be present in the database already. @@ -1048,6 +1048,7 @@ class dblink(object): from portage import db vartree = db[myroot]["vartree"] self.vartree = vartree + self._blockers = blockers self.dbroot = normalize_path(os.path.join(myroot, VDB_PATH)) self.dbcatdir = self.dbroot+"/"+cat @@ -1124,6 +1125,11 @@ class dblink(object): if os.path.exists(self.dbdir+"/CONTENTS"): os.unlink(self.dbdir+"/CONTENTS") + def _clear_contents_cache(self): + self.contentscache = None + self._contents_inodes = None + self._contents_basenames = None + def getcontents(self): """ Get the installed files of a given package (aka what that package installed) @@ -2006,6 +2012,7 @@ class dblink(object): """ srcroot = normalize_path(srcroot).rstrip(os.path.sep) + os.path.sep + destroot = normalize_path(destroot).rstrip(os.path.sep) + os.path.sep if not os.path.isdir(srcroot): writemsg("!!! Directory Not Found: D='%s'\n" % srcroot, @@ -2146,8 +2153,11 @@ class dblink(object): self._preserve_libs(srcroot, destroot, myfilelist+mylinklist, counter, inforoot) # check for package collisions - collisions = self._collision_protect(srcroot, destroot, others_in_slot, - myfilelist+mylinklist) + blockers = self._blockers + if blockers is None: + blockers = [] + collisions = self._collision_protect(srcroot, destroot, + others_in_slot + blockers, myfilelist + mylinklist) # Make sure the ebuild environment is initialized and that ${T}/elog # exists for logging of collision-protect eerror messages. @@ -2367,6 +2377,42 @@ class dblink(object): self.dbdir = self.dbpkgdir self.delete() _movefile(self.dbtmpdir, self.dbpkgdir, mysettings=self.settings) + + # Check for file collisions with blocking packages + # and remove any colliding files from their CONTENTS + # since they now belong to this package. + self._clear_contents_cache() + contents = self.getcontents() + destroot_len = len(destroot) - 1 + for blocker in blockers: + blocker_contents = blocker.getcontents() + collisions = [] + for filename in blocker_contents: + relative_filename = filename[destroot_len:] + if self.isowner(relative_filename, destroot): + collisions.append(filename) + if not collisions: + continue + for filename in collisions: + del blocker_contents[filename] + f = atomic_ofstream(os.path.join(blocker.dbdir, "CONTENTS")) + for filename in sorted(blocker_contents): + entry_data = blocker_contents[filename] + entry_type = entry_data[0] + relative_filename = filename[destroot_len:] + if entry_type == "obj": + entry_type, mtime, md5sum = entry_data + line = "%s %s %s %s\n" % \ + (entry_type, relative_filename, md5sum, mtime) + elif entry_type == "sym": + entry_type, mtime, link = entry_data + line = "%s %s -> %s %s\n" % \ + (entry_type, relative_filename, link, mtime) + else: # dir, dev, fif + line = "%s %s\n" % (entry_type, relative_filename) + f.write(line) + f.close() + # Due to mtime granularity, mtime checks do not always properly # invalidate vardbapi caches. self.vartree.dbapi.mtdircache.pop(self.cat, None) diff --git a/pym/portage/output.py b/pym/portage/output.py index 5338b1eb9..e9a1a559a 100644 --- a/pym/portage/output.py +++ b/pym/portage/output.py @@ -148,6 +148,8 @@ codes["INFORM"] = codes["darkgreen"] codes["UNMERGE_WARN"] = codes["red"] codes["SECURITY_WARN"] = codes["red"] codes["MERGE_LIST_PROGRESS"] = codes["yellow"] +codes["PKG_BLOCKER"] = codes["red"] +codes["PKG_BLOCKER_SATISFIED"] = codes["darkblue"] codes["PKG_MERGE"] = codes["darkgreen"] codes["PKG_MERGE_SYSTEM"] = codes["darkgreen"] codes["PKG_MERGE_WORLD"] = codes["green"] -- 2.26.2