Merged from trunk -r15460:15465
authorFabian Groffen <grobian@gentoo.org>
Sat, 27 Feb 2010 19:32:48 +0000 (19:32 -0000)
committerFabian Groffen <grobian@gentoo.org>
Sat, 27 Feb 2010 19:32:48 +0000 (19:32 -0000)
   | 15461   | Move dep.py to dep/__init__.py, for splitting into smaller   |
   | zmedico | files.                                                       |

   | 15462   | Move portage.dep_check and related functions to              |
   | zmedico | portage.dep.dep_check.                                       |

   | 15463   | Split getmaskingstatus and getmaskingreason info             |
   | zmedico | portage.package.ebuild submodules.                           |

   | 15464   | Move portage.movefile to portage.util.movefile.              |
   | zmedico |                                                              |

   | 15465   | Avoid name collision with dep_expand submodule so epydoc     |
   | zmedico | won't crash.                                                 |

svn path=/main/branches/prefix/; revision=15483

pym/portage/__init__.py
pym/portage/dbapi/__init__.py
pym/portage/dbapi/vartree.py
pym/portage/dep/__init__.py [moved from pym/portage/dep.py with 100% similarity]
pym/portage/dep/dep_check.py [new file with mode: 0644]
pym/portage/package/ebuild/getmaskingreason.py [new file with mode: 0644]
pym/portage/package/ebuild/getmaskingstatus.py [new file with mode: 0644]
pym/portage/util/movefile.py [new file with mode: 0644]

index 5edbf843fdd3a358e78ba430dd7f0768b0c22d2e..ad372fd79ef0ef7b36c2342be27f3524d9fbebbc 100644 (file)
@@ -92,6 +92,7 @@ try:
                'portage.dep:best_match_to_list,dep_getcpv,dep_getkey,' + \
                        'flatten,get_operator,isjustname,isspecific,isvalidatom,' + \
                        'match_from_list,match_to_list',
+               'portage.dep.dep_check:dep_check,dep_eval,dep_wordreduce,dep_zapdeps',
                'portage.eclass_cache',
                'portage.env.loaders',
                'portage.exception',
@@ -109,6 +110,8 @@ try:
                'portage.package.ebuild.digestcheck:digestcheck',
                'portage.package.ebuild.digestgen:digestgen',
                'portage.package.ebuild.fetch:fetch',
+               'portage.package.ebuild.getmaskingreason:getmaskingreason',
+               'portage.package.ebuild.getmaskingstatus:getmaskingstatus',
                'portage.package.ebuild.prepare_build_dirs:prepare_build_dirs',
                'portage.process',
                'portage.process:atexit_register,run_exitfuncs',
@@ -127,6 +130,7 @@ try:
                'portage.util.env_update:env_update',
                'portage.util.ExtractKernelVersion:ExtractKernelVersion',
                'portage.util.listdir:cacheddir,listdir',
+               'portage.util.movefile:movefile',
                'portage.versions',
                'portage.versions:best,catpkgsplit,catsplit,cpv_getkey,' + \
                        'cpv_getkey@getCPFromCPV,endversion_keys,' + \
@@ -634,223 +638,6 @@ def _movefile(src, dest, **kwargs):
                raise portage.exception.PortageException(
                        "mv '%s' '%s'" % (src, dest))
 
-def movefile(src, dest, newmtime=None, sstat=None, mysettings=None,
-               hardlink_candidates=None, encoding=_encodings['fs']):
-       """moves a file from src to dest, preserving all permissions and attributes; mtime will
-       be preserved even when moving across filesystems.  Returns true on success and false on
-       failure.  Move is atomic."""
-       #print "movefile("+str(src)+","+str(dest)+","+str(newmtime)+","+str(sstat)+")"
-
-       if mysettings is None:
-               global settings
-               mysettings = settings
-
-       selinux_enabled = mysettings.selinux_enabled()
-       if selinux_enabled:
-               selinux = _unicode_module_wrapper(_selinux, encoding=encoding)
-
-       lchown = _unicode_func_wrapper(data.lchown, encoding=encoding)
-       os = _unicode_module_wrapper(_os,
-               encoding=encoding, overrides=_os_overrides)
-       shutil = _unicode_module_wrapper(_shutil, encoding=encoding)
-
-       try:
-               if not sstat:
-                       sstat=os.lstat(src)
-
-       except SystemExit as e:
-               raise
-       except Exception as e:
-               print(_("!!! Stating source file failed... movefile()"))
-               print("!!!",e)
-               return None
-
-       destexists=1
-       try:
-               dstat=os.lstat(dest)
-       except (OSError, IOError):
-               dstat=os.lstat(os.path.dirname(dest))
-               destexists=0
-
-       if bsd_chflags:
-               if destexists and dstat.st_flags != 0:
-                       bsd_chflags.lchflags(dest, 0)
-               # Use normal stat/chflags for the parent since we want to
-               # follow any symlinks to the real parent directory.
-               pflags = os.stat(os.path.dirname(dest)).st_flags
-               if pflags != 0:
-                       bsd_chflags.chflags(os.path.dirname(dest), 0)
-
-       if destexists:
-               if stat.S_ISLNK(dstat[stat.ST_MODE]):
-                       try:
-                               os.unlink(dest)
-                               destexists=0
-                       except SystemExit as e:
-                               raise
-                       except Exception as e:
-                               pass
-
-       if stat.S_ISLNK(sstat[stat.ST_MODE]):
-               try:
-                       target=os.readlink(src)
-                       if mysettings and mysettings["D"]:
-                               if target.find(mysettings["D"])==0:
-                                       target=target[len(mysettings["D"]):]
-                       if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
-                               os.unlink(dest)
-                       if selinux_enabled:
-                               selinux.symlink(target, dest, src)
-                       else:
-                               os.symlink(target,dest)
-                       lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
-                       # utime() only works on the target of a symlink, so it's not
-                       # possible to perserve mtime on symlinks.
-                       return os.lstat(dest)[stat.ST_MTIME]
-               except SystemExit as e:
-                       raise
-               except Exception as e:
-                       print(_("!!! failed to properly create symlink:"))
-                       print("!!!",dest,"->",target)
-                       print("!!!",e)
-                       return None
-
-       hardlinked = False
-       # Since identical files might be merged to multiple filesystems,
-       # so os.link() calls might fail for some paths, so try them all.
-       # For atomic replacement, first create the link as a temp file
-       # and them use os.rename() to replace the destination.
-       if hardlink_candidates:
-               head, tail = os.path.split(dest)
-               hardlink_tmp = os.path.join(head, ".%s._portage_merge_.%s" % \
-                       (tail, os.getpid()))
-               try:
-                       os.unlink(hardlink_tmp)
-               except OSError as e:
-                       if e.errno != errno.ENOENT:
-                               writemsg(_("!!! Failed to remove hardlink temp file: %s\n") % \
-                                       (hardlink_tmp,), noiselevel=-1)
-                               writemsg("!!! %s\n" % (e,), noiselevel=-1)
-                               return None
-                       del e
-               for hardlink_src in hardlink_candidates:
-                       try:
-                               os.link(hardlink_src, hardlink_tmp)
-                       except OSError:
-                               continue
-                       else:
-                               try:
-                                       os.rename(hardlink_tmp, dest)
-                               except OSError as e:
-                                       writemsg(_("!!! Failed to rename %s to %s\n") % \
-                                               (hardlink_tmp, dest), noiselevel=-1)
-                                       writemsg("!!! %s\n" % (e,), noiselevel=-1)
-                                       return None
-                               hardlinked = True
-                               break
-
-       renamefailed=1
-       if hardlinked:
-               renamefailed = False
-       if not hardlinked and (selinux_enabled or sstat.st_dev == dstat.st_dev):
-               try:
-                       if selinux_enabled:
-                               ret = selinux.rename(src, dest)
-                       else:
-                               ret=os.rename(src,dest)
-                       renamefailed=0
-               except OSError as e:
-                       if e.errno != errno.EXDEV:
-                               # Some random error.
-                               print(_("!!! Failed to move %(src)s to %(dest)s") % {"src": src, "dest": dest})
-                               print("!!!",e)
-                               return None
-                       # Invalid cross-device-link 'bind' mounted or actually Cross-Device
-       if renamefailed:
-               didcopy=0
-               if stat.S_ISREG(sstat[stat.ST_MODE]):
-                       try: # For safety copy then move it over.
-                               if selinux_enabled:
-                                       selinux.copyfile(src, dest + "#new")
-                                       selinux.rename(dest + "#new", dest)
-                               else:
-                                       shutil.copyfile(src,dest+"#new")
-                                       os.rename(dest+"#new",dest)
-                               didcopy=1
-                       except SystemExit as e:
-                               raise
-                       except Exception as e:
-                               print(_('!!! copy %(src)s -> %(dest)s failed.') % {"src": src, "dest": dest})
-                               print("!!!",e)
-                               return None
-               else:
-                       #we don't yet handle special, so we need to fall back to /bin/mv
-                       a = process.spawn([MOVE_BINARY, '-f', src, dest], env=os.environ)
-                       if a != os.EX_OK:
-                               writemsg(_("!!! Failed to move special file:\n"), noiselevel=-1)
-                               writemsg(_("!!! '%(src)s' to '%(dest)s'\n") % \
-                                       {"src": _unicode_decode(src, encoding=encoding),
-                                       "dest": _unicode_decode(dest, encoding=encoding)}, noiselevel=-1)
-                               writemsg("!!! %s\n" % a, noiselevel=-1)
-                               return None # failure
-               try:
-                       if didcopy:
-                               if stat.S_ISLNK(sstat[stat.ST_MODE]):
-                                       lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
-                               else:
-                                       os.chown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
-                               os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
-                               os.unlink(src)
-               except SystemExit as e:
-                       raise
-               except Exception as e:
-                       print(_("!!! Failed to chown/chmod/unlink in movefile()"))
-                       print("!!!",dest)
-                       print("!!!",e)
-                       return None
-
-       # Always use stat_obj[stat.ST_MTIME] for the integral timestamp which
-       # is returned, since the stat_obj.st_mtime float attribute rounds *up*
-       # if the nanosecond part of the timestamp is 999999881 ns or greater.
-       try:
-               if hardlinked:
-                       newmtime = os.stat(dest)[stat.ST_MTIME]
-               else:
-                       # Note: It is not possible to preserve nanosecond precision
-                       # (supported in POSIX.1-2008 via utimensat) with the IEEE 754
-                       # double precision float which only has a 53 bit significand.
-                       if newmtime is not None:
-                               os.utime(dest, (newmtime, newmtime))
-                       else:
-                               newmtime = sstat[stat.ST_MTIME]
-                               if renamefailed:
-                                       # If rename succeeded then timestamps are automatically
-                                       # preserved with complete precision because the source
-                                       # and destination inode are the same. Otherwise, round
-                                       # down to the nearest whole second since python's float
-                                       # st_mtime cannot be used to preserve the st_mtim.tv_nsec
-                                       # field with complete precision. Note that we have to use
-                                       # stat_obj[stat.ST_MTIME] here because the float
-                                       # stat_obj.st_mtime rounds *up* sometimes.
-                                       os.utime(dest, (newmtime, newmtime))
-       except OSError:
-               # The utime can fail here with EPERM even though the move succeeded.
-               # Instead of failing, use stat to return the mtime if possible.
-               try:
-                       newmtime = os.stat(dest)[stat.ST_MTIME]
-               except OSError as e:
-                       writemsg(_("!!! Failed to stat in movefile()\n"), noiselevel=-1)
-                       writemsg("!!! %s\n" % dest, noiselevel=-1)
-                       writemsg("!!! %s\n" % str(e), noiselevel=-1)
-                       return None
-
-       if bsd_chflags:
-               # Restore the flags we saved before moving
-               if pflags:
-                       bsd_chflags.chflags(os.path.dirname(dest), pflags)
-
-       return newmtime
-
 def merge(mycat, mypkg, pkgloc, infloc, myroot, mysettings, myebuild=None,
        mytree=None, mydbapi=None, vartree=None, prev_mtimes=None, blockers=None,
        scheduler=None):
@@ -911,835 +698,6 @@ def dep_virtual(mysplit, mysettings):
                                newsplit.append(x)
        return newsplit
 
-def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/",
-       trees=None, use_mask=None, use_force=None, **kwargs):
-       """
-       In order to solve bug #141118, recursively expand new-style virtuals so
-       as to collapse one or more levels of indirection, generating an expanded
-       search space. In dep_zapdeps, new-style virtuals will be assigned
-       zero cost regardless of whether or not they are currently installed. Virtual
-       blockers are supported but only when the virtual expands to a single
-       atom because it wouldn't necessarily make sense to block all the components
-       of a compound virtual.  When more than one new-style virtual is matched,
-       the matches are sorted from highest to lowest versions and the atom is
-       expanded to || ( highest match ... lowest match )."""
-       newsplit = []
-       mytrees = trees[myroot]
-       portdb = mytrees["porttree"].dbapi
-       atom_graph = mytrees.get("atom_graph")
-       parent = mytrees.get("parent")
-       virt_parent = mytrees.get("virt_parent")
-       graph_parent = None
-       eapi = None
-       if parent is not None:
-               if virt_parent is not None:
-                       graph_parent = virt_parent
-                       eapi = virt_parent[0].metadata['EAPI']
-               else:
-                       graph_parent = parent
-                       eapi = parent.metadata["EAPI"]
-       repoman = not mysettings.local_config
-       if kwargs["use_binaries"]:
-               portdb = trees[myroot]["bintree"].dbapi
-       myvirtuals = mysettings.getvirtuals()
-       pprovideddict = mysettings.pprovideddict
-       myuse = kwargs["myuse"]
-       for x in mysplit:
-               if x == "||":
-                       newsplit.append(x)
-                       continue
-               elif isinstance(x, list):
-                       newsplit.append(_expand_new_virtuals(x, edebug, mydbapi,
-                               mysettings, myroot=myroot, trees=trees, use_mask=use_mask,
-                               use_force=use_force, **kwargs))
-                       continue
-
-               if not isinstance(x, portage.dep.Atom):
-                       try:
-                               x = portage.dep.Atom(x)
-                       except portage.exception.InvalidAtom:
-                               if portage.dep._dep_check_strict:
-                                       raise portage.exception.ParseError(
-                                               _("invalid atom: '%s'") % x)
-                               else:
-                                       # Only real Atom instances are allowed past this point.
-                                       continue
-                       else:
-                               if x.blocker and x.blocker.overlap.forbid and \
-                                       eapi in ("0", "1") and portage.dep._dep_check_strict:
-                                       raise portage.exception.ParseError(
-                                               _("invalid atom: '%s'") % (x,))
-                               if x.use and eapi in ("0", "1") and \
-                                       portage.dep._dep_check_strict:
-                                       raise portage.exception.ParseError(
-                                               _("invalid atom: '%s'") % (x,))
-
-               if repoman and x.use and x.use.conditional:
-                       evaluated_atom = portage.dep.remove_slot(x)
-                       if x.slot:
-                               evaluated_atom += ":%s" % x.slot
-                       evaluated_atom += str(x.use._eval_qa_conditionals(
-                               use_mask, use_force))
-                       x = portage.dep.Atom(evaluated_atom)
-
-               if not repoman and \
-                       myuse is not None and isinstance(x, portage.dep.Atom) and x.use:
-                       if x.use.conditional:
-                               x = x.evaluate_conditionals(myuse)
-
-               mykey = x.cp
-               if not mykey.startswith("virtual/"):
-                       newsplit.append(x)
-                       if atom_graph is not None:
-                               atom_graph.add(x, graph_parent)
-                       continue
-               mychoices = myvirtuals.get(mykey, [])
-               if x.blocker:
-                       # Virtual blockers are no longer expanded here since
-                       # the un-expanded virtual atom is more useful for
-                       # maintaining a cache of blocker atoms.
-                       newsplit.append(x)
-                       if atom_graph is not None:
-                               atom_graph.add(x, graph_parent)
-                       continue
-
-               if repoman or not hasattr(portdb, 'match_pkgs'):
-                       if portdb.cp_list(x.cp):
-                               newsplit.append(x)
-                       else:
-                               # TODO: Add PROVIDE check for repoman.
-                               a = []
-                               for y in mychoices:
-                                       a.append(dep.Atom(x.replace(x.cp, y.cp, 1)))
-                               if not a:
-                                       newsplit.append(x)
-                               elif len(a) == 1:
-                                       newsplit.append(a[0])
-                               else:
-                                       newsplit.append(['||'] + a)
-                       continue
-
-               pkgs = []
-               # Ignore USE deps here, since otherwise we might not
-               # get any matches. Choices with correct USE settings
-               # will be preferred in dep_zapdeps().
-               matches = portdb.match_pkgs(x.without_use)
-               # Use descending order to prefer higher versions.
-               matches.reverse()
-               for pkg in matches:
-                       # only use new-style matches
-                       if pkg.cp.startswith("virtual/"):
-                               pkgs.append(pkg)
-               if not (pkgs or mychoices):
-                       # This one couldn't be expanded as a new-style virtual.  Old-style
-                       # virtuals have already been expanded by dep_virtual, so this one
-                       # is unavailable and dep_zapdeps will identify it as such.  The
-                       # atom is not eliminated here since it may still represent a
-                       # dependency that needs to be satisfied.
-                       newsplit.append(x)
-                       if atom_graph is not None:
-                               atom_graph.add(x, graph_parent)
-                       continue
-
-               a = []
-               for pkg in pkgs:
-                       virt_atom = '=' + pkg.cpv
-                       if x.use:
-                               virt_atom += str(x.use)
-                       virt_atom = dep.Atom(virt_atom)
-                       # According to GLEP 37, RDEPEND is the only dependency
-                       # type that is valid for new-style virtuals. Repoman
-                       # should enforce this.
-                       depstring = pkg.metadata['RDEPEND']
-                       pkg_kwargs = kwargs.copy()
-                       pkg_kwargs["myuse"] = pkg.use.enabled
-                       if edebug:
-                               util.writemsg_level(_("Virtual Parent:      %s\n") \
-                                       % (pkg,), noiselevel=-1, level=logging.DEBUG)
-                               util.writemsg_level(_("Virtual Depstring:   %s\n") \
-                                       % (depstring,), noiselevel=-1, level=logging.DEBUG)
-
-                       # Set EAPI used for validation in dep_check() recursion.
-                       mytrees["virt_parent"] = (pkg, virt_atom)
-
-                       try:
-                               mycheck = dep_check(depstring, mydbapi, mysettings,
-                                       myroot=myroot, trees=trees, **pkg_kwargs)
-                       finally:
-                               # Restore previous EAPI after recursion.
-                               if virt_parent is not None:
-                                       mytrees["virt_parent"] = virt_parent
-                               else:
-                                       del mytrees["virt_parent"]
-
-                       if not mycheck[0]:
-                               raise portage.exception.ParseError(
-                                       "%s: %s '%s'" % (y[0], mycheck[1], depstring))
-
-                       # pull in the new-style virtual
-                       mycheck[1].append(virt_atom)
-                       a.append(mycheck[1])
-                       if atom_graph is not None:
-                               atom_graph.add(virt_atom, graph_parent)
-               # Plain old-style virtuals.  New-style virtuals are preferred.
-               if not pkgs:
-                               for y in mychoices:
-                                       new_atom = dep.Atom(x.replace(x.cp, y.cp, 1))
-                                       matches = portdb.match(new_atom)
-                                       # portdb is an instance of depgraph._dep_check_composite_db, so
-                                       # USE conditionals are already evaluated.
-                                       if matches and mykey in \
-                                               portdb.aux_get(matches[-1], ['PROVIDE'])[0].split():
-                                               a.append(new_atom)
-                                               if atom_graph is not None:
-                                                       atom_graph.add(new_atom, graph_parent)
-
-               if not a and mychoices:
-                       # Check for a virtual package.provided match.
-                       for y in mychoices:
-                               new_atom = dep.Atom(x.replace(x.cp, y.cp, 1))
-                               if match_from_list(new_atom,
-                                       pprovideddict.get(new_atom.cp, [])):
-                                       a.append(new_atom)
-                                       if atom_graph is not None:
-                                               atom_graph.add(new_atom, graph_parent)
-
-               if not a:
-                       newsplit.append(x)
-                       if atom_graph is not None:
-                               atom_graph.add(x, graph_parent)
-               elif len(a) == 1:
-                       newsplit.append(a[0])
-               else:
-                       newsplit.append(['||'] + a)
-
-       return newsplit
-
-def dep_eval(deplist):
-       if not deplist:
-               return 1
-       if deplist[0]=="||":
-               #or list; we just need one "1"
-               for x in deplist[1:]:
-                       if isinstance(x, list):
-                               if dep_eval(x)==1:
-                                       return 1
-                       elif x==1:
-                                       return 1
-               #XXX: unless there's no available atoms in the list
-               #in which case we need to assume that everything is
-               #okay as some ebuilds are relying on an old bug.
-               if len(deplist) == 1:
-                       return 1
-               return 0
-       else:
-               for x in deplist:
-                       if isinstance(x, list):
-                               if dep_eval(x)==0:
-                                       return 0
-                       elif x==0 or x==2:
-                               return 0
-               return 1
-
-def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None):
-       """Takes an unreduced and reduced deplist and removes satisfied dependencies.
-       Returned deplist contains steps that must be taken to satisfy dependencies."""
-       if trees is None:
-               global db
-               trees = db
-       writemsg("ZapDeps -- %s\n" % (use_binaries), 2)
-       if not reduced or unreduced == ["||"] or dep_eval(reduced):
-               return []
-
-       if unreduced[0] != "||":
-               unresolved = []
-               for x, satisfied in zip(unreduced, reduced):
-                       if isinstance(x, list):
-                               unresolved += dep_zapdeps(x, satisfied, myroot,
-                                       use_binaries=use_binaries, trees=trees)
-                       elif not satisfied:
-                               unresolved.append(x)
-               return unresolved
-
-       # We're at a ( || atom ... ) type level and need to make a choice
-       deps = unreduced[1:]
-       satisfieds = reduced[1:]
-
-       # Our preference order is for an the first item that:
-       # a) contains all unmasked packages with the same key as installed packages
-       # b) contains all unmasked packages
-       # c) contains masked installed packages
-       # d) is the first item
-
-       preferred_installed = []
-       preferred_in_graph = []
-       preferred_any_slot = []
-       preferred_non_installed = []
-       unsat_use_in_graph = []
-       unsat_use_installed = []
-       unsat_use_non_installed = []
-       other = []
-
-       # unsat_use_* must come after preferred_non_installed
-       # for correct ordering in cases like || ( foo[a] foo[b] ).
-       choice_bins = (
-               preferred_in_graph,
-               preferred_installed,
-               preferred_any_slot,
-               preferred_non_installed,
-               unsat_use_in_graph,
-               unsat_use_installed,
-               unsat_use_non_installed,
-               other,
-       )
-
-       # Alias the trees we'll be checking availability against
-       parent   = trees[myroot].get("parent")
-       priority = trees[myroot].get("priority")
-       graph_db = trees[myroot].get("graph_db")
-       vardb = None
-       if "vartree" in trees[myroot]:
-               vardb = trees[myroot]["vartree"].dbapi
-       if use_binaries:
-               mydbapi = trees[myroot]["bintree"].dbapi
-       else:
-               mydbapi = trees[myroot]["porttree"].dbapi
-
-       # Sort the deps into installed, not installed but already 
-       # in the graph and other, not installed and not in the graph
-       # and other, with values of [[required_atom], availablility]
-       for x, satisfied in zip(deps, satisfieds):
-               if isinstance(x, list):
-                       atoms = dep_zapdeps(x, satisfied, myroot,
-                               use_binaries=use_binaries, trees=trees)
-               else:
-                       atoms = [x]
-               if vardb is None:
-                       # When called by repoman, we can simply return the first choice
-                       # because dep_eval() handles preference selection.
-                       return atoms
-
-               all_available = True
-               all_use_satisfied = True
-               slot_map = {}
-               cp_map = {}
-               for atom in atoms:
-                       if atom.blocker:
-                               continue
-                       # Ignore USE dependencies here since we don't want USE
-                       # settings to adversely affect || preference evaluation.
-                       avail_pkg = mydbapi.match(atom.without_use)
-                       if avail_pkg:
-                               avail_pkg = avail_pkg[-1] # highest (ascending order)
-                               avail_slot = dep.Atom("%s:%s" % (atom.cp,
-                                       mydbapi.aux_get(avail_pkg, ["SLOT"])[0]))
-                       if not avail_pkg:
-                               all_available = False
-                               all_use_satisfied = False
-                               break
-
-                       if atom.use:
-                               avail_pkg_use = mydbapi.match(atom)
-                               if not avail_pkg_use:
-                                       all_use_satisfied = False
-                               else:
-                                       # highest (ascending order)
-                                       avail_pkg_use = avail_pkg_use[-1]
-                                       if avail_pkg_use != avail_pkg:
-                                               avail_pkg = avail_pkg_use
-                                               avail_slot = dep.Atom("%s:%s" % (atom.cp,
-                                                       mydbapi.aux_get(avail_pkg, ["SLOT"])[0]))
-
-                       slot_map[avail_slot] = avail_pkg
-                       pkg_cp = cpv_getkey(avail_pkg)
-                       highest_cpv = cp_map.get(pkg_cp)
-                       if highest_cpv is None or \
-                               pkgcmp(catpkgsplit(avail_pkg)[1:],
-                               catpkgsplit(highest_cpv)[1:]) > 0:
-                               cp_map[pkg_cp] = avail_pkg
-
-               this_choice = (atoms, slot_map, cp_map, all_available)
-               if all_available:
-                       # The "all installed" criterion is not version or slot specific.
-                       # If any version of a package is already in the graph then we
-                       # assume that it is preferred over other possible packages choices.
-                       all_installed = True
-                       for atom in set(dep.Atom(atom.cp) for atom in atoms \
-                               if not atom.blocker):
-                               # New-style virtuals have zero cost to install.
-                               if not vardb.match(atom) and not atom.startswith("virtual/"):
-                                       all_installed = False
-                                       break
-                       all_installed_slots = False
-                       if all_installed:
-                               all_installed_slots = True
-                               for slot_atom in slot_map:
-                                       # New-style virtuals have zero cost to install.
-                                       if not vardb.match(slot_atom) and \
-                                               not slot_atom.startswith("virtual/"):
-                                               all_installed_slots = False
-                                               break
-                       if graph_db is None:
-                               if all_use_satisfied:
-                                       if all_installed:
-                                               if all_installed_slots:
-                                                       preferred_installed.append(this_choice)
-                                               else:
-                                                       preferred_any_slot.append(this_choice)
-                                       else:
-                                               preferred_non_installed.append(this_choice)
-                               else:
-                                       if all_installed_slots:
-                                               unsat_use_installed.append(this_choice)
-                                       else:
-                                               unsat_use_non_installed.append(this_choice)
-                       else:
-                               all_in_graph = True
-                               for slot_atom in slot_map:
-                                       # New-style virtuals have zero cost to install.
-                                       if not graph_db.match(slot_atom) and \
-                                               not slot_atom.startswith("virtual/"):
-                                               all_in_graph = False
-                                               break
-                               circular_atom = None
-                               if all_in_graph:
-                                       if parent is None or priority is None:
-                                               pass
-                                       elif priority.buildtime:
-                                               # Check if the atom would result in a direct circular
-                                               # dependency and try to avoid that if it seems likely
-                                               # to be unresolvable. This is only relevant for
-                                               # buildtime deps that aren't already satisfied by an
-                                               # installed package.
-                                               cpv_slot_list = [parent]
-                                               for atom in atoms:
-                                                       if atom.blocker:
-                                                               continue
-                                                       if vardb.match(atom):
-                                                               # If the atom is satisfied by an installed
-                                                               # version then it's not a circular dep.
-                                                               continue
-                                                       if atom.cp != parent.cp:
-                                                               continue
-                                                       if match_from_list(atom, cpv_slot_list):
-                                                               circular_atom = atom
-                                                               break
-                               if circular_atom is not None:
-                                       other.append(this_choice)
-                               else:
-                                       if all_use_satisfied:
-                                               if all_in_graph:
-                                                       preferred_in_graph.append(this_choice)
-                                               elif all_installed:
-                                                       if all_installed_slots:
-                                                               preferred_installed.append(this_choice)
-                                                       else:
-                                                               preferred_any_slot.append(this_choice)
-                                               else:
-                                                       preferred_non_installed.append(this_choice)
-                                       else:
-                                               if all_in_graph:
-                                                       unsat_use_in_graph.append(this_choice)
-                                               elif all_installed_slots:
-                                                       unsat_use_installed.append(this_choice)
-                                               else:
-                                                       unsat_use_non_installed.append(this_choice)
-               else:
-                       other.append(this_choice)
-
-       # Prefer choices which contain upgrades to higher slots. This helps
-       # for deps such as || ( foo:1 foo:2 ), where we want to prefer the
-       # atom which matches the higher version rather than the atom furthest
-       # to the left. Sorting is done separately for each of choice_bins, so
-       # as not to interfere with the ordering of the bins. Because of the
-       # bin separation, the main function of this code is to allow
-       # --depclean to remove old slots (rather than to pull in new slots).
-       for choices in choice_bins:
-               if len(choices) < 2:
-                       continue
-               for choice_1 in choices[1:]:
-                       atoms_1, slot_map_1, cp_map_1, all_available_1 = choice_1
-                       cps = set(cp_map_1)
-                       for choice_2 in choices:
-                               if choice_1 is choice_2:
-                                       # choice_1 will not be promoted, so move on
-                                       break
-                               atoms_2, slot_map_2, cp_map_2, all_available_2 = choice_2
-                               intersecting_cps = cps.intersection(cp_map_2)
-                               if not intersecting_cps:
-                                       continue
-                               has_upgrade = False
-                               has_downgrade = False
-                               for cp in intersecting_cps:
-                                       version_1 = cp_map_1[cp]
-                                       version_2 = cp_map_2[cp]
-                                       difference = pkgcmp(catpkgsplit(version_1)[1:],
-                                               catpkgsplit(version_2)[1:])
-                                       if difference != 0:
-                                               if difference > 0:
-                                                       has_upgrade = True
-                                               else:
-                                                       has_downgrade = True
-                                                       break
-                               if has_upgrade and not has_downgrade:
-                                       # promote choice_1 in front of choice_2
-                                       choices.remove(choice_1)
-                                       index_2 = choices.index(choice_2)
-                                       choices.insert(index_2, choice_1)
-                                       break
-
-       for allow_masked in (False, True):
-               for choices in choice_bins:
-                       for atoms, slot_map, cp_map, all_available in choices:
-                               if all_available or allow_masked:
-                                       return atoms
-
-       assert(False) # This point should not be reachable
-
-def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None,
-       use_cache=1, use_binaries=0, myroot="/", trees=None):
-       """Takes a depend string and parses the condition."""
-       edebug = mysettings.get("PORTAGE_DEBUG", None) == "1"
-       #check_config_instance(mysettings)
-       if trees is None:
-               trees = globals()["db"]
-       if use=="yes":
-               if myuse is None:
-                       #default behavior
-                       myusesplit = mysettings["PORTAGE_USE"].split()
-               else:
-                       myusesplit = myuse
-                       # We've been given useflags to use.
-                       #print "USE FLAGS PASSED IN."
-                       #print myuse
-                       #if "bindist" in myusesplit:
-                       #       print "BINDIST is set!"
-                       #else:
-                       #       print "BINDIST NOT set."
-       else:
-               #we are being run by autouse(), don't consult USE vars yet.
-               # WE ALSO CANNOT USE SETTINGS
-               myusesplit=[]
-
-       #convert parenthesis to sublists
-       try:
-               mysplit = portage.dep.paren_reduce(depstring)
-       except portage.exception.InvalidDependString as e:
-               return [0, str(e)]
-
-       mymasks = set()
-       useforce = set()
-       useforce.add(mysettings["ARCH"])
-       if use == "all":
-               # This masking/forcing is only for repoman.  In other cases, relevant
-               # masking/forcing should have already been applied via
-               # config.regenerate().  Also, binary or installed packages may have
-               # been built with flags that are now masked, and it would be
-               # inconsistent to mask them now.  Additionally, myuse may consist of
-               # flags from a parent package that is being merged to a $ROOT that is
-               # different from the one that mysettings represents.
-               mymasks.update(mysettings.usemask)
-               mymasks.update(mysettings.archlist())
-               mymasks.discard(mysettings["ARCH"])
-               useforce.update(mysettings.useforce)
-               useforce.difference_update(mymasks)
-       try:
-               mysplit = portage.dep.use_reduce(mysplit, uselist=myusesplit,
-                       masklist=mymasks, matchall=(use=="all"), excludeall=useforce)
-       except portage.exception.InvalidDependString as e:
-               return [0, str(e)]
-
-       # Do the || conversions
-       mysplit=portage.dep.dep_opconvert(mysplit)
-
-       if mysplit == []:
-               #dependencies were reduced to nothing
-               return [1,[]]
-
-       # Recursively expand new-style virtuals so as to
-       # collapse one or more levels of indirection.
-       try:
-               mysplit = _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings,
-                       use=use, mode=mode, myuse=myuse,
-                       use_force=useforce, use_mask=mymasks, use_cache=use_cache,
-                       use_binaries=use_binaries, myroot=myroot, trees=trees)
-       except portage.exception.ParseError as e:
-               return [0, str(e)]
-
-       mysplit2=mysplit[:]
-       mysplit2=dep_wordreduce(mysplit2,mysettings,mydbapi,mode,use_cache=use_cache)
-       if mysplit2 is None:
-               return [0, _("Invalid token")]
-
-       writemsg("\n\n\n", 1)
-       writemsg("mysplit:  %s\n" % (mysplit), 1)
-       writemsg("mysplit2: %s\n" % (mysplit2), 1)
-
-       try:
-               selected_atoms = dep_zapdeps(mysplit, mysplit2, myroot,
-                       use_binaries=use_binaries, trees=trees)
-       except portage.exception.InvalidAtom as e:
-               if portage.dep._dep_check_strict:
-                       raise # This shouldn't happen.
-               # dbapi.match() failed due to an invalid atom in
-               # the dependencies of an installed package.
-               return [0, _("Invalid atom: '%s'") % (e,)]
-
-       return [1, selected_atoms]
-
-def dep_wordreduce(mydeplist,mysettings,mydbapi,mode,use_cache=1):
-       "Reduces the deplist to ones and zeros"
-       deplist=mydeplist[:]
-       for mypos, token in enumerate(deplist):
-               if isinstance(deplist[mypos], list):
-                       #recurse
-                       deplist[mypos]=dep_wordreduce(deplist[mypos],mysettings,mydbapi,mode,use_cache=use_cache)
-               elif deplist[mypos]=="||":
-                       pass
-               elif token[:1] == "!":
-                       deplist[mypos] = False
-               else:
-                       mykey = deplist[mypos].cp
-                       if mysettings and mykey in mysettings.pprovideddict and \
-                               match_from_list(deplist[mypos], mysettings.pprovideddict[mykey]):
-                               deplist[mypos]=True
-                       elif mydbapi is None:
-                               # Assume nothing is satisfied.  This forces dep_zapdeps to
-                               # return all of deps the deps that have been selected
-                               # (excluding those satisfied by package.provided).
-                               deplist[mypos] = False
-                       else:
-                               if mode:
-                                       x = mydbapi.xmatch(mode, deplist[mypos])
-                                       if mode.startswith("minimum-"):
-                                               mydep = []
-                                               if x:
-                                                       mydep.append(x)
-                                       else:
-                                               mydep = x
-                               else:
-                                       mydep=mydbapi.match(deplist[mypos],use_cache=use_cache)
-                               if mydep!=None:
-                                       tmp=(len(mydep)>=1)
-                                       if deplist[mypos][0]=="!":
-                                               tmp=False
-                                       deplist[mypos]=tmp
-                               else:
-                                       #encountered invalid string
-                                       return None
-       return deplist
-
-def getmaskingreason(mycpv, metadata=None, settings=None, portdb=None, return_location=False):
-       from portage.util import grablines
-       if settings is None:
-               settings = globals()["settings"]
-       if portdb is None:
-               portdb = globals()["portdb"]
-       mysplit = catpkgsplit(mycpv)
-       if not mysplit:
-               raise ValueError(_("invalid CPV: %s") % mycpv)
-       if metadata is None:
-               db_keys = list(portdb._aux_cache_keys)
-               try:
-                       metadata = dict(zip(db_keys, portdb.aux_get(mycpv, db_keys)))
-               except KeyError:
-                       if not portdb.cpv_exists(mycpv):
-                               raise
-       if metadata is None:
-               # Can't access SLOT due to corruption.
-               cpv_slot_list = [mycpv]
-       else:
-               cpv_slot_list = ["%s:%s" % (mycpv, metadata["SLOT"])]
-       mycp=mysplit[0]+"/"+mysplit[1]
-
-       # XXX- This is a temporary duplicate of code from the config constructor.
-       locations = [os.path.join(settings["PORTDIR"], "profiles")]
-       locations.extend(settings.profiles)
-       for ov in settings["PORTDIR_OVERLAY"].split():
-               profdir = os.path.join(normalize_path(ov), "profiles")
-               if os.path.isdir(profdir):
-                       locations.append(profdir)
-       locations.append(os.path.join(settings["PORTAGE_CONFIGROOT"],
-               USER_CONFIG_PATH))
-       locations.reverse()
-       pmasklists = [(x, grablines(os.path.join(x, "package.mask"), recursive=1)) for x in locations]
-
-       if mycp in settings.pmaskdict:
-               for x in settings.pmaskdict[mycp]:
-                       if match_from_list(x, cpv_slot_list):
-                               for pmask in pmasklists:
-                                       comment = ""
-                                       comment_valid = -1
-                                       pmask_filename = os.path.join(pmask[0], "package.mask")
-                                       for i in range(len(pmask[1])):
-                                               l = pmask[1][i].strip()
-                                               if l == "":
-                                                       comment = ""
-                                                       comment_valid = -1
-                                               elif l[0] == "#":
-                                                       comment += (l+"\n")
-                                                       comment_valid = i + 1
-                                               elif l == x:
-                                                       if comment_valid != i:
-                                                               comment = ""
-                                                       if return_location:
-                                                               return (comment, pmask_filename)
-                                                       else:
-                                                               return comment
-                                               elif comment_valid != -1:
-                                                       # Apparently this comment applies to muliple masks, so
-                                                       # it remains valid until a blank line is encountered.
-                                                       comment_valid += 1
-       if return_location:
-               return (None, None)
-       else:
-               return None
-
-def getmaskingstatus(mycpv, settings=None, portdb=None):
-       if settings is None:
-               settings = config(clone=globals()["settings"])
-       if portdb is None:
-               portdb = globals()["portdb"]
-
-       metadata = None
-       installed = False
-       if not isinstance(mycpv, basestring):
-               # emerge passed in a Package instance
-               pkg = mycpv
-               mycpv = pkg.cpv
-               metadata = pkg.metadata
-               installed = pkg.installed
-
-       mysplit = catpkgsplit(mycpv)
-       if not mysplit:
-               raise ValueError(_("invalid CPV: %s") % mycpv)
-       if metadata is None:
-               db_keys = list(portdb._aux_cache_keys)
-               try:
-                       metadata = dict(zip(db_keys, portdb.aux_get(mycpv, db_keys)))
-               except KeyError:
-                       if not portdb.cpv_exists(mycpv):
-                               raise
-                       return ["corruption"]
-               if "?" in metadata["LICENSE"]:
-                       settings.setcpv(mycpv, mydb=metadata)
-                       metadata["USE"] = settings["PORTAGE_USE"]
-               else:
-                       metadata["USE"] = ""
-       mycp=mysplit[0]+"/"+mysplit[1]
-
-       rValue = []
-
-       # profile checking
-       if settings._getProfileMaskAtom(mycpv, metadata):
-               rValue.append("profile")
-
-       # package.mask checking
-       if settings._getMaskAtom(mycpv, metadata):
-               rValue.append("package.mask")
-
-       # keywords checking
-       eapi = metadata["EAPI"]
-       mygroups = settings._getKeywords(mycpv, metadata)
-       licenses = metadata["LICENSE"]
-       properties = metadata["PROPERTIES"]
-       slot = metadata["SLOT"]
-       if eapi.startswith("-"):
-               eapi = eapi[1:]
-       if not eapi_is_supported(eapi):
-               return ["EAPI %s" % eapi]
-       elif _eapi_is_deprecated(eapi) and not installed:
-               return ["EAPI %s" % eapi]
-       egroups = settings.configdict["backupenv"].get(
-               "ACCEPT_KEYWORDS", "").split()
-       pgroups = settings["ACCEPT_KEYWORDS"].split()
-       myarch = settings["ARCH"]
-       if pgroups and myarch not in pgroups:
-               """For operating systems other than Linux, ARCH is not necessarily a
-               valid keyword."""
-               myarch = pgroups[0].lstrip("~")
-
-       cp = cpv_getkey(mycpv)
-       pkgdict = settings.pkeywordsdict.get(cp)
-       matches = False
-       if pkgdict:
-               cpv_slot_list = ["%s:%s" % (mycpv, metadata["SLOT"])]
-               for atom, pkgkeywords in pkgdict.items():
-                       if match_from_list(atom, cpv_slot_list):
-                               matches = True
-                               pgroups.extend(pkgkeywords)
-       if matches or egroups:
-               pgroups.extend(egroups)
-               inc_pgroups = set()
-               for x in pgroups:
-                       if x.startswith("-"):
-                               if x == "-*":
-                                       inc_pgroups.clear()
-                               else:
-                                       inc_pgroups.discard(x[1:])
-                       else:
-                               inc_pgroups.add(x)
-               pgroups = inc_pgroups
-               del inc_pgroups
-
-       kmask = "missing"
-
-       if '**' in pgroups:
-               kmask = None
-       else:
-               for keyword in pgroups:
-                       if keyword in mygroups:
-                               kmask = None
-                               break
-
-       if kmask:
-               fallback = None
-               for gp in mygroups:
-                       if gp=="*":
-                               kmask=None
-                               break
-                       elif gp=="-"+myarch and myarch in pgroups:
-                               kmask="-"+myarch
-                               break
-                       elif gp=="~"+myarch and myarch in pgroups:
-                               kmask="~"+myarch
-                               break
-
-       try:
-               missing_licenses = settings._getMissingLicenses(mycpv, metadata)
-               if missing_licenses:
-                       allowed_tokens = set(["||", "(", ")"])
-                       allowed_tokens.update(missing_licenses)
-                       license_split = licenses.split()
-                       license_split = [x for x in license_split \
-                               if x in allowed_tokens]
-                       msg = license_split[:]
-                       msg.append("license(s)")
-                       rValue.append(" ".join(msg))
-       except portage.exception.InvalidDependString as e:
-               rValue.append("LICENSE: "+str(e))
-
-       try:
-               missing_properties = settings._getMissingProperties(mycpv, metadata)
-               if missing_properties:
-                       allowed_tokens = set(["||", "(", ")"])
-                       allowed_tokens.update(missing_properties)
-                       properties_split = properties.split()
-                       properties_split = [x for x in properties_split \
-                                       if x in allowed_tokens]
-                       msg = properties_split[:]
-                       msg.append("properties")
-                       rValue.append(" ".join(msg))
-       except portage.exception.InvalidDependString as e:
-               rValue.append("PROPERTIES: "+str(e))
-
-       # Only show KEYWORDS masks for installed packages
-       # if they're not masked for any other reason.
-       if kmask and (not installed or not rValue):
-               rValue.append(kmask+" keyword")
-
-       return rValue
-
 auxdbkeys = (
   'DEPEND',    'RDEPEND',   'SLOT',      'SRC_URI',
        'RESTRICT',  'HOMEPAGE',  'LICENSE',   'DESCRIPTION',
index dcd933f623b78ce2054a691c18eda6961e0ff676..54d3877b272d2b7cda3dee82994d86f7486041f7 100644 (file)
@@ -8,7 +8,7 @@ import re
 
 import portage
 portage.proxy.lazyimport.lazyimport(globals(),
-       'portage.dbapi.dep_expand:dep_expand',
+       'portage.dbapi.dep_expand:_dep_expand',
        'portage.dep:match_from_list',
        'portage.locks:unlockfile',
        'portage.output:colorize',
@@ -122,7 +122,7 @@ class dbapi(object):
                Returns:
                        a list of packages that match origdep
                """
-               mydep = dep_expand(origdep, mydb=self, settings=self.settings)
+               mydep = _dep_expand(origdep, mydb=self, settings=self.settings)
                return list(self._iter_match(mydep,
                        self.cp_list(mydep.cp, use_cache=use_cache)))
 
index b2983a568ca009797bafa54bda50f429dd5283aa..0cbcd88b122d7ca8c9e3c1b176fc422678d87240 100644 (file)
@@ -40,8 +40,9 @@ from portage.exception import CommandNotFound, \
        InvalidData, InvalidPackageName, \
        FileNotFound, PermissionDenied, UnsupportedAPIException
 from portage.localization import _
+from portage.util.movefile import movefile
 
-from portage import abssymlink, movefile, _movefile, bsd_chflags
+from portage import abssymlink, _movefile, bsd_chflags
 
 # This is a special version of the os module, wrapped for unicode support.
 from portage import os
diff --git a/pym/portage/dep/dep_check.py b/pym/portage/dep/dep_check.py
new file mode 100644 (file)
index 0000000..1bc1e40
--- /dev/null
@@ -0,0 +1,634 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+__all__ = ['dep_check', 'dep_eval', 'dep_wordreduce', 'dep_zapdeps']
+
+import logging
+
+import portage
+from portage.dep import Atom, dep_opconvert, match_from_list, paren_reduce, \
+       remove_slot, use_reduce
+from portage.exception import InvalidAtom, InvalidDependString, ParseError
+from portage.localization import _
+from portage.util import writemsg, writemsg_level
+from portage.versions import catpkgsplit, cpv_getkey, pkgcmp
+
+def _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings, myroot="/",
+       trees=None, use_mask=None, use_force=None, **kwargs):
+       """
+       In order to solve bug #141118, recursively expand new-style virtuals so
+       as to collapse one or more levels of indirection, generating an expanded
+       search space. In dep_zapdeps, new-style virtuals will be assigned
+       zero cost regardless of whether or not they are currently installed. Virtual
+       blockers are supported but only when the virtual expands to a single
+       atom because it wouldn't necessarily make sense to block all the components
+       of a compound virtual.  When more than one new-style virtual is matched,
+       the matches are sorted from highest to lowest versions and the atom is
+       expanded to || ( highest match ... lowest match )."""
+       newsplit = []
+       mytrees = trees[myroot]
+       portdb = mytrees["porttree"].dbapi
+       atom_graph = mytrees.get("atom_graph")
+       parent = mytrees.get("parent")
+       virt_parent = mytrees.get("virt_parent")
+       graph_parent = None
+       eapi = None
+       if parent is not None:
+               if virt_parent is not None:
+                       graph_parent = virt_parent
+                       eapi = virt_parent[0].metadata['EAPI']
+               else:
+                       graph_parent = parent
+                       eapi = parent.metadata["EAPI"]
+       repoman = not mysettings.local_config
+       if kwargs["use_binaries"]:
+               portdb = trees[myroot]["bintree"].dbapi
+       myvirtuals = mysettings.getvirtuals()
+       pprovideddict = mysettings.pprovideddict
+       myuse = kwargs["myuse"]
+       for x in mysplit:
+               if x == "||":
+                       newsplit.append(x)
+                       continue
+               elif isinstance(x, list):
+                       newsplit.append(_expand_new_virtuals(x, edebug, mydbapi,
+                               mysettings, myroot=myroot, trees=trees, use_mask=use_mask,
+                               use_force=use_force, **kwargs))
+                       continue
+
+               if not isinstance(x, Atom):
+                       try:
+                               x = Atom(x)
+                       except InvalidAtom:
+                               if portage.dep._dep_check_strict:
+                                       raise ParseError(
+                                               _("invalid atom: '%s'") % x)
+                               else:
+                                       # Only real Atom instances are allowed past this point.
+                                       continue
+                       else:
+                               if x.blocker and x.blocker.overlap.forbid and \
+                                       eapi in ("0", "1") and portage.dep._dep_check_strict:
+                                       raise ParseError(
+                                               _("invalid atom: '%s'") % (x,))
+                               if x.use and eapi in ("0", "1") and \
+                                       portage.dep._dep_check_strict:
+                                       raise ParseError(
+                                               _("invalid atom: '%s'") % (x,))
+
+               if repoman and x.use and x.use.conditional:
+                       evaluated_atom = remove_slot(x)
+                       if x.slot:
+                               evaluated_atom += ":%s" % x.slot
+                       evaluated_atom += str(x.use._eval_qa_conditionals(
+                               use_mask, use_force))
+                       x = Atom(evaluated_atom)
+
+               if not repoman and \
+                       myuse is not None and isinstance(x, Atom) and x.use:
+                       if x.use.conditional:
+                               x = x.evaluate_conditionals(myuse)
+
+               mykey = x.cp
+               if not mykey.startswith("virtual/"):
+                       newsplit.append(x)
+                       if atom_graph is not None:
+                               atom_graph.add(x, graph_parent)
+                       continue
+               mychoices = myvirtuals.get(mykey, [])
+               if x.blocker:
+                       # Virtual blockers are no longer expanded here since
+                       # the un-expanded virtual atom is more useful for
+                       # maintaining a cache of blocker atoms.
+                       newsplit.append(x)
+                       if atom_graph is not None:
+                               atom_graph.add(x, graph_parent)
+                       continue
+
+               if repoman or not hasattr(portdb, 'match_pkgs'):
+                       if portdb.cp_list(x.cp):
+                               newsplit.append(x)
+                       else:
+                               # TODO: Add PROVIDE check for repoman.
+                               a = []
+                               for y in mychoices:
+                                       a.append(Atom(x.replace(x.cp, y.cp, 1)))
+                               if not a:
+                                       newsplit.append(x)
+                               elif len(a) == 1:
+                                       newsplit.append(a[0])
+                               else:
+                                       newsplit.append(['||'] + a)
+                       continue
+
+               pkgs = []
+               # Ignore USE deps here, since otherwise we might not
+               # get any matches. Choices with correct USE settings
+               # will be preferred in dep_zapdeps().
+               matches = portdb.match_pkgs(x.without_use)
+               # Use descending order to prefer higher versions.
+               matches.reverse()
+               for pkg in matches:
+                       # only use new-style matches
+                       if pkg.cp.startswith("virtual/"):
+                               pkgs.append(pkg)
+               if not (pkgs or mychoices):
+                       # This one couldn't be expanded as a new-style virtual.  Old-style
+                       # virtuals have already been expanded by dep_virtual, so this one
+                       # is unavailable and dep_zapdeps will identify it as such.  The
+                       # atom is not eliminated here since it may still represent a
+                       # dependency that needs to be satisfied.
+                       newsplit.append(x)
+                       if atom_graph is not None:
+                               atom_graph.add(x, graph_parent)
+                       continue
+
+               a = []
+               for pkg in pkgs:
+                       virt_atom = '=' + pkg.cpv
+                       if x.use:
+                               virt_atom += str(x.use)
+                       virt_atom = Atom(virt_atom)
+                       # According to GLEP 37, RDEPEND is the only dependency
+                       # type that is valid for new-style virtuals. Repoman
+                       # should enforce this.
+                       depstring = pkg.metadata['RDEPEND']
+                       pkg_kwargs = kwargs.copy()
+                       pkg_kwargs["myuse"] = pkg.use.enabled
+                       if edebug:
+                               writemsg_level(_("Virtual Parent:      %s\n") \
+                                       % (pkg,), noiselevel=-1, level=logging.DEBUG)
+                               writemsg_level(_("Virtual Depstring:   %s\n") \
+                                       % (depstring,), noiselevel=-1, level=logging.DEBUG)
+
+                       # Set EAPI used for validation in dep_check() recursion.
+                       mytrees["virt_parent"] = (pkg, virt_atom)
+
+                       try:
+                               mycheck = dep_check(depstring, mydbapi, mysettings,
+                                       myroot=myroot, trees=trees, **pkg_kwargs)
+                       finally:
+                               # Restore previous EAPI after recursion.
+                               if virt_parent is not None:
+                                       mytrees["virt_parent"] = virt_parent
+                               else:
+                                       del mytrees["virt_parent"]
+
+                       if not mycheck[0]:
+                               raise ParseError(
+                                       "%s: %s '%s'" % (y[0], mycheck[1], depstring))
+
+                       # pull in the new-style virtual
+                       mycheck[1].append(virt_atom)
+                       a.append(mycheck[1])
+                       if atom_graph is not None:
+                               atom_graph.add(virt_atom, graph_parent)
+               # Plain old-style virtuals.  New-style virtuals are preferred.
+               if not pkgs:
+                               for y in mychoices:
+                                       new_atom = Atom(x.replace(x.cp, y.cp, 1))
+                                       matches = portdb.match(new_atom)
+                                       # portdb is an instance of depgraph._dep_check_composite_db, so
+                                       # USE conditionals are already evaluated.
+                                       if matches and mykey in \
+                                               portdb.aux_get(matches[-1], ['PROVIDE'])[0].split():
+                                               a.append(new_atom)
+                                               if atom_graph is not None:
+                                                       atom_graph.add(new_atom, graph_parent)
+
+               if not a and mychoices:
+                       # Check for a virtual package.provided match.
+                       for y in mychoices:
+                               new_atom = Atom(x.replace(x.cp, y.cp, 1))
+                               if match_from_list(new_atom,
+                                       pprovideddict.get(new_atom.cp, [])):
+                                       a.append(new_atom)
+                                       if atom_graph is not None:
+                                               atom_graph.add(new_atom, graph_parent)
+
+               if not a:
+                       newsplit.append(x)
+                       if atom_graph is not None:
+                               atom_graph.add(x, graph_parent)
+               elif len(a) == 1:
+                       newsplit.append(a[0])
+               else:
+                       newsplit.append(['||'] + a)
+
+       return newsplit
+
+def dep_eval(deplist):
+       if not deplist:
+               return 1
+       if deplist[0]=="||":
+               #or list; we just need one "1"
+               for x in deplist[1:]:
+                       if isinstance(x, list):
+                               if dep_eval(x)==1:
+                                       return 1
+                       elif x==1:
+                                       return 1
+               #XXX: unless there's no available atoms in the list
+               #in which case we need to assume that everything is
+               #okay as some ebuilds are relying on an old bug.
+               if len(deplist) == 1:
+                       return 1
+               return 0
+       else:
+               for x in deplist:
+                       if isinstance(x, list):
+                               if dep_eval(x)==0:
+                                       return 0
+                       elif x==0 or x==2:
+                               return 0
+               return 1
+
+def dep_zapdeps(unreduced, reduced, myroot, use_binaries=0, trees=None):
+       """
+       Takes an unreduced and reduced deplist and removes satisfied dependencies.
+       Returned deplist contains steps that must be taken to satisfy dependencies.
+       """
+       if trees is None:
+               trees = portage.db
+       writemsg("ZapDeps -- %s\n" % (use_binaries), 2)
+       if not reduced or unreduced == ["||"] or dep_eval(reduced):
+               return []
+
+       if unreduced[0] != "||":
+               unresolved = []
+               for x, satisfied in zip(unreduced, reduced):
+                       if isinstance(x, list):
+                               unresolved += dep_zapdeps(x, satisfied, myroot,
+                                       use_binaries=use_binaries, trees=trees)
+                       elif not satisfied:
+                               unresolved.append(x)
+               return unresolved
+
+       # We're at a ( || atom ... ) type level and need to make a choice
+       deps = unreduced[1:]
+       satisfieds = reduced[1:]
+
+       # Our preference order is for an the first item that:
+       # a) contains all unmasked packages with the same key as installed packages
+       # b) contains all unmasked packages
+       # c) contains masked installed packages
+       # d) is the first item
+
+       preferred_installed = []
+       preferred_in_graph = []
+       preferred_any_slot = []
+       preferred_non_installed = []
+       unsat_use_in_graph = []
+       unsat_use_installed = []
+       unsat_use_non_installed = []
+       other = []
+
+       # unsat_use_* must come after preferred_non_installed
+       # for correct ordering in cases like || ( foo[a] foo[b] ).
+       choice_bins = (
+               preferred_in_graph,
+               preferred_installed,
+               preferred_any_slot,
+               preferred_non_installed,
+               unsat_use_in_graph,
+               unsat_use_installed,
+               unsat_use_non_installed,
+               other,
+       )
+
+       # Alias the trees we'll be checking availability against
+       parent   = trees[myroot].get("parent")
+       priority = trees[myroot].get("priority")
+       graph_db = trees[myroot].get("graph_db")
+       vardb = None
+       if "vartree" in trees[myroot]:
+               vardb = trees[myroot]["vartree"].dbapi
+       if use_binaries:
+               mydbapi = trees[myroot]["bintree"].dbapi
+       else:
+               mydbapi = trees[myroot]["porttree"].dbapi
+
+       # Sort the deps into installed, not installed but already 
+       # in the graph and other, not installed and not in the graph
+       # and other, with values of [[required_atom], availablility]
+       for x, satisfied in zip(deps, satisfieds):
+               if isinstance(x, list):
+                       atoms = dep_zapdeps(x, satisfied, myroot,
+                               use_binaries=use_binaries, trees=trees)
+               else:
+                       atoms = [x]
+               if vardb is None:
+                       # When called by repoman, we can simply return the first choice
+                       # because dep_eval() handles preference selection.
+                       return atoms
+
+               all_available = True
+               all_use_satisfied = True
+               slot_map = {}
+               cp_map = {}
+               for atom in atoms:
+                       if atom.blocker:
+                               continue
+                       # Ignore USE dependencies here since we don't want USE
+                       # settings to adversely affect || preference evaluation.
+                       avail_pkg = mydbapi.match(atom.without_use)
+                       if avail_pkg:
+                               avail_pkg = avail_pkg[-1] # highest (ascending order)
+                               avail_slot = Atom("%s:%s" % (atom.cp,
+                                       mydbapi.aux_get(avail_pkg, ["SLOT"])[0]))
+                       if not avail_pkg:
+                               all_available = False
+                               all_use_satisfied = False
+                               break
+
+                       if atom.use:
+                               avail_pkg_use = mydbapi.match(atom)
+                               if not avail_pkg_use:
+                                       all_use_satisfied = False
+                               else:
+                                       # highest (ascending order)
+                                       avail_pkg_use = avail_pkg_use[-1]
+                                       if avail_pkg_use != avail_pkg:
+                                               avail_pkg = avail_pkg_use
+                                               avail_slot = Atom("%s:%s" % (atom.cp,
+                                                       mydbapi.aux_get(avail_pkg, ["SLOT"])[0]))
+
+                       slot_map[avail_slot] = avail_pkg
+                       pkg_cp = cpv_getkey(avail_pkg)
+                       highest_cpv = cp_map.get(pkg_cp)
+                       if highest_cpv is None or \
+                               pkgcmp(catpkgsplit(avail_pkg)[1:],
+                               catpkgsplit(highest_cpv)[1:]) > 0:
+                               cp_map[pkg_cp] = avail_pkg
+
+               this_choice = (atoms, slot_map, cp_map, all_available)
+               if all_available:
+                       # The "all installed" criterion is not version or slot specific.
+                       # If any version of a package is already in the graph then we
+                       # assume that it is preferred over other possible packages choices.
+                       all_installed = True
+                       for atom in set(Atom(atom.cp) for atom in atoms \
+                               if not atom.blocker):
+                               # New-style virtuals have zero cost to install.
+                               if not vardb.match(atom) and not atom.startswith("virtual/"):
+                                       all_installed = False
+                                       break
+                       all_installed_slots = False
+                       if all_installed:
+                               all_installed_slots = True
+                               for slot_atom in slot_map:
+                                       # New-style virtuals have zero cost to install.
+                                       if not vardb.match(slot_atom) and \
+                                               not slot_atom.startswith("virtual/"):
+                                               all_installed_slots = False
+                                               break
+                       if graph_db is None:
+                               if all_use_satisfied:
+                                       if all_installed:
+                                               if all_installed_slots:
+                                                       preferred_installed.append(this_choice)
+                                               else:
+                                                       preferred_any_slot.append(this_choice)
+                                       else:
+                                               preferred_non_installed.append(this_choice)
+                               else:
+                                       if all_installed_slots:
+                                               unsat_use_installed.append(this_choice)
+                                       else:
+                                               unsat_use_non_installed.append(this_choice)
+                       else:
+                               all_in_graph = True
+                               for slot_atom in slot_map:
+                                       # New-style virtuals have zero cost to install.
+                                       if not graph_db.match(slot_atom) and \
+                                               not slot_atom.startswith("virtual/"):
+                                               all_in_graph = False
+                                               break
+                               circular_atom = None
+                               if all_in_graph:
+                                       if parent is None or priority is None:
+                                               pass
+                                       elif priority.buildtime:
+                                               # Check if the atom would result in a direct circular
+                                               # dependency and try to avoid that if it seems likely
+                                               # to be unresolvable. This is only relevant for
+                                               # buildtime deps that aren't already satisfied by an
+                                               # installed package.
+                                               cpv_slot_list = [parent]
+                                               for atom in atoms:
+                                                       if atom.blocker:
+                                                               continue
+                                                       if vardb.match(atom):
+                                                               # If the atom is satisfied by an installed
+                                                               # version then it's not a circular dep.
+                                                               continue
+                                                       if atom.cp != parent.cp:
+                                                               continue
+                                                       if match_from_list(atom, cpv_slot_list):
+                                                               circular_atom = atom
+                                                               break
+                               if circular_atom is not None:
+                                       other.append(this_choice)
+                               else:
+                                       if all_use_satisfied:
+                                               if all_in_graph:
+                                                       preferred_in_graph.append(this_choice)
+                                               elif all_installed:
+                                                       if all_installed_slots:
+                                                               preferred_installed.append(this_choice)
+                                                       else:
+                                                               preferred_any_slot.append(this_choice)
+                                               else:
+                                                       preferred_non_installed.append(this_choice)
+                                       else:
+                                               if all_in_graph:
+                                                       unsat_use_in_graph.append(this_choice)
+                                               elif all_installed_slots:
+                                                       unsat_use_installed.append(this_choice)
+                                               else:
+                                                       unsat_use_non_installed.append(this_choice)
+               else:
+                       other.append(this_choice)
+
+       # Prefer choices which contain upgrades to higher slots. This helps
+       # for deps such as || ( foo:1 foo:2 ), where we want to prefer the
+       # atom which matches the higher version rather than the atom furthest
+       # to the left. Sorting is done separately for each of choice_bins, so
+       # as not to interfere with the ordering of the bins. Because of the
+       # bin separation, the main function of this code is to allow
+       # --depclean to remove old slots (rather than to pull in new slots).
+       for choices in choice_bins:
+               if len(choices) < 2:
+                       continue
+               for choice_1 in choices[1:]:
+                       atoms_1, slot_map_1, cp_map_1, all_available_1 = choice_1
+                       cps = set(cp_map_1)
+                       for choice_2 in choices:
+                               if choice_1 is choice_2:
+                                       # choice_1 will not be promoted, so move on
+                                       break
+                               atoms_2, slot_map_2, cp_map_2, all_available_2 = choice_2
+                               intersecting_cps = cps.intersection(cp_map_2)
+                               if not intersecting_cps:
+                                       continue
+                               has_upgrade = False
+                               has_downgrade = False
+                               for cp in intersecting_cps:
+                                       version_1 = cp_map_1[cp]
+                                       version_2 = cp_map_2[cp]
+                                       difference = pkgcmp(catpkgsplit(version_1)[1:],
+                                               catpkgsplit(version_2)[1:])
+                                       if difference != 0:
+                                               if difference > 0:
+                                                       has_upgrade = True
+                                               else:
+                                                       has_downgrade = True
+                                                       break
+                               if has_upgrade and not has_downgrade:
+                                       # promote choice_1 in front of choice_2
+                                       choices.remove(choice_1)
+                                       index_2 = choices.index(choice_2)
+                                       choices.insert(index_2, choice_1)
+                                       break
+
+       for allow_masked in (False, True):
+               for choices in choice_bins:
+                       for atoms, slot_map, cp_map, all_available in choices:
+                               if all_available or allow_masked:
+                                       return atoms
+
+       assert(False) # This point should not be reachable
+
+def dep_check(depstring, mydbapi, mysettings, use="yes", mode=None, myuse=None,
+       use_cache=1, use_binaries=0, myroot="/", trees=None):
+       """Takes a depend string and parses the condition."""
+       edebug = mysettings.get("PORTAGE_DEBUG", None) == "1"
+       #check_config_instance(mysettings)
+       if trees is None:
+               trees = globals()["db"]
+       if use=="yes":
+               if myuse is None:
+                       #default behavior
+                       myusesplit = mysettings["PORTAGE_USE"].split()
+               else:
+                       myusesplit = myuse
+                       # We've been given useflags to use.
+                       #print "USE FLAGS PASSED IN."
+                       #print myuse
+                       #if "bindist" in myusesplit:
+                       #       print "BINDIST is set!"
+                       #else:
+                       #       print "BINDIST NOT set."
+       else:
+               #we are being run by autouse(), don't consult USE vars yet.
+               # WE ALSO CANNOT USE SETTINGS
+               myusesplit=[]
+
+       #convert parenthesis to sublists
+       try:
+               mysplit = paren_reduce(depstring)
+       except InvalidDependString as e:
+               return [0, str(e)]
+
+       mymasks = set()
+       useforce = set()
+       useforce.add(mysettings["ARCH"])
+       if use == "all":
+               # This masking/forcing is only for repoman.  In other cases, relevant
+               # masking/forcing should have already been applied via
+               # config.regenerate().  Also, binary or installed packages may have
+               # been built with flags that are now masked, and it would be
+               # inconsistent to mask them now.  Additionally, myuse may consist of
+               # flags from a parent package that is being merged to a $ROOT that is
+               # different from the one that mysettings represents.
+               mymasks.update(mysettings.usemask)
+               mymasks.update(mysettings.archlist())
+               mymasks.discard(mysettings["ARCH"])
+               useforce.update(mysettings.useforce)
+               useforce.difference_update(mymasks)
+       try:
+               mysplit = use_reduce(mysplit, uselist=myusesplit,
+                       masklist=mymasks, matchall=(use=="all"), excludeall=useforce)
+       except InvalidDependString as e:
+               return [0, str(e)]
+
+       # Do the || conversions
+       mysplit = dep_opconvert(mysplit)
+
+       if mysplit == []:
+               #dependencies were reduced to nothing
+               return [1,[]]
+
+       # Recursively expand new-style virtuals so as to
+       # collapse one or more levels of indirection.
+       try:
+               mysplit = _expand_new_virtuals(mysplit, edebug, mydbapi, mysettings,
+                       use=use, mode=mode, myuse=myuse,
+                       use_force=useforce, use_mask=mymasks, use_cache=use_cache,
+                       use_binaries=use_binaries, myroot=myroot, trees=trees)
+       except ParseError as e:
+               return [0, str(e)]
+
+       mysplit2=mysplit[:]
+       mysplit2=dep_wordreduce(mysplit2,mysettings,mydbapi,mode,use_cache=use_cache)
+       if mysplit2 is None:
+               return [0, _("Invalid token")]
+
+       writemsg("\n\n\n", 1)
+       writemsg("mysplit:  %s\n" % (mysplit), 1)
+       writemsg("mysplit2: %s\n" % (mysplit2), 1)
+
+       try:
+               selected_atoms = dep_zapdeps(mysplit, mysplit2, myroot,
+                       use_binaries=use_binaries, trees=trees)
+       except InvalidAtom as e:
+               if portage.dep._dep_check_strict:
+                       raise # This shouldn't happen.
+               # dbapi.match() failed due to an invalid atom in
+               # the dependencies of an installed package.
+               return [0, _("Invalid atom: '%s'") % (e,)]
+
+       return [1, selected_atoms]
+
+def dep_wordreduce(mydeplist,mysettings,mydbapi,mode,use_cache=1):
+       "Reduces the deplist to ones and zeros"
+       deplist=mydeplist[:]
+       for mypos, token in enumerate(deplist):
+               if isinstance(deplist[mypos], list):
+                       #recurse
+                       deplist[mypos]=dep_wordreduce(deplist[mypos],mysettings,mydbapi,mode,use_cache=use_cache)
+               elif deplist[mypos]=="||":
+                       pass
+               elif token[:1] == "!":
+                       deplist[mypos] = False
+               else:
+                       mykey = deplist[mypos].cp
+                       if mysettings and mykey in mysettings.pprovideddict and \
+                               match_from_list(deplist[mypos], mysettings.pprovideddict[mykey]):
+                               deplist[mypos]=True
+                       elif mydbapi is None:
+                               # Assume nothing is satisfied.  This forces dep_zapdeps to
+                               # return all of deps the deps that have been selected
+                               # (excluding those satisfied by package.provided).
+                               deplist[mypos] = False
+                       else:
+                               if mode:
+                                       x = mydbapi.xmatch(mode, deplist[mypos])
+                                       if mode.startswith("minimum-"):
+                                               mydep = []
+                                               if x:
+                                                       mydep.append(x)
+                                       else:
+                                               mydep = x
+                               else:
+                                       mydep=mydbapi.match(deplist[mypos],use_cache=use_cache)
+                               if mydep!=None:
+                                       tmp=(len(mydep)>=1)
+                                       if deplist[mypos][0]=="!":
+                                               tmp=False
+                                       deplist[mypos]=tmp
+                               else:
+                                       #encountered invalid string
+                                       return None
+       return deplist
diff --git a/pym/portage/package/ebuild/getmaskingreason.py b/pym/portage/package/ebuild/getmaskingreason.py
new file mode 100644 (file)
index 0000000..d0c8164
--- /dev/null
@@ -0,0 +1,78 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+__all__ = ['getmaskingreason']
+
+import portage
+from portage import os
+from portage.const import USER_CONFIG_PATH
+from portage.dep import match_from_list
+from portage.localization import _
+from portage.util import grablines, normalize_path
+from portage.versions import catpkgsplit
+
+def getmaskingreason(mycpv, metadata=None, settings=None, portdb=None, return_location=False):
+       if settings is None:
+               settings = portage.settings
+       if portdb is None:
+               portdb = portage.portdb
+       mysplit = catpkgsplit(mycpv)
+       if not mysplit:
+               raise ValueError(_("invalid CPV: %s") % mycpv)
+       if metadata is None:
+               db_keys = list(portdb._aux_cache_keys)
+               try:
+                       metadata = dict(zip(db_keys, portdb.aux_get(mycpv, db_keys)))
+               except KeyError:
+                       if not portdb.cpv_exists(mycpv):
+                               raise
+       if metadata is None:
+               # Can't access SLOT due to corruption.
+               cpv_slot_list = [mycpv]
+       else:
+               cpv_slot_list = ["%s:%s" % (mycpv, metadata["SLOT"])]
+       mycp=mysplit[0]+"/"+mysplit[1]
+
+       # XXX- This is a temporary duplicate of code from the config constructor.
+       locations = [os.path.join(settings["PORTDIR"], "profiles")]
+       locations.extend(settings.profiles)
+       for ov in settings["PORTDIR_OVERLAY"].split():
+               profdir = os.path.join(normalize_path(ov), "profiles")
+               if os.path.isdir(profdir):
+                       locations.append(profdir)
+       locations.append(os.path.join(settings["PORTAGE_CONFIGROOT"],
+               USER_CONFIG_PATH))
+       locations.reverse()
+       pmasklists = [(x, grablines(os.path.join(x, "package.mask"), recursive=1)) for x in locations]
+
+       if mycp in settings.pmaskdict:
+               for x in settings.pmaskdict[mycp]:
+                       if match_from_list(x, cpv_slot_list):
+                               for pmask in pmasklists:
+                                       comment = ""
+                                       comment_valid = -1
+                                       pmask_filename = os.path.join(pmask[0], "package.mask")
+                                       for i in range(len(pmask[1])):
+                                               l = pmask[1][i].strip()
+                                               if l == "":
+                                                       comment = ""
+                                                       comment_valid = -1
+                                               elif l[0] == "#":
+                                                       comment += (l+"\n")
+                                                       comment_valid = i + 1
+                                               elif l == x:
+                                                       if comment_valid != i:
+                                                               comment = ""
+                                                       if return_location:
+                                                               return (comment, pmask_filename)
+                                                       else:
+                                                               return comment
+                                               elif comment_valid != -1:
+                                                       # Apparently this comment applies to muliple masks, so
+                                                       # it remains valid until a blank line is encountered.
+                                                       comment_valid += 1
+       if return_location:
+               return (None, None)
+       else:
+               return None
diff --git a/pym/portage/package/ebuild/getmaskingstatus.py b/pym/portage/package/ebuild/getmaskingstatus.py
new file mode 100644 (file)
index 0000000..bfe92d8
--- /dev/null
@@ -0,0 +1,154 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+__all__ = ['getmaskingstatus']
+
+import portage
+from portage import eapi_is_supported, _eapi_is_deprecated
+from portage.dep import match_from_list
+from portage.localization import _
+from portage.package.ebuild.config import config
+from portage.versions import catpkgsplit, cpv_getkey
+
+def getmaskingstatus(mycpv, settings=None, portdb=None):
+       if settings is None:
+               settings = config(clone=portage.settings)
+       if portdb is None:
+               portdb = portage.portdb
+
+       metadata = None
+       installed = False
+       if not isinstance(mycpv, basestring):
+               # emerge passed in a Package instance
+               pkg = mycpv
+               mycpv = pkg.cpv
+               metadata = pkg.metadata
+               installed = pkg.installed
+
+       mysplit = catpkgsplit(mycpv)
+       if not mysplit:
+               raise ValueError(_("invalid CPV: %s") % mycpv)
+       if metadata is None:
+               db_keys = list(portdb._aux_cache_keys)
+               try:
+                       metadata = dict(zip(db_keys, portdb.aux_get(mycpv, db_keys)))
+               except KeyError:
+                       if not portdb.cpv_exists(mycpv):
+                               raise
+                       return ["corruption"]
+               if "?" in metadata["LICENSE"]:
+                       settings.setcpv(mycpv, mydb=metadata)
+                       metadata["USE"] = settings["PORTAGE_USE"]
+               else:
+                       metadata["USE"] = ""
+
+       rValue = []
+
+       # profile checking
+       if settings._getProfileMaskAtom(mycpv, metadata):
+               rValue.append("profile")
+
+       # package.mask checking
+       if settings._getMaskAtom(mycpv, metadata):
+               rValue.append("package.mask")
+
+       # keywords checking
+       eapi = metadata["EAPI"]
+       mygroups = settings._getKeywords(mycpv, metadata)
+       licenses = metadata["LICENSE"]
+       properties = metadata["PROPERTIES"]
+       if eapi.startswith("-"):
+               eapi = eapi[1:]
+       if not eapi_is_supported(eapi):
+               return ["EAPI %s" % eapi]
+       elif _eapi_is_deprecated(eapi) and not installed:
+               return ["EAPI %s" % eapi]
+       egroups = settings.configdict["backupenv"].get(
+               "ACCEPT_KEYWORDS", "").split()
+       pgroups = settings["ACCEPT_KEYWORDS"].split()
+       myarch = settings["ARCH"]
+       if pgroups and myarch not in pgroups:
+               """For operating systems other than Linux, ARCH is not necessarily a
+               valid keyword."""
+               myarch = pgroups[0].lstrip("~")
+
+       cp = cpv_getkey(mycpv)
+       pkgdict = settings.pkeywordsdict.get(cp)
+       matches = False
+       if pkgdict:
+               cpv_slot_list = ["%s:%s" % (mycpv, metadata["SLOT"])]
+               for atom, pkgkeywords in pkgdict.items():
+                       if match_from_list(atom, cpv_slot_list):
+                               matches = True
+                               pgroups.extend(pkgkeywords)
+       if matches or egroups:
+               pgroups.extend(egroups)
+               inc_pgroups = set()
+               for x in pgroups:
+                       if x.startswith("-"):
+                               if x == "-*":
+                                       inc_pgroups.clear()
+                               else:
+                                       inc_pgroups.discard(x[1:])
+                       else:
+                               inc_pgroups.add(x)
+               pgroups = inc_pgroups
+               del inc_pgroups
+
+       kmask = "missing"
+
+       if '**' in pgroups:
+               kmask = None
+       else:
+               for keyword in pgroups:
+                       if keyword in mygroups:
+                               kmask = None
+                               break
+
+       if kmask:
+               for gp in mygroups:
+                       if gp=="*":
+                               kmask=None
+                               break
+                       elif gp=="-"+myarch and myarch in pgroups:
+                               kmask="-"+myarch
+                               break
+                       elif gp=="~"+myarch and myarch in pgroups:
+                               kmask="~"+myarch
+                               break
+
+       try:
+               missing_licenses = settings._getMissingLicenses(mycpv, metadata)
+               if missing_licenses:
+                       allowed_tokens = set(["||", "(", ")"])
+                       allowed_tokens.update(missing_licenses)
+                       license_split = licenses.split()
+                       license_split = [x for x in license_split \
+                               if x in allowed_tokens]
+                       msg = license_split[:]
+                       msg.append("license(s)")
+                       rValue.append(" ".join(msg))
+       except portage.exception.InvalidDependString as e:
+               rValue.append("LICENSE: "+str(e))
+
+       try:
+               missing_properties = settings._getMissingProperties(mycpv, metadata)
+               if missing_properties:
+                       allowed_tokens = set(["||", "(", ")"])
+                       allowed_tokens.update(missing_properties)
+                       properties_split = properties.split()
+                       properties_split = [x for x in properties_split \
+                                       if x in allowed_tokens]
+                       msg = properties_split[:]
+                       msg.append("properties")
+                       rValue.append(" ".join(msg))
+       except portage.exception.InvalidDependString as e:
+               rValue.append("PROPERTIES: "+str(e))
+
+       # Only show KEYWORDS masks for installed packages
+       # if they're not masked for any other reason.
+       if kmask and (not installed or not rValue):
+               rValue.append(kmask+" keyword")
+
+       return rValue
diff --git a/pym/portage/util/movefile.py b/pym/portage/util/movefile.py
new file mode 100644 (file)
index 0000000..23110ab
--- /dev/null
@@ -0,0 +1,234 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+__all__ = ['movefile']
+
+import errno
+import os as _os
+import shutil as _shutil
+import stat
+
+import portage
+from portage import bsd_chflags, _encodings, _os_overrides, _selinux, \
+       _unicode_decode, _unicode_func_wrapper, _unicode_module_wrapper
+from portage.const import MOVE_BINARY
+from portage.localization import _
+from portage.process import spawn
+from portage.util import writemsg
+
+def movefile(src, dest, newmtime=None, sstat=None, mysettings=None,
+               hardlink_candidates=None, encoding=_encodings['fs']):
+       """moves a file from src to dest, preserving all permissions and attributes; mtime will
+       be preserved even when moving across filesystems.  Returns true on success and false on
+       failure.  Move is atomic."""
+       #print "movefile("+str(src)+","+str(dest)+","+str(newmtime)+","+str(sstat)+")"
+
+       if mysettings is None:
+               mysettings = portage.settings
+
+       selinux_enabled = mysettings.selinux_enabled()
+       if selinux_enabled:
+               selinux = _unicode_module_wrapper(_selinux, encoding=encoding)
+
+       lchown = _unicode_func_wrapper(portage.data.lchown, encoding=encoding)
+       os = _unicode_module_wrapper(_os,
+               encoding=encoding, overrides=_os_overrides)
+       shutil = _unicode_module_wrapper(_shutil, encoding=encoding)
+
+       try:
+               if not sstat:
+                       sstat=os.lstat(src)
+
+       except SystemExit as e:
+               raise
+       except Exception as e:
+               print(_("!!! Stating source file failed... movefile()"))
+               print("!!!",e)
+               return None
+
+       destexists=1
+       try:
+               dstat=os.lstat(dest)
+       except (OSError, IOError):
+               dstat=os.lstat(os.path.dirname(dest))
+               destexists=0
+
+       if bsd_chflags:
+               if destexists and dstat.st_flags != 0:
+                       bsd_chflags.lchflags(dest, 0)
+               # Use normal stat/chflags for the parent since we want to
+               # follow any symlinks to the real parent directory.
+               pflags = os.stat(os.path.dirname(dest)).st_flags
+               if pflags != 0:
+                       bsd_chflags.chflags(os.path.dirname(dest), 0)
+
+       if destexists:
+               if stat.S_ISLNK(dstat[stat.ST_MODE]):
+                       try:
+                               os.unlink(dest)
+                               destexists=0
+                       except SystemExit as e:
+                               raise
+                       except Exception as e:
+                               pass
+
+       if stat.S_ISLNK(sstat[stat.ST_MODE]):
+               try:
+                       target=os.readlink(src)
+                       if mysettings and mysettings["D"]:
+                               if target.find(mysettings["D"])==0:
+                                       target=target[len(mysettings["D"]):]
+                       if destexists and not stat.S_ISDIR(dstat[stat.ST_MODE]):
+                               os.unlink(dest)
+                       if selinux_enabled:
+                               selinux.symlink(target, dest, src)
+                       else:
+                               os.symlink(target,dest)
+                       lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+                       # utime() only works on the target of a symlink, so it's not
+                       # possible to perserve mtime on symlinks.
+                       return os.lstat(dest)[stat.ST_MTIME]
+               except SystemExit as e:
+                       raise
+               except Exception as e:
+                       print(_("!!! failed to properly create symlink:"))
+                       print("!!!",dest,"->",target)
+                       print("!!!",e)
+                       return None
+
+       hardlinked = False
+       # Since identical files might be merged to multiple filesystems,
+       # so os.link() calls might fail for some paths, so try them all.
+       # For atomic replacement, first create the link as a temp file
+       # and them use os.rename() to replace the destination.
+       if hardlink_candidates:
+               head, tail = os.path.split(dest)
+               hardlink_tmp = os.path.join(head, ".%s._portage_merge_.%s" % \
+                       (tail, os.getpid()))
+               try:
+                       os.unlink(hardlink_tmp)
+               except OSError as e:
+                       if e.errno != errno.ENOENT:
+                               writemsg(_("!!! Failed to remove hardlink temp file: %s\n") % \
+                                       (hardlink_tmp,), noiselevel=-1)
+                               writemsg("!!! %s\n" % (e,), noiselevel=-1)
+                               return None
+                       del e
+               for hardlink_src in hardlink_candidates:
+                       try:
+                               os.link(hardlink_src, hardlink_tmp)
+                       except OSError:
+                               continue
+                       else:
+                               try:
+                                       os.rename(hardlink_tmp, dest)
+                               except OSError as e:
+                                       writemsg(_("!!! Failed to rename %s to %s\n") % \
+                                               (hardlink_tmp, dest), noiselevel=-1)
+                                       writemsg("!!! %s\n" % (e,), noiselevel=-1)
+                                       return None
+                               hardlinked = True
+                               break
+
+       renamefailed=1
+       if hardlinked:
+               renamefailed = False
+       if not hardlinked and (selinux_enabled or sstat.st_dev == dstat.st_dev):
+               try:
+                       if selinux_enabled:
+                               selinux.rename(src, dest)
+                       else:
+                               os.rename(src,dest)
+                       renamefailed=0
+               except OSError as e:
+                       if e.errno != errno.EXDEV:
+                               # Some random error.
+                               print(_("!!! Failed to move %(src)s to %(dest)s") % {"src": src, "dest": dest})
+                               print("!!!",e)
+                               return None
+                       # Invalid cross-device-link 'bind' mounted or actually Cross-Device
+       if renamefailed:
+               didcopy=0
+               if stat.S_ISREG(sstat[stat.ST_MODE]):
+                       try: # For safety copy then move it over.
+                               if selinux_enabled:
+                                       selinux.copyfile(src, dest + "#new")
+                                       selinux.rename(dest + "#new", dest)
+                               else:
+                                       shutil.copyfile(src,dest+"#new")
+                                       os.rename(dest+"#new",dest)
+                               didcopy=1
+                       except SystemExit as e:
+                               raise
+                       except Exception as e:
+                               print(_('!!! copy %(src)s -> %(dest)s failed.') % {"src": src, "dest": dest})
+                               print("!!!",e)
+                               return None
+               else:
+                       #we don't yet handle special, so we need to fall back to /bin/mv
+                       a = spawn([MOVE_BINARY, '-f', src, dest], env=os.environ)
+                       if a != os.EX_OK:
+                               writemsg(_("!!! Failed to move special file:\n"), noiselevel=-1)
+                               writemsg(_("!!! '%(src)s' to '%(dest)s'\n") % \
+                                       {"src": _unicode_decode(src, encoding=encoding),
+                                       "dest": _unicode_decode(dest, encoding=encoding)}, noiselevel=-1)
+                               writemsg("!!! %s\n" % a, noiselevel=-1)
+                               return None # failure
+               try:
+                       if didcopy:
+                               if stat.S_ISLNK(sstat[stat.ST_MODE]):
+                                       lchown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+                               else:
+                                       os.chown(dest,sstat[stat.ST_UID],sstat[stat.ST_GID])
+                               os.chmod(dest, stat.S_IMODE(sstat[stat.ST_MODE])) # Sticky is reset on chown
+                               os.unlink(src)
+               except SystemExit as e:
+                       raise
+               except Exception as e:
+                       print(_("!!! Failed to chown/chmod/unlink in movefile()"))
+                       print("!!!",dest)
+                       print("!!!",e)
+                       return None
+
+       # Always use stat_obj[stat.ST_MTIME] for the integral timestamp which
+       # is returned, since the stat_obj.st_mtime float attribute rounds *up*
+       # if the nanosecond part of the timestamp is 999999881 ns or greater.
+       try:
+               if hardlinked:
+                       newmtime = os.stat(dest)[stat.ST_MTIME]
+               else:
+                       # Note: It is not possible to preserve nanosecond precision
+                       # (supported in POSIX.1-2008 via utimensat) with the IEEE 754
+                       # double precision float which only has a 53 bit significand.
+                       if newmtime is not None:
+                               os.utime(dest, (newmtime, newmtime))
+                       else:
+                               newmtime = sstat[stat.ST_MTIME]
+                               if renamefailed:
+                                       # If rename succeeded then timestamps are automatically
+                                       # preserved with complete precision because the source
+                                       # and destination inode are the same. Otherwise, round
+                                       # down to the nearest whole second since python's float
+                                       # st_mtime cannot be used to preserve the st_mtim.tv_nsec
+                                       # field with complete precision. Note that we have to use
+                                       # stat_obj[stat.ST_MTIME] here because the float
+                                       # stat_obj.st_mtime rounds *up* sometimes.
+                                       os.utime(dest, (newmtime, newmtime))
+       except OSError:
+               # The utime can fail here with EPERM even though the move succeeded.
+               # Instead of failing, use stat to return the mtime if possible.
+               try:
+                       newmtime = os.stat(dest)[stat.ST_MTIME]
+               except OSError as e:
+                       writemsg(_("!!! Failed to stat in movefile()\n"), noiselevel=-1)
+                       writemsg("!!! %s\n" % dest, noiselevel=-1)
+                       writemsg("!!! %s\n" % str(e), noiselevel=-1)
+                       return None
+
+       if bsd_chflags:
+               # Restore the flags we saved before moving
+               if pflags:
+                       bsd_chflags.chflags(os.path.dirname(dest), pflags)
+
+       return newmtime