From a3b4c9399afedb345054124a31e422cf2940d222 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Sat, 27 Feb 2010 19:32:48 +0000 Subject: [PATCH] Merged from trunk -r15460:15465 | 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 | 1050 +---------------- pym/portage/dbapi/__init__.py | 4 +- pym/portage/dbapi/vartree.py | 3 +- pym/portage/{dep.py => dep/__init__.py} | 0 pym/portage/dep/dep_check.py | 634 ++++++++++ .../package/ebuild/getmaskingreason.py | 78 ++ .../package/ebuild/getmaskingstatus.py | 154 +++ pym/portage/util/movefile.py | 234 ++++ 8 files changed, 1108 insertions(+), 1049 deletions(-) rename pym/portage/{dep.py => dep/__init__.py} (100%) create mode 100644 pym/portage/dep/dep_check.py create mode 100644 pym/portage/package/ebuild/getmaskingreason.py create mode 100644 pym/portage/package/ebuild/getmaskingstatus.py create mode 100644 pym/portage/util/movefile.py diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 5edbf843f..ad372fd79 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -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', diff --git a/pym/portage/dbapi/__init__.py b/pym/portage/dbapi/__init__.py index dcd933f62..54d3877b2 100644 --- a/pym/portage/dbapi/__init__.py +++ b/pym/portage/dbapi/__init__.py @@ -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))) diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index b2983a568..0cbcd88b1 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -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.py b/pym/portage/dep/__init__.py similarity index 100% rename from pym/portage/dep.py rename to pym/portage/dep/__init__.py diff --git a/pym/portage/dep/dep_check.py b/pym/portage/dep/dep_check.py new file mode 100644 index 000000000..1bc1e40dc --- /dev/null +++ b/pym/portage/dep/dep_check.py @@ -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 index 000000000..d0c816439 --- /dev/null +++ b/pym/portage/package/ebuild/getmaskingreason.py @@ -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 index 000000000..bfe92d824 --- /dev/null +++ b/pym/portage/package/ebuild/getmaskingstatus.py @@ -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 index 000000000..23110ab05 --- /dev/null +++ b/pym/portage/util/movefile.py @@ -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 -- 2.26.2