Merged from trunk -r14701:14710
authorFabian Groffen <grobian@gentoo.org>
Sat, 24 Oct 2009 11:22:31 +0000 (11:22 -0000)
committerFabian Groffen <grobian@gentoo.org>
Sat, 24 Oct 2009 11:22:31 +0000 (11:22 -0000)
   | 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              |
   |         | <loki_val@g.o> 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

bin/ebuild-helpers/prepstrip
bin/repoman
man/emerge.1
pym/_emerge/Scheduler.py
pym/_emerge/actions.py
pym/_emerge/depgraph.py
pym/_emerge/help.py
pym/_emerge/main.py
pym/_emerge/unmerge.py
pym/_emerge/userquery.py

index 5c0a9dc5fa473db0fd805181d3b344e27bb59ef4..84095f43467928dadbf9b490e7afcd0e782e35aa 100755 (executable)
@@ -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 '/<built-in>$' "${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
index 68a8f799ef6659b3b3fd4fbed3d8b98c92f1ae4a..5169da449ef37382c1c7ed3c4cf4de7c71eb9ca7 100755 (executable)
@@ -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
 
index a04a06b2829feea46fd8de5bd7705a0285992413..346d444c2c24bd351dca240778d6404b93bf3a8c 100644 (file)
@@ -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.
index c95003dd76579bea42a2a2fa7e9154984b54ceae..f969f7105f595f3282869e2b369d10b60ae99a2d 100644 (file)
@@ -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()
index ec661ad86d1db51805aaed13526cadc6e676ca61..26085fead769c82d18e2aa8e18bf3b72d501a8d4 100644 (file)
@@ -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()
index fcda3ec65249737fc72b1d1b65ba56e39e82482c..cef1d47e88ec1b44894a2d051df91a0a4de87422 100644 (file)
@@ -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.
index 346d77712a387ce19921aea36dad000ab4eb9a98..9d036f05f8d40642dbbe0d9d775e40803cca0901 100644 (file)
@@ -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."
index 3a693f145ca47243aac12974c3083d385c26d28b..d69eb61a399ff4c866406f85faa6ec44ca83576a 100644 (file)
@@ -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",
 ]
index 967d72478b40f809894748f31fffcf3d802b504e..aefc94b36d041c5e39bd619a64b50cca5d8e3309 100644 (file)
@@ -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()
index 5da2360070bf8d8f9ddb8581a03c08b44d69b250..f177c9e8794cc604e933ba5c56dc100e7c3c2847 100644 (file)
@@ -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.")