From: Fabian Groffen Date: Sat, 24 Oct 2009 11:22:31 +0000 (-0000) Subject: Merged from trunk -r14701:14710 X-Git-Url: http://git.tremily.us/gitweb.cgi?a=commitdiff_plain;h=70b0b3cdfd18b691a8a57f8e8d1f115d28e3addb;p=portage.git Merged from trunk -r14701:14710 | 14702 | Factor the --tree code out of depgraph.display(). | | zmedico | | | 14703 | Add a --unordered-display option for use with --tree. This | | zmedico | allows the display to be optimized differently since the | | | merge order is not preserved. Thanks to Sebastian Mingramm | | | (few) for the initial patch. | | 14704 | Factor out duplicate "These are the packages that would be | | zmedico | merged", "Calculating dependencies", and spinner cleanup | | | code. | | 14705 | Factor vdb loading code out of the depgraph constructor, | | zmedico | since this procedure is slow and it generates spinner | | | output, which isn't very nice behavior for a constructor. | | | Now it's called on-demand by various methods when necessary. | | 14706 | Bug #289967 - Update installsources rsync code for | | zmedico | >=debugedit-4.4.6-r2. Thanks to Peter Alfredsen | | | for this patch. | | 14707 | Use find with -print0 for absolute safety. | | zmedico | | | 14708 | Add --help output for --unordered-display, and move man page | | zmedico | docs from the actions to the options section. | | 14709 | Use urllib.urlopen() instead of portage.fetch() for fetching | | zmedico | metadata.dtd. | | 14710 | Bug #134466 - Add a --ask-enter-invalid option. When used | | zmedico | together with the --ask option, interpret a single "Enter" | | | key press as invalid input. This helps prevent accidental | | | acceptance of the first choice. | svn path=/main/branches/prefix/; revision=14723 --- diff --git a/bin/ebuild-helpers/prepstrip b/bin/ebuild-helpers/prepstrip index 5c0a9dc5f..84095f434 100755 --- a/bin/ebuild-helpers/prepstrip +++ b/bin/ebuild-helpers/prepstrip @@ -24,6 +24,7 @@ type -P -- ${OBJCOPY} > /dev/null || OBJCOPY=objcopy # of the section when it has the ALLOC flag set on it ... export SAFE_STRIP_FLAGS="--strip-unneeded" export PORTAGE_STRIP_FLAGS=${PORTAGE_STRIP_FLAGS-${SAFE_STRIP_FLAGS} -R .comment} +prepstrip_sources_dir="${EPREFIX}"/usr/src/debug/${CATEGORY}/${PF} if hasq installsources ${FEATURES} && ! type -P debugedit >/dev/null ; then ewarn "FEATURES=installsources is enabled but the debugedit binary could not" @@ -48,17 +49,10 @@ save_elf_sources() { local x=$1 local inode=$(inode_var_name "$x") [[ -n ${!inode} ]] && return 0 - local sources_dir=${EPREFIX}/usr/src/debug/${CATEGORY}/${PF} - debugedit -b "${WORKDIR}" -d "${sources_dir}" \ + debugedit -b "${WORKDIR}" -d "${prepstrip_sources_dir}" \ -l "${T}"/debug.sources "${x}" - if [[ -s ${T}/debug.sources ]] ; then - [[ -d ${D}${sources_dir} ]] || mkdir -p "${D}${sources_dir}" - grep -zv '/$' "${T}"/debug.sources | \ - (cd "${WORKDIR}"; LANG=C sort -z -u | \ - rsync -rtL0 --files-from=- "${WORKDIR}/" "${D}${sources_dir}/" ) - fi } - + save_elf_debug() { hasq splitdebug ${FEATURES} || return 0 @@ -191,3 +185,23 @@ do chmod u-w "${x}" fi done + +if [[ -s ${T}/debug.sources ]] && \ + hasq installsources ${FEATURES} && \ + ! hasq installsources ${RESTRICT} && \ + type -P debugedit >/dev/null +then + vecho "installsources: rsyncing source files" + [[ -d ${D}${prepstrip_sources_dir} ]] || mkdir -p "${D}${prepstrip_sources_dir}" + grep -zv '/<[^/>]*>$' "${T}"/debug.sources | \ + (cd "${WORKDIR}"; LANG=C sort -z -u | \ + rsync -tL0 --files-from=- "${WORKDIR}/" "${D}${prepstrip_sources_dir}/" ) + + # Preserve directory structure. + # Needed after running save_elf_sources. + # https://bugzilla.redhat.com/show_bug.cgi?id=444310 + while read -r -d $'\0' emptydir + do + touch "$emptydir"/.keepdir + done < <(find "${D}${prepstrip_sources_dir}/" -type d -empty -print0) +fi diff --git a/bin/repoman b/bin/repoman index 68a8f799e..5169da449 100755 --- a/bin/repoman +++ b/bin/repoman @@ -25,6 +25,7 @@ import sys import tempfile import time import platform +import urllib from io import StringIO from itertools import chain @@ -440,6 +441,10 @@ suspect_rdepend = frozenset([ "x11-misc/imake", ]) +metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd' +# force refetch if the local copy creation time is older than this +metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days + # file.executable no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"]) @@ -765,59 +770,80 @@ desktop_pattern = re.compile(r'.*\.desktop$') for x in qacats: stats[x]=0 fails[x]=[] + xmllint_capable = False metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd') -if options.mode == "manifest": - pass -elif not find_binary('xmllint'): - print(red("!!! xmllint not found. Can't check metadata.xml.\n")) - if options.xml_parse or repolevel==3: - print(red("!!!")+" sorry, xmllint is needed. failing\n") - sys.exit(1) -else: - #hardcoded paths/urls suck. :-/ - must_fetch=1 - backup_exists=0 - try: - # if it's been over a week since fetching (or the system clock is fscked), grab an updated copy of metadata.dtd - # clock is fscked or it's been a week. time to grab a new one. - ct = os.stat(metadata_dtd)[ST_CTIME] - if abs(time.time() - ct) > (60*60*24*7): - # don't trap the exception, we're watching for errno 2 (file not found), anything else is a bug. - backup_exists=1 - else: - must_fetch=0 - except (OSError,IOError) as e: - if e.errno != 2: - print(red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)) - sys.exit(1) +def fetch_metadata_dtd(): + """ + Fetch metadata.dtd if it doesn't exist or the ctime is older than + metadata_dtd_ctime_interval. + @rtype: bool + @returns: True if successful, otherwise False + """ + + must_fetch = True + metadata_dtd_st = None + current_time = int(time.time()) + try: + metadata_dtd_st = os.stat(metadata_dtd) + except EnvironmentError as e: + if e.errno not in (errno.ENOENT, errno.ESTALE): + raise + del e + else: + # Trigger fetch if metadata.dtd mtime is old or clock is wrong. + if abs(current_time - metadata_dtd_st.st_ctime) \ + < metadata_dtd_ctime_interval: + must_fetch = False if must_fetch: - print() - print(green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now") print() - val = 0 + print(green("***") + " the local copy of metadata.dtd " + \ + "needs to be refetched, doing that now") + print() try: + url_f = urllib.urlopen(metadata_dtd_uri) + last_modified = url_f.info().getdate('last-modified') + if last_modified is not None: + last_modified = time.mktime(last_modified) + + metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid()) try: - os.unlink(metadata_dtd) - except OSError as e: - if e.errno != errno.ENOENT: - raise - del e - val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \ - try_mirrors=0) + local_f = open(metadata_dtd_tmp, mode='wb') + local_f.write(url_f.read()) + local_f.close() + if last_modified is not None: + os.utime(metadata_dtd_tmp, + (int(last_modified), int(last_modified))) + os.rename(metadata_dtd_tmp, metadata_dtd) + finally: + try: + os.unlink(metadata_dtd_tmp) + except OSError: + pass - except SystemExit as e: - raise # Need to propogate this - except Exception as e: + url_f.close() + + except EnvironmentError as e: print() - print(red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught") - print(red("!!!")+" exception '%s' though." % str(e)) - val=0 - if not val: + print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri) + print(red("!!!")+" exception '%s' though." % (e,)) print(red("!!!")+" fetching new metadata.dtd failed, aborting") - sys.exit(1) + return False + + return True + +if options.mode == "manifest": + pass +elif not find_binary('xmllint'): + print(red("!!! xmllint not found. Can't check metadata.xml.\n")) + if options.xml_parse or repolevel==3: + print(red("!!!")+" sorry, xmllint is needed. failing\n") + sys.exit(1) +else: + if not fetch_metadata_dtd(): + sys.exit(1) #this can be problematic if xmllint changes their output xmllint_capable=True diff --git a/man/emerge.1 b/man/emerge.1 index a04a06b28..346d444c2 100644 --- a/man/emerge.1 +++ b/man/emerge.1 @@ -264,7 +264,17 @@ without \fB\-\-pretend\fR, as dependencies will only need to be calculated once. \fBWARNING: If the "Enter" key is pressed at the prompt (with no other input), it is interpreted as acceptance of the first choice. Note that the input buffer is not cleared prior to the prompt, so an accidental press of the -"Enter" key at any time prior to the prompt will be interpreted as a choice!\fR +"Enter" key at any time prior to the prompt will be interpreted as a choice! +Use the \-\-ask\-enter\-invalid option if you want a single "Enter" key +press to be interpreted as invalid input.\fR +.TP +.BR "\-\-ask\-enter\-invalid" +When used together with the \fB\-\-ask\fR option, +interpret a single "Enter" key press as +invalid input. This helps prevent accidental +acceptance of the first choice. This option is +intended to be set in the \fBmake.conf\fR(5) +\fBEMERGE_DEFAULT_OPTS\fR variable. .TP .BR "\-\-binpkg\-respect\-use < y | n >" Tells emerge to ignore binary packages if their use flags @@ -501,6 +511,13 @@ Shows the dependency tree for the given target by indenting dependencies. This is only really useful in combination with \fB\-\-emptytree\fR or \fB\-\-update\fR and \fB\-\-deep\fR. .TP +.BR "\-\-unordered\-display" +By default the displayed merge list is sorted using the +order in which the packages will be merged. When +\fB\-\-tree\fR is used together with this option, this +constraint is removed, hopefully leading to a more +readable dependency tree. +.TP .BR "\-\-use\-ebuild\-visibility[=n]" Use unbuilt ebuild metadata for visibility checks on built packages. diff --git a/pym/_emerge/Scheduler.py b/pym/_emerge/Scheduler.py index c95003dd7..f969f7105 100644 --- a/pym/_emerge/Scheduler.py +++ b/pym/_emerge/Scheduler.py @@ -1490,23 +1490,6 @@ class Scheduler(PollScheduler): """ print(colorize("GOOD", "*** Resuming merge...")) - if self._show_list(): - if "--tree" in self.myopts: - portage.writemsg_stdout("\n" + \ - darkgreen("These are the packages that " + \ - "would be merged, in reverse order:\n\n")) - - else: - portage.writemsg_stdout("\n" + \ - darkgreen("These are the packages that " + \ - "would be merged, in order:\n\n")) - - show_spinner = "--quiet" not in self.myopts and \ - "--nodeps" not in self.myopts - - if show_spinner: - print("Calculating dependencies ", end=' ') - myparams = create_depgraph_params(self.myopts, None) success = False e = None @@ -1522,9 +1505,6 @@ class Scheduler(PollScheduler): mydepgraph = e.depgraph dropped_tasks = set() - if show_spinner: - print("\b\b... done!") - if e is not None: def unsatisfied_resume_dep_msg(): mydepgraph.display_problems() diff --git a/pym/_emerge/actions.py b/pym/_emerge/actions.py index ec661ad86..26085fead 100644 --- a/pym/_emerge/actions.py +++ b/pym/_emerge/actions.py @@ -134,6 +134,7 @@ def action_build(settings, trees, mtimedb, pretend = "--pretend" in myopts fetchonly = "--fetchonly" in myopts or "--fetch-all-uri" in myopts ask = "--ask" in myopts + enter_invalid = '--ask-enter-invalid' in myopts nodeps = "--nodeps" in myopts oneshot = "--oneshot" in myopts or "--onlydeps" in myopts tree = "--tree" in myopts @@ -165,36 +166,10 @@ def action_build(settings, trees, mtimedb, writemsg("%s%s\n" % (prefix, line)) writemsg(prefix + "\n") - if "--quiet" not in myopts and \ - ("--pretend" in myopts or "--ask" in myopts or \ - "--tree" in myopts or "--verbose" in myopts): - action = "" - if "--fetchonly" in myopts or "--fetch-all-uri" in myopts: - action = "fetched" - elif "--buildpkgonly" in myopts: - action = "built" - else: - action = "merged" - if "--tree" in myopts and action != "fetched": # Tree doesn't work with fetching - print() - print(darkgreen("These are the packages that would be %s, in reverse order:") % action) - print() - else: - print() - print(darkgreen("These are the packages that would be %s, in order:") % action) - print() - - show_spinner = "--quiet" not in myopts and "--nodeps" not in myopts - if not show_spinner: - spinner.update = spinner.update_quiet - if resume: favorites = mtimedb["resume"].get("favorites") if not isinstance(favorites, list): favorites = [] - - if show_spinner: - print("Calculating dependencies ", end=' ') myparams = create_depgraph_params(myopts, myaction) resume_data = mtimedb["resume"] @@ -215,8 +190,7 @@ def action_build(settings, trees, mtimedb, depgraph.UnsatisfiedResumeDep) as e: if isinstance(e, depgraph.UnsatisfiedResumeDep): mydepgraph = e.depgraph - if show_spinner: - print() + from textwrap import wrap from portage.output import EOutput out = EOutput() @@ -268,9 +242,6 @@ def action_build(settings, trees, mtimedb, "the operation manually." for line in wrap(msg, 72): out.eerror(line) - else: - if show_spinner: - print("\b\b... done!") if success: if dropped_tasks: @@ -298,24 +269,15 @@ def action_build(settings, trees, mtimedb, print(darkgreen("emerge: It seems we have nothing to resume...")) return os.EX_OK - if "--quiet" not in myopts and "--nodeps" not in myopts: - print("Calculating dependencies ", end=' ') - sys.stdout.flush() - myparams = create_depgraph_params(myopts, myaction) try: success, mydepgraph, favorites = backtrack_depgraph( settings, trees, myopts, myparams, myaction, myfiles, spinner) except portage.exception.PackageSetNotFound as e: - if show_spinner: - print("\b\b... done!") root_config = trees[settings["ROOT"]]["root_config"] display_missing_pkg_set(root_config, e.value) return 1 - if show_spinner: - print("\b\b... done!") - if not success: mydepgraph.display_problems() return 1 @@ -379,7 +341,7 @@ def action_build(settings, trees, mtimedb, else: prompt="Would you like to merge these packages?" print() - if "--ask" in myopts and userquery(prompt) == "No": + if "--ask" in myopts and userquery(prompt, enter_invalid) == "No": print() print("Quitting.") print() @@ -480,6 +442,7 @@ def action_build(settings, trees, mtimedb, return retval def action_config(settings, trees, myopts, myfiles): + enter_invalid = '--ask-enter-invalid' in myopts if len(myfiles) != 1: print(red("!!! config can only take a single package atom at this time\n")) sys.exit(1) @@ -509,7 +472,7 @@ def action_config(settings, trees, myopts, myfiles): print(options[-1]+") "+pkg) print("X) Cancel") options.append("X") - idx = userquery("Selection?", options) + idx = userquery("Selection?", enter_invalid, responses=options) if idx == "X": sys.exit(0) pkg = pkgs[int(idx)-1] @@ -524,7 +487,7 @@ def action_config(settings, trees, myopts, myfiles): print() if "--ask" in myopts: - if userquery("Ready to configure "+pkg+"?") == "No": + if userquery("Ready to configure %s?" % pkg, enter_invalid) == "No": sys.exit(0) else: print("Configuring pkg...") @@ -1181,6 +1144,7 @@ def calc_depclean(settings, trees, ldpath_mtimes, return 0, [], False, required_pkgs_total def action_deselect(settings, trees, opts, atoms): + enter_invalid = '--ask-enter-invalid' in opts root_config = trees[settings['ROOT']]['root_config'] world_set = root_config.sets['selected'] if not hasattr(world_set, 'update'): @@ -1223,7 +1187,7 @@ def action_deselect(settings, trees, opts, atoms): if '--ask' in opts: prompt = "Would you like to remove these " + \ "packages from your world favorites?" - if userquery(prompt) == 'No': + if userquery(prompt, enter_invalid) == 'No': return os.EX_OK remaining = set(world_set) @@ -1718,6 +1682,7 @@ def action_search(root_config, myopts, myfiles, spinner): searchinstance.output() def action_sync(settings, trees, mtimedb, myopts, myaction): + enter_invalid = '--ask-enter-invalid' in myopts xterm_titles = "notitles" not in settings.features emergelog(xterm_titles, " === sync") portdb = trees[settings["ROOT"]]["porttree"].dbapi @@ -1963,7 +1928,9 @@ def action_sync(settings, trees, mtimedb, myopts, myaction): if (retries==0): if "--ask" in myopts: - if userquery("Do you want to sync your Portage tree with the mirror at\n" + blue(dosyncuri) + bold("?"))=="No": + if userquery("Do you want to sync your Portage tree " + \ + "with the mirror at\n" + blue(dosyncuri) + bold("?"), + enter_invalid) == "No": print() print("Quitting.") print() diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py index fcda3ec65..cef1d47e8 100644 --- a/pym/_emerge/depgraph.py +++ b/pym/_emerge/depgraph.py @@ -97,6 +97,7 @@ class _dynamic_depgraph_config(object): def __init__(self, depgraph, myparams, allow_backtracking, runtime_pkg_mask): self.myparams = myparams.copy() + self._vdb_loaded = False self._allow_backtracking = allow_backtracking # Maps slot atom to package for each Package added to the graph. self._slot_pkg_map = {} @@ -171,26 +172,9 @@ class _dynamic_depgraph_config(object): for myroot in depgraph._frozen_config.trees: self._slot_pkg_map[myroot] = {} vardb = depgraph._frozen_config.trees[myroot]["vartree"].dbapi - preload_installed_pkgs = \ - "--nodeps" not in depgraph._frozen_config.myopts and \ - "--buildpkgonly" not in depgraph._frozen_config.myopts - # This fakedbapi instance will model the state that the vdb will + # This dbapi instance will model the state that the vdb will # have after new packages have been installed. fakedb = PackageVirtualDbapi(vardb.settings) - if preload_installed_pkgs: - for pkg in vardb: - depgraph._spinner_update() - # This triggers metadata updates via FakeVartree. - vardb.aux_get(pkg.cpv, []) - fakedb.cpv_inject(pkg) - - # Now that the vardb state is cached in our FakeVartree, - # we won't be needing the real vartree cache for awhile. - # To make some room on the heap, clear the vardbapi - # caches. - depgraph._frozen_config._trees_orig[myroot - ]["vartree"].dbapi._clear_cache() - gc.collect() self.mydbapi[myroot] = fakedb def graph_tree(): @@ -268,6 +252,43 @@ class depgraph(object): self._select_atoms = self._select_atoms_highest_available self._select_package = self._select_pkg_highest_available + def _load_vdb(self): + """ + Load installed package metadata if appropriate. This used to be called + from the constructor, but that wasn't very nice since this procedure + is slow and it generates spinner output. So, now it's called on-demand + by various methods when necessary. + """ + + if self._dynamic_config._vdb_loaded: + return + + for myroot in self._frozen_config.trees: + + preload_installed_pkgs = \ + "--nodeps" not in self._frozen_config.myopts and \ + "--buildpkgonly" not in self._frozen_config.myopts + + if preload_installed_pkgs: + fakedb = self._dynamic_config._graph_trees[ + myroot]["vartree"].dbapi + vardb = self._frozen_config.trees[myroot]["vartree"].dbapi + for pkg in vardb: + self._spinner_update() + # This triggers metadata updates via FakeVartree. + vardb.aux_get(pkg.cpv, []) + fakedb.cpv_inject(pkg) + + # Now that the vardb state is cached in our FakeVartree, + # we won't be needing the real vartree cache for awhile. + # To make some room on the heap, clear the vardbapi + # caches. + self._frozen_config._trees_orig[myroot + ]["vartree"].dbapi._clear_cache() + gc.collect() + + self._dynamic_config._vdb_loaded = True + def _spinner_update(self): if self._frozen_config.spinner: self._frozen_config.spinner.update() @@ -1438,6 +1459,7 @@ class depgraph(object): """Given a list of .tbz2s, .ebuilds sets, and deps, populate self._dynamic_config._initial_arg_list and call self._resolve to create the appropriate depgraph and return a favorite list.""" + self._load_vdb() debug = "--debug" in self._frozen_config.myopts root_config = self._frozen_config.roots[self._frozen_config.target_root] sets = root_config.sets @@ -2589,6 +2611,8 @@ class depgraph(object): # Skip this to avoid consuming enough time to disturb users. return 1 + self._load_vdb() + # Put the depgraph into a mode that causes it to only # select packages that have already been added to the # graph or those that are installed and have not been @@ -3876,6 +3900,7 @@ class depgraph(object): oneshot = "--oneshot" in self._frozen_config.myopts or \ "--onlydeps" in self._frozen_config.myopts columns = "--columns" in self._frozen_config.myopts + tree_display = "--tree" in self._frozen_config.myopts changelogs=[] p=[] blockers = [] @@ -3953,151 +3978,23 @@ class depgraph(object): return ret repo_display = RepoDisplay(self._frozen_config.roots) - - tree_nodes = [] - display_list = [] - mygraph = self._dynamic_config.digraph.copy() - - # If there are any Uninstall instances, add the corresponding - # blockers to the digraph (useful for --tree display). - - executed_uninstalls = set(node for node in mylist \ - if isinstance(node, Package) and node.operation == "unmerge") - - for uninstall in self._dynamic_config._blocker_uninstalls.leaf_nodes(): - uninstall_parents = \ - self._dynamic_config._blocker_uninstalls.parent_nodes(uninstall) - if not uninstall_parents: - continue - - # Remove the corresponding "nomerge" node and substitute - # the Uninstall node. - inst_pkg = self._pkg(uninstall.cpv, "installed", - uninstall.root_config, installed=True) - - try: - mygraph.remove(inst_pkg) - except KeyError: - pass - - try: - inst_pkg_blockers = self._dynamic_config._blocker_parents.child_nodes(inst_pkg) - except KeyError: - inst_pkg_blockers = [] - - # Break the Package -> Uninstall edges. - mygraph.remove(uninstall) - - # Resolution of a package's blockers - # depend on it's own uninstallation. - for blocker in inst_pkg_blockers: - mygraph.add(uninstall, blocker) - - # Expand Package -> Uninstall edges into - # Package -> Blocker -> Uninstall edges. - for blocker in uninstall_parents: - mygraph.add(uninstall, blocker) - for parent in self._dynamic_config._blocker_parents.parent_nodes(blocker): - if parent != inst_pkg: - mygraph.add(blocker, parent) - - # If the uninstall task did not need to be executed because - # of an upgrade, display Blocker -> Upgrade edges since the - # corresponding Blocker -> Uninstall edges will not be shown. - upgrade_node = \ - self._dynamic_config._slot_pkg_map[uninstall.root].get(uninstall.slot_atom) - if upgrade_node is not None and \ - uninstall not in executed_uninstalls: - for blocker in uninstall_parents: - mygraph.add(upgrade_node, blocker) - unsatisfied_blockers = [] - i = 0 - depth = 0 - shown_edges = set() + ordered_nodes = [] for x in mylist: if isinstance(x, Blocker) and not x.satisfied: unsatisfied_blockers.append(x) - continue - graph_key = x - if "--tree" in self._frozen_config.myopts: - depth = len(tree_nodes) - while depth and graph_key not in \ - mygraph.child_nodes(tree_nodes[depth-1]): - depth -= 1 - if depth: - tree_nodes = tree_nodes[:depth] - tree_nodes.append(graph_key) - display_list.append((x, depth, True)) - shown_edges.add((graph_key, tree_nodes[depth-1])) - else: - traversed_nodes = set() # prevent endless circles - traversed_nodes.add(graph_key) - def add_parents(current_node, ordered): - parent_nodes = None - # Do not traverse to parents if this node is an - # an argument or a direct member of a set that has - # been specified as an argument (system or world). - if current_node not in self._dynamic_config._set_nodes: - parent_nodes = mygraph.parent_nodes(current_node) - if parent_nodes: - child_nodes = set(mygraph.child_nodes(current_node)) - selected_parent = None - # First, try to avoid a direct cycle. - for node in parent_nodes: - if not isinstance(node, (Blocker, Package)): - continue - if node not in traversed_nodes and \ - node not in child_nodes: - edge = (current_node, node) - if edge in shown_edges: - continue - selected_parent = node - break - if not selected_parent: - # A direct cycle is unavoidable. - for node in parent_nodes: - if not isinstance(node, (Blocker, Package)): - continue - if node not in traversed_nodes: - edge = (current_node, node) - if edge in shown_edges: - continue - selected_parent = node - break - if selected_parent: - shown_edges.add((current_node, selected_parent)) - traversed_nodes.add(selected_parent) - add_parents(selected_parent, False) - display_list.append((current_node, - len(tree_nodes), ordered)) - tree_nodes.append(current_node) - tree_nodes = [] - add_parents(graph_key, True) else: - display_list.append((x, depth, True)) + ordered_nodes.append(x) + + if tree_display: + display_list = self._tree_display(ordered_nodes) + else: + display_list = [(x, 0, True) for x in ordered_nodes] + mylist = display_list for x in unsatisfied_blockers: mylist.append((x, 0, True)) - last_merge_depth = 0 - for i in range(len(mylist)-1,-1,-1): - graph_key, depth, ordered = mylist[i] - if not ordered and depth == 0 and i > 0 \ - and graph_key == mylist[i-1][0] and \ - mylist[i-1][1] == 0: - # An ordered node got a consecutive duplicate when the tree was - # being filled in. - del mylist[i] - continue - if ordered and graph_key[-1] != "nomerge": - last_merge_depth = depth - continue - if depth >= last_merge_depth or \ - i < len(mylist) - 1 and \ - depth >= mylist[i+1][1]: - del mylist[i] - # files to fetch list - avoids counting a same file twice # in size display (verbose mode) myfetchlist=[] @@ -4605,6 +4502,178 @@ class depgraph(object): sys.stdout.flush() return os.EX_OK + def _tree_display(self, mylist): + + # If there are any Uninstall instances, add the + # corresponding blockers to the digraph. + mygraph = self._dynamic_config.digraph.copy() + + executed_uninstalls = set(node for node in mylist \ + if isinstance(node, Package) and node.operation == "unmerge") + + for uninstall in self._dynamic_config._blocker_uninstalls.leaf_nodes(): + uninstall_parents = \ + self._dynamic_config._blocker_uninstalls.parent_nodes(uninstall) + if not uninstall_parents: + continue + + # Remove the corresponding "nomerge" node and substitute + # the Uninstall node. + inst_pkg = self._pkg(uninstall.cpv, "installed", + uninstall.root_config, installed=True) + + try: + mygraph.remove(inst_pkg) + except KeyError: + pass + + try: + inst_pkg_blockers = self._dynamic_config._blocker_parents.child_nodes(inst_pkg) + except KeyError: + inst_pkg_blockers = [] + + # Break the Package -> Uninstall edges. + mygraph.remove(uninstall) + + # Resolution of a package's blockers + # depend on it's own uninstallation. + for blocker in inst_pkg_blockers: + mygraph.add(uninstall, blocker) + + # Expand Package -> Uninstall edges into + # Package -> Blocker -> Uninstall edges. + for blocker in uninstall_parents: + mygraph.add(uninstall, blocker) + for parent in self._dynamic_config._blocker_parents.parent_nodes(blocker): + if parent != inst_pkg: + mygraph.add(blocker, parent) + + # If the uninstall task did not need to be executed because + # of an upgrade, display Blocker -> Upgrade edges since the + # corresponding Blocker -> Uninstall edges will not be shown. + upgrade_node = \ + self._dynamic_config._slot_pkg_map[uninstall.root].get(uninstall.slot_atom) + if upgrade_node is not None and \ + uninstall not in executed_uninstalls: + for blocker in uninstall_parents: + mygraph.add(upgrade_node, blocker) + + if "--unordered-display" in self._frozen_config.myopts: + display_list = self._unordered_tree_display(mygraph, mylist) + else: + display_list = self._ordered_tree_display(mygraph, mylist) + + self._prune_tree_display(display_list) + + return display_list + + def _unordered_tree_display(self, mygraph, mylist): + display_list = [] + seen_nodes = set() + + def print_node(node, depth): + + if node in seen_nodes: + pass + else: + seen_nodes.add(node) + + if isinstance(node, Package): + display_list.append((node, depth, True)) + else: + depth = -1 + + for child_node in mygraph.child_nodes(node): + print_node(child_node, depth + 1) + + for root_node in mygraph.root_nodes(): + print_node(root_node, 0) + + return display_list + + def _ordered_tree_display(self, mygraph, mylist): + depth = 0 + shown_edges = set() + tree_nodes = [] + display_list = [] + + for x in mylist: + depth = len(tree_nodes) + while depth and x not in \ + mygraph.child_nodes(tree_nodes[depth-1]): + depth -= 1 + if depth: + tree_nodes = tree_nodes[:depth] + tree_nodes.append(x) + display_list.append((x, depth, True)) + shown_edges.add((x, tree_nodes[depth-1])) + else: + traversed_nodes = set() # prevent endless circles + traversed_nodes.add(x) + def add_parents(current_node, ordered): + parent_nodes = None + # Do not traverse to parents if this node is an + # an argument or a direct member of a set that has + # been specified as an argument (system or world). + if current_node not in self._dynamic_config._set_nodes: + parent_nodes = mygraph.parent_nodes(current_node) + if parent_nodes: + child_nodes = set(mygraph.child_nodes(current_node)) + selected_parent = None + # First, try to avoid a direct cycle. + for node in parent_nodes: + if not isinstance(node, (Blocker, Package)): + continue + if node not in traversed_nodes and \ + node not in child_nodes: + edge = (current_node, node) + if edge in shown_edges: + continue + selected_parent = node + break + if not selected_parent: + # A direct cycle is unavoidable. + for node in parent_nodes: + if not isinstance(node, (Blocker, Package)): + continue + if node not in traversed_nodes: + edge = (current_node, node) + if edge in shown_edges: + continue + selected_parent = node + break + if selected_parent: + shown_edges.add((current_node, selected_parent)) + traversed_nodes.add(selected_parent) + add_parents(selected_parent, False) + display_list.append((current_node, + len(tree_nodes), ordered)) + tree_nodes.append(current_node) + tree_nodes = [] + add_parents(x, True) + + return display_list + + def _prune_tree_display(self, display_list): + last_merge_depth = 0 + for i in range(len(display_list) - 1, -1, -1): + node, depth, ordered = display_list[i] + if not ordered and depth == 0 and i > 0 \ + and node == display_list[i-1][0] and \ + display_list[i-1][1] == 0: + # An ordered node got a consecutive duplicate + # when the tree was being filled in. + del display_list[i] + continue + if ordered and isinstance(node, Package) \ + and node.operation == 'merge': + last_merge_depth = depth + continue + if depth >= last_merge_depth or \ + i < len(display_list) - 1 and \ + depth >= display_list[i+1][1]: + del display_list[i] + def display_problems(self): """ Display problems with the dependency graph such as slot collisions. @@ -4802,6 +4871,8 @@ class depgraph(object): will raise a PackageNotFound exception if a package is not available. """ + self._load_vdb() + if not isinstance(resume_data, dict): return False @@ -5202,11 +5273,62 @@ def insert_category_into_atom(atom, category): ret = None return ret +def _spinner_start(spinner, myopts): + if spinner is None: + return + if "--quiet" not in myopts and \ + ("--pretend" in myopts or "--ask" in myopts or \ + "--tree" in myopts or "--verbose" in myopts): + action = "" + if "--fetchonly" in myopts or "--fetch-all-uri" in myopts: + action = "fetched" + elif "--buildpkgonly" in myopts: + action = "built" + else: + action = "merged" + if "--tree" in myopts and action != "fetched": # Tree doesn't work with fetching + if "--unordered-display" in myopts: + portage.writemsg_stdout("\n" + \ + darkgreen("These are the packages that " + \ + "would be %s:" % action) + "\n\n") + else: + portage.writemsg_stdout("\n" + \ + darkgreen("These are the packages that " + \ + "would be %s, in reverse order:" % action) + "\n\n") + else: + portage.writemsg_stdout("\n" + \ + darkgreen("These are the packages that " + \ + "would be %s, in order:" % action) + "\n\n") + + show_spinner = "--quiet" not in myopts and "--nodeps" not in myopts + if not show_spinner: + spinner.update = spinner.update_quiet + + if show_spinner: + portage.writemsg_stdout("Calculating dependencies ") + +def _spinner_stop(spinner): + if spinner is None or \ + spinner.update is spinner.update_quiet: + return + + portage.writemsg_stdout("\b\b... done!\n") + def backtrack_depgraph(settings, trees, myopts, myparams, myaction, myfiles, spinner): """ Raises PackageSetNotFound if myfiles contains a missing package set. """ + _spinner_start(spinner, myopts) + try: + return _backtrack_depgraph(settings, trees, myopts, myparams, + myaction, myfiles, spinner) + finally: + _spinner_stop(spinner) + +def _backtrack_depgraph(settings, trees, myopts, myparams, + myaction, myfiles, spinner): + backtrack_max = 30 runtime_pkg_mask = None allow_backtracking = True @@ -5239,6 +5361,17 @@ def backtrack_depgraph(settings, trees, myopts, myparams, return (success, mydepgraph, favorites) def resume_depgraph(settings, trees, mtimedb, myopts, myparams, spinner): + """ + Raises PackageSetNotFound if myfiles contains a missing package set. + """ + _spinner_start(spinner, myopts) + try: + return _resume_depgraph(settings, trees, mtimedb, myopts, + myparams, spinner) + finally: + _spinner_stop(spinner) + +def _resume_depgraph(settings, trees, mtimedb, myopts, myparams, spinner): """ Construct a depgraph for the given resume list. This will raise PackageNotFound or depgraph.UnsatisfiedResumeDep when necessary. diff --git a/pym/_emerge/help.py b/pym/_emerge/help.py index 346d77712..9d036f05f 100644 --- a/pym/_emerge/help.py +++ b/pym/_emerge/help.py @@ -255,16 +255,29 @@ def help(myopts, havecolor=1): print(" be displayed as USE=\"-bar dar -foo\"") print() print(" "+green("--ask")+" ("+green("-a")+" short option)") - print(" before performing the merge, display what ebuilds and tbz2s will") - print(" be installed, in the same format as when using --pretend; then") - print(" ask whether to continue with the merge or abort. Using --ask is") - print(" more efficient than using --pretend and then executing the same") - print(" command without --pretend, as dependencies will only need to be") - print(" calculated once. WARNING: If the \"Enter\" key is pressed at the") - print(" prompt (with no other input), it is interpreted as acceptance of") - print(" the first choice. Note that the input buffer is not cleared prior") - print(" to the prompt, so an accidental press of the \"Enter\" key at any") - print(" time prior to the prompt will be interpreted as a choice!") + desc = "Before performing the action, display what will take place (server info for " + \ + "--sync, --pretend output for merge, and so forth), then ask " + \ + "whether to proceed with the action or abort. Using --ask is more " + \ + "efficient than using --pretend and then executing the same command " + \ + "without --pretend, as dependencies will only need to be calculated once. " + \ + "WARNING: If the \"Enter\" key is pressed at the prompt (with no other input), " + \ + "it is interpreted as acceptance of the first choice. Note that the input " + \ + "buffer is not cleared prior to the prompt, so an accidental press of the " + \ + "\"Enter\" key at any time prior to the prompt will be interpreted as a choice! " + \ + "Use the --ask-enter-invalid option if you want a single \"Enter\" key " + \ + "press to be interpreted as invalid input." + for line in wrap(desc, desc_width): + print(desc_indent + line) + print() + print(" " + green("--ask-enter-invalid")) + desc = "When used together with the --ask option, " + \ + "interpret a single \"Enter\" key press as " + \ + "invalid input. This helps prevent accidental " + \ + "acceptance of the first choice. This option is " + \ + "intended to be set in the make.conf(5) " + \ + "EMERGE_DEFAULT_OPTS variable." + for line in wrap(desc, desc_width): + print(desc_indent + line) print() print(" " + green("--binpkg-respect-use") + \ " < " + turquoise("y") + " | " + turquoise("n") + " >") @@ -538,6 +551,15 @@ def help(myopts, havecolor=1): print(" a package's dependencies follow the package. Only really useful") print(" in combination with --emptytree, --update or --deep.") print() + print(" " + green("--unordered-display")) + desc = "By default the displayed merge list is sorted using the " + \ + "order in which the packages will be merged. When " + \ + "--tree is used together with this option, this " + \ + "constraint is removed, hopefully leading to a more " + \ + "readable dependency tree." + for line in wrap(desc, desc_width): + print(desc_indent + line) + print() print(" " + green("--use-ebuild-visibility") + "[=%s]" % turquoise("n")) desc = "Use unbuilt ebuild metadata for visibility " + \ "checks on built packages." diff --git a/pym/_emerge/main.py b/pym/_emerge/main.py index 3a693f145..d69eb61a3 100644 --- a/pym/_emerge/main.py +++ b/pym/_emerge/main.py @@ -50,6 +50,7 @@ if sys.hexversion >= 0x3000000: options=[ "--ask", "--alphabetical", +"--ask-enter-invalid", "--buildpkg", "--buildpkgonly", "--changed-use", "--changelog", "--columns", @@ -67,6 +68,7 @@ options=[ "--searchdesc", "--skipfirst", "--tree", +"--unordered-display", "--update", "--verbose", ] diff --git a/pym/_emerge/unmerge.py b/pym/_emerge/unmerge.py index 967d72478..aefc94b36 100644 --- a/pym/_emerge/unmerge.py +++ b/pym/_emerge/unmerge.py @@ -27,6 +27,7 @@ def unmerge(root_config, myopts, unmerge_action, if clean_world: clean_world = myopts.get('--deselect') != 'n' quiet = "--quiet" in myopts + enter_invalid = '--ask-enter-invalid' in myopts settings = root_config.settings sets = root_config.sets vartree = root_config.trees["vartree"] @@ -483,7 +484,8 @@ def unmerge(root_config, myopts, unmerge_action, #we're done... return return 0 if "--ask" in myopts: - if userquery("Would you like to unmerge these packages?")=="No": + if userquery("Would you like to unmerge these packages?", + enter_invalid) == "No": # enter pretend mode for correct formatting of results myopts["--pretend"] = True print() diff --git a/pym/_emerge/userquery.py b/pym/_emerge/userquery.py index 5da236007..f177c9e87 100644 --- a/pym/_emerge/userquery.py +++ b/pym/_emerge/userquery.py @@ -8,11 +8,12 @@ import sys from portage.output import bold, create_color_func -def userquery(prompt, responses=None, colours=None): +def userquery(prompt, enter_invalid, responses=None, colours=None): """Displays a prompt and a set of responses, then waits for a response which is checked against the responses and the first to match is - returned. An empty response will match the first value in responses. The - input buffer is *not* cleared prior to the prompt! + returned. An empty response will match the first value in responses, + unless enter_invalid is True. The input buffer is *not* cleared prior + to the prompt! prompt: a String. responses: a List of Strings. @@ -42,10 +43,12 @@ def userquery(prompt, responses=None, colours=None): response=input("["+"/".join([colours[i](responses[i]) for i in range(len(responses))])+"] ") else: response=raw_input("["+"/".join([colours[i](responses[i]) for i in range(len(responses))])+"] ") - for key in responses: - # An empty response will match the first value in responses. - if response.upper()==key[:len(response)].upper(): - return key + if response or not enter_invalid: + for key in responses: + # An empty response will match the + # first value in responses. + if response.upper()==key[:len(response)].upper(): + return key print("Sorry, response '%s' not understood." % response, end=' ') except (EOFError, KeyboardInterrupt): print("Interrupted.")