blob: fbc797a334d79843376cb038cdddd7c766bc3d96 [file] [log] [blame]
#!/usr/bin/python -O
# Copyright 1999-2006 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Id$
import sys
# This block ensures that ^C interrupts are handled quietly.
try:
import signal
def exithandler(signum,frame):
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
sys.exit(1)
signal.signal(signal.SIGINT, exithandler)
signal.signal(signal.SIGTERM, exithandler)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except KeyboardInterrupt:
sys.exit(1)
import os, stat
os.environ["PORTAGE_LEGACY_GLOBALS"] = "false"
try:
import portage
except ImportError:
from os import path as osp
sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
import portage
del os.environ["PORTAGE_LEGACY_GLOBALS"]
from portage import digraph
import emergehelp, xpak, commands, errno, re, socket, time, types
import output
from output import blue, bold, colorize, darkblue, darkgreen, darkred, green, \
havecolor, nc_len, nocolor, red, teal, turquoise, white, xtermTitle, \
xtermTitleReset, yellow
from output import create_color_func
good = create_color_func("GOOD")
bad = create_color_func("BAD")
# white looks bad on terminals with white background
from output import bold as white
import portage_const
import portage_dep
portage_dep._dep_check_strict = True
import portage_util
import portage_locks
import portage_exception
from portage_data import secpass
from portage_util import normalize_path as normpath
from portage_util import writemsg
if not hasattr(__builtins__, "set"):
from sets import Set as set
from itertools import chain, izip
from UserDict import DictMixin
try:
import cPickle
except ImportError:
import pickle as cPickle
class stdout_spinner(object):
scroll_msgs = [
"Gentoo Rocks ("+os.uname()[0]+")",
"Thank you for using Gentoo. :)",
"Are you actually trying to read this?",
"How many times have you stared at this?",
"We are generating the cache right now",
"You are paying too much attention.",
"A theory is better than its explanation.",
"Phasers locked on target, Captain.",
"Thrashing is just virtual crashing.",
"To be is to program.",
"Real Users hate Real Programmers.",
"When all else fails, read the instructions.",
"Functionality breeds Contempt.",
"The future lies ahead.",
"3.1415926535897932384626433832795028841971694",
"Sometimes insanity is the only alternative.",
"Inaccuracy saves a world of explanation.",
]
twirl_sequence = "/-\\|/-\\|/-\\|/-\\|\\-/|\\-/|\\-/|\\-/|"
def __init__(self):
self.spinpos = 0
self.update = self.update_twirl
self.scroll_sequence = self.scroll_msgs[
int(time.time() * 100) % len(self.scroll_msgs)]
self.last_update = 0
self.min_display_latency = 0.05
def _return_early(self):
"""
Flushing ouput to the tty too frequently wastes cpu time. Therefore,
each update* method should return without doing any output when this
method returns True.
"""
cur_time = time.time()
if cur_time - self.last_update < self.min_display_latency:
return True
self.last_update = cur_time
return False
def update_basic(self):
self.spinpos = (self.spinpos + 1) % 500
if self._return_early():
return
if (self.spinpos % 100) == 0:
if self.spinpos == 0:
sys.stdout.write(". ")
else:
sys.stdout.write(".")
sys.stdout.flush()
def update_scroll(self):
if self._return_early():
return
if(self.spinpos >= len(self.scroll_sequence)):
sys.stdout.write(darkgreen(" \b\b\b" + self.scroll_sequence[
len(self.scroll_sequence) - 1 - (self.spinpos % len(self.scroll_sequence))]))
else:
sys.stdout.write(green("\b " + self.scroll_sequence[self.spinpos]))
sys.stdout.flush()
self.spinpos = (self.spinpos + 1) % (2 * len(self.scroll_sequence))
def update_twirl(self):
self.spinpos = (self.spinpos + 1) % len(self.twirl_sequence)
if self._return_early():
return
sys.stdout.write("\b\b " + self.twirl_sequence[self.spinpos])
sys.stdout.flush()
def update_quiet(self):
return
def userquery(prompt, responses=None, colours=None):
"""Displays a prompt and a set of responses, then waits for a response
which is checked against the responses and the first to match is
returned. An empty response will match the first value in responses. The
input buffer is *not* cleared prior to the prompt!
prompt: a String.
responses: a List of Strings.
colours: a List of Functions taking and returning a String, used to
process the responses for display. Typically these will be functions
like red() but could be e.g. lambda x: "DisplayString".
If responses is omitted, defaults to ["Yes", "No"], [green, red].
If only colours is omitted, defaults to [bold, ...].
Returns a member of the List responses. (If called without optional
arguments, returns "Yes" or "No".)
KeyboardInterrupt is converted to SystemExit to avoid tracebacks being
printed."""
if responses is None:
responses = ["Yes", "No"]
colours = [
create_color_func("PROMPT_CHOICE_DEFAULT"),
create_color_func("PROMPT_CHOICE_OTHER")
]
elif colours is None:
colours=[bold]
colours=(colours*len(responses))[:len(responses)]
print bold(prompt),
try:
while True:
response=raw_input("["+"/".join([colours[i](responses[i]) for i in range(len(responses))])+"] ")
for key in responses:
# An empty response will match the first value in responses.
if response.upper()==key[:len(response)].upper():
return key
print "Sorry, response '%s' not understood." % response,
except (EOFError, KeyboardInterrupt):
print "Interrupted."
sys.exit(1)
actions=[
"clean", "config", "depclean",
"info", "metadata",
"prune", "regen", "search",
"sync", "system", "unmerge", "world",
]
options=[
"--ask", "--alphabetical",
"--buildpkg", "--buildpkgonly",
"--changelog", "--columns",
"--complete-graph",
"--debug", "--deep",
"--digest",
"--emptytree",
"--fetchonly", "--fetch-all-uri",
"--getbinpkg", "--getbinpkgonly",
"--help", "--ignore-default-opts",
"--noconfmem",
"--newuse", "--nocolor",
"--nodeps", "--noreplace",
"--nospinner", "--oneshot",
"--onlydeps", "--pretend",
"--quiet", "--resume",
"--searchdesc", "--selective",
"--skipfirst",
"--tree",
"--update",
"--usepkg", "--usepkgonly",
"--verbose", "--version"
]
shortmapping={
"1":"--oneshot",
"a":"--ask",
"b":"--buildpkg", "B":"--buildpkgonly",
"c":"--clean", "C":"--unmerge",
"d":"--debug", "D":"--deep",
"e":"--emptytree",
"f":"--fetchonly", "F":"--fetch-all-uri",
"g":"--getbinpkg", "G":"--getbinpkgonly",
"h":"--help",
"k":"--usepkg", "K":"--usepkgonly",
"l":"--changelog",
"n":"--noreplace", "N":"--newuse",
"o":"--onlydeps", "O":"--nodeps",
"p":"--pretend", "P":"--prune",
"q":"--quiet",
"s":"--search", "S":"--searchdesc",
"t":"--tree",
"u":"--update",
"v":"--verbose", "V":"--version"
}
def emergelog(xterm_titles, mystr, short_msg=None):
if xterm_titles:
if short_msg == None:
short_msg = mystr
if "HOSTNAME" in os.environ:
short_msg = os.environ["HOSTNAME"]+": "+short_msg
xtermTitle(short_msg)
try:
file_path = "/var/log/emerge.log"
mylogfile = open(file_path, "a")
portage_util.apply_secpass_permissions(file_path,
uid=portage.portage_uid, gid=portage.portage_gid,
mode=0660)
mylock = None
try:
mylock = portage_locks.lockfile(mylogfile)
# seek because we may have gotten held up by the lock.
# if so, we may not be positioned at the end of the file.
mylogfile.seek(0, 2)
mylogfile.write(str(time.time())[:10]+": "+mystr+"\n")
mylogfile.flush()
finally:
if mylock:
portage_locks.unlockfile(mylock)
mylogfile.close()
except (IOError,OSError,portage_exception.PortageException), e:
if secpass >= 1:
print >> sys.stderr, "emergelog():",e
def countdown(secs=5, doing="Starting"):
if secs:
print ">>> Waiting",secs,"seconds before starting..."
print ">>> (Control-C to abort)...\n"+doing+" in: ",
ticks=range(secs)
ticks.reverse()
for sec in ticks:
sys.stdout.write(colorize("UNMERGE_WARN", str(sec+1)+" "))
sys.stdout.flush()
time.sleep(1)
print
# formats a size given in bytes nicely
def format_size(mysize):
if type(mysize) not in [types.IntType,types.LongType]:
return str(mysize)
if 0 != mysize % 1024:
# Always round up to the next kB so that it doesn't show 0 kB when
# some small file still needs to be fetched.
mysize += 1024 - mysize % 1024
mystr=str(mysize/1024)
mycount=len(mystr)
while (mycount > 3):
mycount-=3
mystr=mystr[:mycount]+","+mystr[mycount:]
return mystr+" kB"
def getgccversion(chost):
"""
rtype: C{str}
return: the current in-use gcc version
"""
gcc_ver_command = 'gcc -dumpversion'
gcc_ver_prefix = 'gcc-'
gcc_not_found_error = red(
"!!! No gcc found. You probably need to 'source /etc/profile'\n" +
"!!! to update the environment of this terminal and possibly\n" +
"!!! other terminals also.\n"
)
mystatus, myoutput = commands.getstatusoutput("gcc-config -c")
if mystatus == os.EX_OK and myoutput.startswith(chost + "-"):
return myoutput.replace(chost + "-", gcc_ver_prefix, 1)
mystatus, myoutput = commands.getstatusoutput(
chost + "-" + gcc_ver_command)
if mystatus == os.EX_OK:
return gcc_ver_prefix + myoutput
mystatus, myoutput = commands.getstatusoutput(gcc_ver_command)
if mystatus == os.EX_OK:
return gcc_ver_prefix + myoutput
portage.writemsg(gcc_not_found_error, noiselevel=-1)
return "[unavailable]"
def getportageversion(portdir, target_root, profile, chost, vardb):
profilever = "unavailable"
if profile:
realpath = os.path.realpath(profile)
basepath = os.path.realpath(os.path.join(portdir, "profiles"))
if realpath.startswith(basepath):
profilever = realpath[1 + len(basepath):]
else:
try:
profilever = "!" + os.readlink(profile)
except (OSError):
pass
del realpath, basepath
libcver=[]
libclist = vardb.match("virtual/libc")
libclist += vardb.match("virtual/glibc")
libclist = portage_util.unique_array(libclist)
for x in libclist:
xs=portage.catpkgsplit(x)
if libcver:
libcver+=","+"-".join(xs[1:])
else:
libcver="-".join(xs[1:])
if libcver==[]:
libcver="unavailable"
gccver = getgccversion(chost)
unameout=os.uname()[2]+" "+os.uname()[4]
return "Portage " + portage.VERSION +" ("+profilever+", "+gccver+", "+libcver+", "+unameout+")"
def create_depgraph_params(myopts, myaction):
#configure emerge engine parameters
#
# self: include _this_ package regardless of if it is merged.
# selective: exclude the package if it is merged
# recurse: go into the dependencies
# deep: go into the dependencies of already merged packages
# empty: pretend nothing is merged
# complete: completely account for all known dependencies
myparams = set(["recurse"])
if "--update" in myopts or \
"--newuse" in myopts or \
"--reinstall" in myopts or \
"--noreplace" in myopts or \
myaction in ("system", "world"):
myparams.add("selective")
if "--emptytree" in myopts:
myparams.add("empty")
myparams.discard("selective")
if "--nodeps" in myopts:
myparams.discard("recurse")
if "--deep" in myopts:
myparams.add("deep")
if "--complete-graph" in myopts:
myparams.add("complete")
return myparams
# search functionality
class search:
#
# class constants
#
VERSION_SHORT=1
VERSION_RELEASE=2
#
# public interface
#
def __init__(self, settings, trees, spinner, searchdesc,
verbose, usepkg, usepkgonly):
"""Searches the available and installed packages for the supplied search key.
The list of available and installed packages is created at object instantiation.
This makes successive searches faster."""
self.settings = settings
self.vartree = trees["vartree"]
self.spinner = spinner
self.verbose = verbose
self.searchdesc = searchdesc
def fake_portdb():
pass
self.portdb = fake_portdb
for attrib in ("aux_get", "cp_all",
"xmatch", "findname", "getfetchlist"):
setattr(fake_portdb, attrib, getattr(self, "_"+attrib))
self._dbs = []
portdb = trees["porttree"].dbapi
bindb = trees["bintree"].dbapi
vardb = trees["vartree"].dbapi
if not usepkgonly and portdb._have_root_eclass_dir:
self._dbs.append(portdb)
if (usepkg or usepkgonly) and bindb.cp_all():
self._dbs.append(bindb)
self._dbs.append(vardb)
self._portdb = portdb
def _cp_all(self):
cp_all = set()
for db in self._dbs:
cp_all.update(db.cp_all())
return list(sorted(cp_all))
def _aux_get(self, *args, **kwargs):
for db in self._dbs:
try:
return db.aux_get(*args, **kwargs)
except KeyError:
pass
raise
def _findname(self, *args, **kwargs):
for db in self._dbs:
if db is not self._portdb:
# We don't want findname to return anything
# unless it's an ebuild in a portage tree.
# Otherwise, it's already built and we don't
# care about it.
continue
func = getattr(db, "findname", None)
if func:
value = func(*args, **kwargs)
if value:
return value
return None
def _getfetchlist(self, *args, **kwargs):
for db in self._dbs:
func = getattr(db, "getfetchlist", None)
if func:
value = func(*args, **kwargs)
if value:
return value
return [], []
def _visible(self, db, cpv, metadata):
installed = db is self.vartree.dbapi
built = installed or db is not self._portdb
pkg_type = "ebuild"
if installed:
pkg_type = "installed"
elif built:
pkg_type = "binary"
return visible(self.settings,
Package(type_name=pkg_type, root=self.settings["ROOT"],
cpv=cpv, built=built, installed=installed, metadata=metadata))
def _xmatch(self, level, atom):
"""
This method does not expand old-style virtuals because it
is restricted to returning matches for a single ${CATEGORY}/${PN}
and old-style virual matches unreliable for that when querying
multiple package databases. If necessary, old-style virtuals
can be performed on atoms prior to calling this method.
"""
cp = portage.dep_getkey(atom)
if level == "match-all":
matches = set()
for db in self._dbs:
if hasattr(db, "xmatch"):
matches.update(db.xmatch(level, atom))
else:
matches.update(db.match(atom))
result = list(x for x in matches if portage.cpv_getkey(x) == cp)
db._cpv_sort_ascending(result)
elif level == "match-visible":
matches = set()
for db in self._dbs:
if hasattr(db, "xmatch"):
matches.update(db.xmatch(level, atom))
else:
db_keys = list(db._aux_cache_keys)
for cpv in db.match(atom):
metadata = dict(izip(db_keys,
db.aux_get(cpv, db_keys)))
if not self._visible(db, cpv, metadata):
continue
matches.add(cpv)
result = list(x for x in matches if portage.cpv_getkey(x) == cp)
db._cpv_sort_ascending(result)
elif level == "bestmatch-visible":
result = None
for db in self._dbs:
if hasattr(db, "xmatch"):
cpv = db.xmatch("bestmatch-visible", atom)
if not cpv or portage.cpv_getkey(cpv) != cp:
continue
if not result or cpv == portage.best([cpv, result]):
result = cpv
else:
db_keys = list(db._aux_cache_keys)
# break out of this loop with highest visible
# match, checked in descending order
for cpv in reversed(db.match(atom)):
if portage.cpv_getkey(cpv) != cp:
continue
metadata = dict(izip(db_keys,
db.aux_get(cpv, db_keys)))
if not self._visible(db, cpv, metadata):
continue
if not result or cpv == portage.best([cpv, result]):
result = cpv
break
else:
raise NotImplementedError(level)
return result
def execute(self,searchkey):
"""Performs the search for the supplied search key"""
match_category = 0
self.searchkey=searchkey
self.packagematches = []
if self.searchdesc:
self.searchdesc=1
self.matches = {"pkg":[], "desc":[]}
else:
self.searchdesc=0
self.matches = {"pkg":[]}
print "Searching... ",
regexsearch = False
if self.searchkey.startswith('%'):
regexsearch = True
self.searchkey = self.searchkey[1:]
if self.searchkey.startswith('@'):
match_category = 1
self.searchkey = self.searchkey[1:]
if regexsearch:
self.searchre=re.compile(self.searchkey,re.I)
else:
self.searchre=re.compile(re.escape(self.searchkey), re.I)
for package in self.portdb.cp_all():
self.spinner.update()
if match_category:
match_string = package[:]
else:
match_string = package.split("/")[-1]
masked=0
if self.searchre.search(match_string):
if not self.portdb.xmatch("match-visible", package):
masked=1
self.matches["pkg"].append([package,masked])
elif self.searchdesc: # DESCRIPTION searching
full_package = self.portdb.xmatch("bestmatch-visible", package)
if not full_package:
#no match found; we don't want to query description
full_package = portage.best(
self.portdb.xmatch("match-all", package))
if not full_package:
continue
else:
masked=1
try:
full_desc = self.portdb.aux_get(
full_package, ["DESCRIPTION"])[0]
except KeyError:
print "emerge: search: aux_get() failed, skipping"
continue
if self.searchre.search(full_desc):
self.matches["desc"].append([full_package,masked])
self.mlen=0
for mtype in self.matches:
self.matches[mtype].sort()
self.mlen += len(self.matches[mtype])
def output(self):
"""Outputs the results of the search."""
print "\b\b \n[ Results for search key : "+white(self.searchkey)+" ]"
print "[ Applications found : "+white(str(self.mlen))+" ]"
print " "
vardb = self.vartree.dbapi
for mtype in self.matches:
for match,masked in self.matches[mtype]:
if mtype=="pkg":
catpack=match
full_package = self.portdb.xmatch(
"bestmatch-visible", match)
if not full_package:
#no match found; we don't want to query description
masked=1
full_package = portage.best(
self.portdb.xmatch("match-all",match))
else:
full_package = match
match = portage.cpv_getkey(match)
if full_package:
try:
desc, homepage, license = self.portdb.aux_get(
full_package, ["DESCRIPTION","HOMEPAGE","LICENSE"])
except KeyError:
print "emerge: search: aux_get() failed, skipping"
continue
if masked:
print green("*")+" "+white(match)+" "+red("[ Masked ]")
else:
print green("*")+" "+white(match)
myversion = self.getVersion(full_package, search.VERSION_RELEASE)
mysum = [0,0]
file_size_str = None
mycat = match.split("/")[0]
mypkg = match.split("/")[1]
mycpv = match + "-" + myversion
myebuild = self.portdb.findname(mycpv)
if myebuild:
pkgdir = os.path.dirname(myebuild)
import portage_manifest as manifest
mf = manifest.Manifest(
pkgdir, self.settings["DISTDIR"])
fetchlist = self.portdb.getfetchlist(mycpv,
mysettings=self.settings, all=True)[1]
try:
mysum[0] = mf.getDistfilesSize(fetchlist)
except KeyError, e:
file_size_str = "Unknown (missing digest for %s)" % \
str(e)
available = False
for db in self._dbs:
if db is not vardb and \
db.cpv_exists(mycpv):
available = True
if not myebuild and hasattr(db, "bintree"):
myebuild = db.bintree.getname(mycpv)
try:
mysum[0] = os.stat(myebuild).st_size
except OSError:
myebuild = None
break
if myebuild and file_size_str is None:
mystr = str(mysum[0] / 1024)
mycount = len(mystr)
while (mycount > 3):
mycount -= 3
mystr = mystr[:mycount] + "," + mystr[mycount:]
file_size_str = mystr + " kB"
if self.verbose:
if available:
print " ", darkgreen("Latest version available:"),myversion
print " ", self.getInstallationStatus(mycat+'/'+mypkg)
if myebuild:
print " %s %s" % \
(darkgreen("Size of files:"), file_size_str)
print " ", darkgreen("Homepage:")+" ",homepage
print " ", darkgreen("Description:")+" ",desc
print " ", darkgreen("License:")+" ",license
print
print
#
# private interface
#
def getInstallationStatus(self,package):
installed_package = self.vartree.dep_bestmatch(package)
result = ""
version = self.getVersion(installed_package,search.VERSION_RELEASE)
if len(version) > 0:
result = darkgreen("Latest version installed:")+" "+version
else:
result = darkgreen("Latest version installed:")+" [ Not Installed ]"
return result
def getVersion(self,full_package,detail):
if len(full_package) > 1:
package_parts = portage.catpkgsplit(full_package)
if detail == search.VERSION_RELEASE and package_parts[3] != 'r0':
result = package_parts[2]+ "-" + package_parts[3]
else:
result = package_parts[2]
else:
result = ""
return result
#build our package digraph
def getlist(settings, mode):
if mode=="system":
mylines = settings.packages
elif mode=="world":
try:
file_path = os.path.join(settings["ROOT"], portage.WORLD_FILE)
myfile = open(file_path, "r")
mylines = myfile.readlines()
myfile.close()
except (OSError, IOError), e:
if e.errno == errno.ENOENT:
portage.writemsg("\n!!! World file does not exist: '%s'\n" % file_path)
mylines=[]
else:
raise
mynewlines=[]
for x in mylines:
myline=" ".join(x.split())
if not len(myline):
continue
elif myline[0]=="#":
continue
elif mode=="system":
if myline[0]!="*":
continue
myline=myline[1:]
mynewlines.append(myline.strip())
return mynewlines
def world_clean_package(vardb, cpv):
"""Remove a package from the world file when unmerged."""
world_set = WorldSet(vardb.settings)
world_set.lock()
world_set.load()
worldlist = list(world_set)
mykey = portage.cpv_getkey(cpv)
newworldlist = []
for x in worldlist:
if portage.dep_getkey(x) == mykey:
matches = vardb.match(x, use_cache=0)
if not matches:
#zap our world entry
pass
elif len(matches) == 1 and matches[0] == cpv:
#zap our world entry
pass
else:
#others are around; keep it.
newworldlist.append(x)
else:
#this doesn't match the package we're unmerging; keep it.
newworldlist.append(x)
world_set.clear()
world_set.update(newworldlist)
world_set.save()
world_set.unlock()
SETPREFIX = "@"
class SetConfig(object):
def __init__(self, settings, trees):
self.sets = {}
self.sets["world"] = WorldSet(settings)
self.sets["world"].load()
self.sets["system"] = SystemSet(settings)
def getSets(self):
return self.sets
def getSetAtoms(self, name):
return set(self.sets[name])
class InternalPackageSet(object):
def __init__(self, initial_atoms=None):
self._atoms = {}
if initial_atoms:
self.update(initial_atoms)
def clear(self):
self._atoms.clear()
def add(self, atom):
cp = portage.dep_getkey(atom)
cp_list = self._atoms.get(cp)
if cp_list is None:
cp_list = []
self._atoms[cp] = cp_list
if atom not in cp_list:
cp_list.append(atom)
def update(self, atoms):
for atom in atoms:
self.add(atom)
def __contains__(self, atom):
cp = portage.dep_getkey(atom)
if cp in self._atoms and atom in self._atoms[cp]:
return True
return False
def findAtomForPackage(self, cpv, metadata):
"""Return the best match for a given package from the arguments, or
None if there are no matches. This matches virtual arguments against
the PROVIDE metadata. This can raise an InvalidDependString exception
if an error occurs while parsing PROVIDE."""
cpv_slot = "%s:%s" % (cpv, metadata["SLOT"])
cp = portage.dep_getkey(cpv)
atoms = self._atoms.get(cp)
if atoms:
best_match = portage.best_match_to_list(cpv_slot, atoms)
if best_match:
return best_match
if not metadata["PROVIDE"]:
return None
provides = portage.flatten(portage_dep.use_reduce(
portage_dep.paren_reduce(metadata["PROVIDE"]),
uselist=metadata["USE"].split()))
for provide in provides:
provided_cp = portage.dep_getkey(provide)
atoms = self._atoms.get(provided_cp)
if atoms:
transformed_atoms = [atom.replace(provided_cp, cp) for atom in atoms]
best_match = portage.best_match_to_list(cpv_slot, transformed_atoms)
if best_match:
return atoms[transformed_atoms.index(best_match)]
return None
def iterAtomsForPackage(self, pkg):
"""
Find all matching atoms for a given package. This matches virtual
arguments against the PROVIDE metadata. This will raise an
InvalidDependString exception if PROVIDE is invalid.
"""
cpv_slot_list = ["%s:%s" % (pkg.cpv, pkg.metadata["SLOT"])]
cp = portage.cpv_getkey(pkg.cpv)
atoms = self._atoms.get(cp)
if atoms:
for atom in atoms:
if portage.match_from_list(atom, cpv_slot_list):
yield atom
if not pkg.metadata["PROVIDE"]:
return
provides = portage.flatten(portage_dep.use_reduce(
portage_dep.paren_reduce(pkg.metadata["PROVIDE"]),
uselist=pkg.metadata["USE"].split()))
for provide in provides:
provided_cp = portage.dep_getkey(provide)
atoms = self._atoms.get(provided_cp)
if atoms:
for atom in atoms:
if portage.match_from_list(atom.replace(provided_cp, cp),
cpv_slot_list):
yield atom
def __iter__(self):
for atoms in self._atoms.itervalues():
for atom in atoms:
yield atom
class SystemSet(InternalPackageSet):
def __init__(self, settings, **kwargs):
InternalPackageSet.__init__(self, **kwargs)
self.update(getlist(settings, "system"))
class WorldSet(InternalPackageSet):
def __init__(self, settings, **kwargs):
InternalPackageSet.__init__(self, **kwargs)
self.world_file = os.path.join(settings["ROOT"], portage.WORLD_FILE)
self._lock = None
def _ensure_dirs(self):
portage_util.ensure_dirs(os.path.dirname(self.world_file),
gid=portage.portage_gid, mode=02750, mask=02)
def load(self):
self.clear()
self.update(portage_util.grabfile_package(self.world_file))
def save(self):
self._ensure_dirs()
portage.write_atomic(self.world_file,
"\n".join(sorted(self)) + "\n")
def lock(self):
self._ensure_dirs()
self._lock = portage_locks.lockfile(self.world_file, wantnewlockfile=1)
def unlock(self):
portage_locks.unlockfile(self._lock)
self._lock = None
class RootConfig(object):
"""This is used internally by depgraph to track information about a
particular $ROOT."""
def __init__(self, settings, trees, setconfig):
self.trees = trees
self.settings = settings
self.root = self.settings["ROOT"]
self.setconfig = setconfig
self.sets = self.setconfig.getSets()
self.visible_pkgs = PackageVirtualDbapi(self.settings)
def create_world_atom(pkg_key, metadata, args_set, root_config):
"""Create a new atom for the world file if one does not exist. If the
argument atom is precise enough to identify a specific slot then a slot
atom will be returned. Atoms that are in the system set may also be stored
in world since system atoms can only match one slot while world atoms can
be greedy with respect to slots. Unslotted system packages will not be
stored in world."""
arg_atom = args_set.findAtomForPackage(pkg_key, metadata)
if not arg_atom:
return None
cp = portage.dep_getkey(arg_atom)
new_world_atom = cp
sets = root_config.sets
portdb = root_config.trees["porttree"].dbapi
vardb = root_config.trees["vartree"].dbapi
available_slots = set(portdb.aux_get(cpv, ["SLOT"])[0] \
for cpv in portdb.match(cp))
slotted = len(available_slots) > 1 or \
(len(available_slots) == 1 and "0" not in available_slots)
if not slotted:
# check the vdb in case this is multislot
available_slots = set(vardb.aux_get(cpv, ["SLOT"])[0] \
for cpv in vardb.match(cp))
slotted = len(available_slots) > 1 or \
(len(available_slots) == 1 and "0" not in available_slots)
if slotted and arg_atom != cp:
# If the user gave a specific atom, store it as a
# slot atom in the world file.
slot_atom = "%s:%s" % (cp, metadata["SLOT"])
# For USE=multislot, there are a couple of cases to
# handle here:
#
# 1) SLOT="0", but the real SLOT spontaneously changed to some
# unknown value, so just record an unslotted atom.
#
# 2) SLOT comes from an installed package and there is no
# matching SLOT in the portage tree.
#
# Make sure that the slot atom is available in either the
# portdb or the vardb, since otherwise the user certainly
# doesn't want the SLOT atom recorded in the world file
# (case 1 above). If it's only available in the vardb,
# the user may be trying to prevent a USE=multislot
# package from being removed by --depclean (case 2 above).
mydb = portdb
if not portdb.match(slot_atom):
# SLOT seems to come from an installed multislot package
mydb = vardb
# If there is no installed package matching the SLOT atom,
# it probably changed SLOT spontaneously due to USE=multislot,
# so just record an unslotted atom.
if vardb.match(slot_atom):
# Now verify that the argument is precise
# enough to identify a specific slot.
matches = mydb.match(arg_atom)
matched_slots = set()
for cpv in matches:
matched_slots.add(mydb.aux_get(cpv, ["SLOT"])[0])
if len(matched_slots) == 1:
new_world_atom = slot_atom
if new_world_atom == sets["world"].findAtomForPackage(pkg_key, metadata):
# Both atoms would be identical, so there's nothing to add.
return None
if not slotted:
# Unlike world atoms, system atoms are not greedy for slots, so they
# can't be safely excluded from world if they are slotted.
system_atom = sets["system"].findAtomForPackage(pkg_key, metadata)
if system_atom:
if not portage.dep_getkey(system_atom).startswith("virtual/"):
return None
# System virtuals aren't safe to exclude from world since they can
# match multiple old-style virtuals but only one of them will be
# pulled in by update or depclean.
providers = portdb.mysettings.getvirtuals().get(
portage.dep_getkey(system_atom))
if providers and len(providers) == 1 and providers[0] == cp:
return None
return new_world_atom
def filter_iuse_defaults(iuse):
for flag in iuse:
if flag.startswith("+") or flag.startswith("-"):
yield flag[1:]
else:
yield flag
class SlotObject(object):
__slots__ = ("__weakref__",)
def __init__(self, **kwargs):
classes = [self.__class__]
while classes:
c = classes.pop()
if c is SlotObject:
continue
classes.extend(c.__bases__)
slots = getattr(c, "__slots__", None)
if not slots:
continue
for myattr in slots:
myvalue = kwargs.get(myattr, None)
setattr(self, myattr, myvalue)
class AbstractDepPriority(SlotObject):
__slots__ = ("buildtime", "runtime", "runtime_post")
def __lt__(self, other):
return self.__int__() < other
def __le__(self, other):
return self.__int__() <= other
def __eq__(self, other):
return self.__int__() == other
def __ne__(self, other):
return self.__int__() != other
def __gt__(self, other):
return self.__int__() > other
def __ge__(self, other):
return self.__int__() >= other
def copy(self):
import copy
return copy.copy(self)
class DepPriority(AbstractDepPriority):
"""
This class generates an integer priority level based of various
attributes of the dependency relationship. Attributes can be assigned
at any time and the new integer value will be generated on calls to the
__int__() method. Rich comparison operators are supported.
The boolean attributes that affect the integer value are "satisfied",
"buildtime", "runtime", and "system". Various combinations of
attributes lead to the following priority levels:
Combination of properties Priority Category
not satisfied and buildtime 0 HARD
not satisfied and runtime -1 MEDIUM
not satisfied and runtime_post -2 MEDIUM_SOFT
satisfied and buildtime and rebuild -3 SOFT
satisfied and buildtime -4 SOFT
satisfied and runtime -5 SOFT
satisfied and runtime_post -6 SOFT
(none of the above) -6 SOFT
Several integer constants are defined for categorization of priority
levels:
MEDIUM The upper boundary for medium dependencies.
MEDIUM_SOFT The upper boundary for medium-soft dependencies.
SOFT The upper boundary for soft dependencies.
MIN The lower boundary for soft dependencies.
"""
__slots__ = ("satisfied", "rebuild")
MEDIUM = -1
MEDIUM_SOFT = -2
SOFT = -3
MIN = -6
def __int__(self):
if not self.satisfied:
if self.buildtime:
return 0
if self.runtime:
return -1
if self.runtime_post:
return -2
if self.buildtime:
if self.rebuild:
return -3
return -4
if self.runtime:
return -5
if self.runtime_post:
return -6
return -6
def __str__(self):
myvalue = self.__int__()
if myvalue > self.MEDIUM:
return "hard"
if myvalue > self.MEDIUM_SOFT:
return "medium"
if myvalue > self.SOFT:
return "medium-soft"
return "soft"
class BlockerDepPriority(DepPriority):
__slots__ = ()
def __int__(self):
return 0
BlockerDepPriority.instance = BlockerDepPriority()
class UnmergeDepPriority(AbstractDepPriority):
__slots__ = ()
"""
Combination of properties Priority Category
runtime 0 HARD
runtime_post -1 HARD
buildtime -2 SOFT
(none of the above) -2 SOFT
"""
MAX = 0
SOFT = -2
MIN = -2
def __int__(self):
if self.runtime:
return 0
if self.runtime_post:
return -1
if self.buildtime:
return -2
return -2
def __str__(self):
myvalue = self.__int__()
if myvalue > self.SOFT:
return "hard"
return "soft"
class FakeVartree(portage.vartree):
"""This is implements an in-memory copy of a vartree instance that provides
all the interfaces required for use by the depgraph. The vardb is locked
during the constructor call just long enough to read a copy of the
installed package information. This allows the depgraph to do it's
dependency calculations without holding a lock on the vardb. It also
allows things like vardb global updates to be done in memory so that the
user doesn't necessarily need write access to the vardb in cases where
global updates are necessary (updates are performed when necessary if there
is not a matching ebuild in the tree)."""
def __init__(self, real_vartree, portdb,
db_keys, pkg_cache, acquire_lock=1):
self.root = real_vartree.root
self.settings = real_vartree.settings
mykeys = db_keys[:]
for required_key in ("COUNTER", "SLOT"):
if required_key not in mykeys:
mykeys.append(required_key)
self._pkg_cache = pkg_cache
self.dbapi = PackageVirtualDbapi(real_vartree.settings)
vdb_path = os.path.join(self.root, portage.VDB_PATH)
try:
# At least the parent needs to exist for the lock file.
portage_util.ensure_dirs(vdb_path)
except portage_exception.PortageException:
pass
vdb_lock = None
try:
if acquire_lock and os.access(vdb_path, os.W_OK):
vdb_lock = portage_locks.lockdir(vdb_path)
real_dbapi = real_vartree.dbapi
slot_counters = {}
for cpv in real_dbapi.cpv_all():
cache_key = ("installed", self.root, cpv, "nomerge")
pkg = self._pkg_cache.get(cache_key)
if pkg is not None:
metadata = pkg.metadata
else:
metadata = dict(izip(mykeys, real_dbapi.aux_get(cpv, mykeys)))
myslot = metadata["SLOT"]
mycp = portage.dep_getkey(cpv)
myslot_atom = "%s:%s" % (mycp, myslot)
try:
mycounter = long(metadata["COUNTER"])
except ValueError:
mycounter = 0
metadata["COUNTER"] = str(mycounter)
other_counter = slot_counters.get(myslot_atom, None)
if other_counter is not None:
if other_counter > mycounter:
continue
slot_counters[myslot_atom] = mycounter
if pkg is None:
pkg = Package(built=True, cpv=cpv,
installed=True, metadata=metadata,
root=self.root, type_name="installed")
self._pkg_cache[pkg] = pkg
self.dbapi.cpv_inject(pkg)
real_dbapi.flush_cache()
finally:
if vdb_lock:
portage_locks.unlockdir(vdb_lock)
# Populate the old-style virtuals using the cached values.
if not self.settings.treeVirtuals:
self.settings.treeVirtuals = portage_util.map_dictlist_vals(
portage.getCPFromCPV, self.get_all_provides())
# Intialize variables needed for lazy cache pulls of the live ebuild
# metadata. This ensures that the vardb lock is released ASAP, without
# being delayed in case cache generation is triggered.
self._aux_get = self.dbapi.aux_get
self.dbapi.aux_get = self._aux_get_wrapper
self._match = self.dbapi.match
self.dbapi.match = self._match_wrapper
self._aux_get_history = set()
self._portdb_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
self._portdb = portdb
self._global_updates = None
def _match_wrapper(self, cpv, use_cache=1):
"""
Make sure the metadata in Package instances gets updated for any
cpv that is returned from a match() call, since the metadata can
be accessed directly from the Package instance instead of via
aux_get().
"""
matches = self._match(cpv, use_cache=use_cache)
for cpv in matches:
if cpv in self._aux_get_history:
continue
self._aux_get_wrapper(cpv, [])
return matches
def _aux_get_wrapper(self, pkg, wants):
if pkg in self._aux_get_history:
return self._aux_get(pkg, wants)
self._aux_get_history.add(pkg)
try:
# Use the live ebuild metadata if possible.
live_metadata = dict(izip(self._portdb_keys,
self._portdb.aux_get(pkg, self._portdb_keys)))
self.dbapi.aux_update(pkg, live_metadata)
except (KeyError, portage_exception.PortageException):
if self._global_updates is None:
self._global_updates = \
grab_global_updates(self._portdb.porttree_root)
perform_global_updates(
pkg, self.dbapi, self._global_updates)
return self._aux_get(pkg, wants)
def grab_global_updates(portdir):
from portage_update import grab_updates, parse_updates
updpath = os.path.join(portdir, "profiles", "updates")
try:
rawupdates = grab_updates(updpath)
except portage_exception.DirectoryNotFound:
rawupdates = []
upd_commands = []
for mykey, mystat, mycontent in rawupdates:
commands, errors = parse_updates(mycontent)
upd_commands.extend(commands)
return upd_commands
def perform_global_updates(mycpv, mydb, mycommands):
from portage_update import update_dbentries
aux_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
aux_dict = dict(izip(aux_keys, mydb.aux_get(mycpv, aux_keys)))
updates = update_dbentries(mycommands, aux_dict)
if updates:
mydb.aux_update(mycpv, updates)
def visible(pkgsettings, pkg):
"""
Check if a package is visible. This can raise an InvalidDependString
exception if LICENSE is invalid.
TODO: optionally generate a list of masking reasons
@rtype: Boolean
@returns: True if the package is visible, False otherwise.
"""
if not pkg.metadata["SLOT"]:
return False
if pkg.built and not pkg.installed:
pkg_chost = pkg.metadata.get("CHOST")
if pkg_chost and pkg_chost != pkgsettings["CHOST"]:
return False
if not portage.eapi_is_supported(pkg.metadata["EAPI"]):
return False
if not pkg.installed and \
pkgsettings._getMissingKeywords(pkg.cpv, pkg.metadata):
return False
if pkgsettings._getMaskAtom(pkg.cpv, pkg.metadata):
return False
if pkgsettings._getProfileMaskAtom(pkg.cpv, pkg.metadata):
return False
return True
def get_masking_status(pkg, pkgsettings, root_config):
mreasons = portage.getmaskingstatus(
pkg, settings=pkgsettings,
portdb=root_config.trees["porttree"].dbapi)
if pkg.built and not pkg.installed:
pkg_chost = pkg.metadata.get("CHOST")
if pkg_chost and pkg_chost != pkgsettings["CHOST"]:
mreasons.append("CHOST: %s" % \
pkg.metadata["CHOST"])
if not pkg.metadata["SLOT"]:
mreasons.append("invalid: SLOT is undefined")
return mreasons
def get_mask_info(root_config, cpv, pkgsettings,
db, pkg_type, built, installed, db_keys):
eapi_masked = False
try:
metadata = dict(izip(db_keys,
db.aux_get(cpv, db_keys)))
except KeyError:
metadata = None
if metadata and not built:
pkgsettings.setcpv(cpv, mydb=metadata)
metadata["USE"] = pkgsettings["PORTAGE_USE"]
if metadata is None:
mreasons = ["corruption"]
else:
pkg = Package(type_name=pkg_type, root=root_config.root,
cpv=cpv, built=built, installed=installed, metadata=metadata)
mreasons = get_masking_status(pkg, pkgsettings, root_config)
return metadata, mreasons
def show_masked_packages(masked_packages):
shown_licenses = set()
shown_comments = set()
# Maybe there is both an ebuild and a binary. Only
# show one of them to avoid redundant appearance.
shown_cpvs = set()
have_eapi_mask = False
for (root_config, pkgsettings, cpv,
metadata, mreasons) in masked_packages:
if cpv in shown_cpvs:
continue
shown_cpvs.add(cpv)
comment, filename = None, None
if "package.mask" in mreasons:
comment, filename = \
portage.getmaskingreason(
cpv, metadata=metadata,
settings=pkgsettings,
portdb=root_config.trees["porttree"].dbapi,
return_location=True)
missing_licenses = []
if metadata:
if not portage.eapi_is_supported(metadata["EAPI"]):
have_eapi_mask = True
print "- "+cpv+" (masked by: "+", ".join(mreasons)+")"
if comment and comment not in shown_comments:
print filename+":"
print comment
shown_comments.add(comment)
return have_eapi_mask
class Task(SlotObject):
__slots__ = ("_hash_key", "_hash_value")
def _get_hash_key(self):
hash_key = getattr(self, "_hash_key", None)
if hash_key is None:
raise NotImplementedError(self)
return hash_key
def __eq__(self, other):
return self._get_hash_key() == other
def __ne__(self, other):
return self._get_hash_key() != other
def __hash__(self):
hash_value = getattr(self, "_hash_value", None)
if hash_value is None:
self._hash_value = hash(self._get_hash_key())
return self._hash_value
def __len__(self):
return len(self._get_hash_key())
def __getitem__(self, key):
return self._get_hash_key()[key]
def __iter__(self):
return iter(self._get_hash_key())
def __contains__(self, key):
return key in self._get_hash_key()
def __str__(self):
return str(self._get_hash_key())
class Blocker(Task):
__slots__ = ("root", "atom", "cp", "satisfied")
def __init__(self, **kwargs):
Task.__init__(self, **kwargs)
self.cp = portage.dep_getkey(self.atom)
def _get_hash_key(self):
hash_key = getattr(self, "_hash_key", None)
if hash_key is None:
self._hash_key = \
("blocks", self.root, self.atom)
return self._hash_key
class Package(Task):
__slots__ = ("built", "cpv", "depth",
"installed", "metadata", "onlydeps", "operation",
"root", "type_name",
"category", "cp", "cpv_slot", "pf", "pv_split", "slot_atom")
metadata_keys = [
"CHOST", "COUNTER", "DEPEND", "EAPI", "IUSE", "KEYWORDS",
"LICENSE", "PDEPEND", "PROVIDE", "RDEPEND",
"repository", "RESTRICT", "SLOT", "USE"]
def __init__(self, **kwargs):
Task.__init__(self, **kwargs)
self.cp = portage.cpv_getkey(self.cpv)
self.slot_atom = "%s:%s" % (self.cp, self.metadata["SLOT"])
self.cpv_slot = "%s:%s" % (self.cpv, self.metadata["SLOT"])
self.category, self.pf = portage.catsplit(self.cpv)
self.pv_split = portage.catpkgsplit(self.cpv)[1:]
def _get_hash_key(self):
hash_key = getattr(self, "_hash_key", None)
if hash_key is None:
if self.operation is None:
self.operation = "merge"
if self.onlydeps or self.installed:
self.operation = "nomerge"
self._hash_key = \
(self.type_name, self.root, self.cpv, self.operation)
return self._hash_key
def __lt__(self, other):
if other.cp != self.cp:
return False
if portage.pkgcmp(self.pv_split, other.pv_split) < 0:
return True
return False
def __le__(self, other):
if other.cp != self.cp:
return False
if portage.pkgcmp(self.pv_split, other.pv_split) <= 0:
return True
return False
def __gt__(self, other):
if other.cp != self.cp:
return False
if portage.pkgcmp(self.pv_split, other.pv_split) > 0:
return True
return False
def __ge__(self, other):
if other.cp != self.cp:
return False
if portage.pkgcmp(self.pv_split, other.pv_split) >= 0:
return True
return False
class DependencyArg(object):
def __init__(self, arg=None, root_config=None):
self.arg = arg
self.root_config = root_config
def __str__(self):
return self.arg
class AtomArg(DependencyArg):
def __init__(self, atom=None, **kwargs):
DependencyArg.__init__(self, **kwargs)
self.atom = atom
self.set = (self.atom, )
class PackageArg(DependencyArg):
def __init__(self, package=None, **kwargs):
DependencyArg.__init__(self, **kwargs)
self.package = package
self.atom = "=" + package.cpv
self.set = (self.atom, )
class SetArg(DependencyArg):
def __init__(self, set=None, **kwargs):
DependencyArg.__init__(self, **kwargs)
self.set = set
self.name = self.arg[len(SETPREFIX):]
class Dependency(SlotObject):
__slots__ = ("atom", "blocker", "depth",
"parent", "onlydeps", "priority", "root")
def __init__(self, **kwargs):
SlotObject.__init__(self, **kwargs)
if self.priority is None:
self.priority = DepPriority()
if self.depth is None:
self.depth = 0
class BlockerCache(DictMixin):
"""This caches blockers of installed packages so that dep_check does not
have to be done for every single installed package on every invocation of
emerge. The cache is invalidated whenever it is detected that something
has changed that might alter the results of dep_check() calls:
1) the set of installed packages (including COUNTER) has changed
2) the old-style virtuals have changed
"""
class BlockerData(object):
__slots__ = ("__weakref__", "atoms", "counter")
def __init__(self, counter, atoms):
self.counter = counter
self.atoms = atoms
def __init__(self, myroot, vardb):
self._vardb = vardb
self._virtuals = vardb.settings.getvirtuals()
self._cache_filename = os.path.join(myroot,
portage.CACHE_PATH.lstrip(os.path.sep), "vdb_blockers.pickle")
self._cache_version = "1"
self._cache_data = None
self._modified = False
self._load()
def _load(self):
try:
f = open(self._cache_filename)
mypickle = cPickle.Unpickler(f)
mypickle.find_global = None
self._cache_data = mypickle.load()
f.close()
del f
except (IOError, OSError, EOFError, cPickle.UnpicklingError):
pass
cache_valid = self._cache_data and \
isinstance(self._cache_data, dict) and \
self._cache_data.get("version") == self._cache_version and \
isinstance(self._cache_data.get("blockers"), dict)
if cache_valid:
# Validate all the atoms and counters so that
# corruption is detected as soon as possible.
invalid_items = set()
for k, v in self._cache_data["blockers"].iteritems():
if not isinstance(k, basestring):
invalid_items.add(k)
continue
try:
if portage.catpkgsplit(k) is None:
invalid_items.add(k)
continue
except portage_exception.InvalidData:
invalid_items.add(k)
continue
if not isinstance(v, tuple) or \
len(v) != 2:
invalid_items.add(k)
continue
counter, atoms = v
if not isinstance(counter, (int, long)):
invalid_items.add(k)
continue
if not isinstance(atoms, list):
invalid_items.add(k)
continue
invalid_atom = False
for atom in atoms:
if not isinstance(atom, basestring):
invalid_atom = True
break
if atom[:1] != "!" or \
not portage.isvalidatom(
atom, allow_blockers=True):
invalid_atom = True
break
if invalid_atom:
invalid_items.add(k)
continue
for k in invalid_items:
del self._cache_data["blockers"][k]
if not self._cache_data["blockers"]:
cache_valid = False
if not cache_valid:
self._cache_data = {"version":self._cache_version}
self._cache_data["blockers"] = {}
self._cache_data["virtuals"] = self._virtuals
self._modified = False
def flush(self):
"""If the current user has permission and the internal blocker cache
been updated, save it to disk and mark it unmodified. This is called
by emerge after it has proccessed blockers for all installed packages.
Currently, the cache is only written if the user has superuser
privileges (since that's required to obtain a lock), but all users
have read access and benefit from faster blocker lookups (as long as
the entire cache is still valid). The cache is stored as a pickled
dict object with the following format:
{
version : "1",
"blockers" : {cpv1:(counter,(atom1, atom2...)), cpv2...},
"virtuals" : vardb.settings.getvirtuals()
}
"""
if self._modified and \
secpass >= 2:
try:
f = portage_util.atomic_ofstream(self._cache_filename)
cPickle.dump(self._cache_data, f, -1)
f.close()
portage_util.apply_secpass_permissions(
self._cache_filename, gid=portage.portage_gid, mode=0644)
except (IOError, OSError), e:
pass
self._modified = False
def __setitem__(self, cpv, blocker_data):
"""
Update the cache and mark it as modified for a future call to
self.flush().
@param cpv: Package for which to cache blockers.
@type cpv: String
@param blocker_data: An object with counter and atoms attributes.
@type blocker_data: BlockerData
"""
self._cache_data["blockers"][cpv] = \
(blocker_data.counter, blocker_data.atoms)
self._modified = True
def __iter__(self):
return iter(self._cache_data["blockers"])
def __delitem__(self, cpv):
del self._cache_data["blockers"][cpv]
self._modified = True
def __getitem__(self, cpv):
"""
@rtype: BlockerData
@returns: An object with counter and atoms attributes.
"""
return self.BlockerData(*self._cache_data["blockers"][cpv])
def keys(self):
"""This needs to be implemented so that self.__repr__() doesn't raise
an AttributeError."""
return list(self)
class BlockerDB(object):
def __init__(self, vartree, portdb):
self._vartree = vartree
self._portdb = portdb
self._blocker_cache = \
BlockerCache(self._vartree.root, vartree.dbapi)
self._dep_check_trees = { self._vartree.root : {
"porttree" : self._vartree,
"vartree" : self._vartree,
}}
def findInstalledBlockers(self, new_pkg, acquire_lock=0):
blocker_cache = self._blocker_cache
dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
dep_check_trees = self._dep_check_trees
settings = self._vartree.settings
stale_cache = set(blocker_cache)
fake_vartree = \
FakeVartree(self._vartree,
self._portdb, Package.metadata_keys, {},
acquire_lock=acquire_lock)
vardb = fake_vartree.dbapi
installed_pkgs = list(vardb)
for inst_pkg in installed_pkgs:
stale_cache.discard(inst_pkg.cpv)
cached_blockers = blocker_cache.get(inst_pkg.cpv)
if cached_blockers is not None and \
cached_blockers.counter != long(inst_pkg.metadata["COUNTER"]):
cached_blockers = None
if cached_blockers is not None:
blocker_atoms = cached_blockers.atoms
else:
myuse = inst_pkg.metadata["USE"].split()
# Use aux_get() to trigger FakeVartree global
# updates on *DEPEND when appropriate.
depstr = " ".join(vardb.aux_get(inst_pkg.cpv, dep_keys))
try:
portage_dep._dep_check_strict = False
success, atoms = portage.dep_check(depstr,
vardb, settings, myuse=myuse,
trees=dep_check_trees, myroot=inst_pkg.root)
finally:
portage_dep._dep_check_strict = True
if not success:
pkg_location = os.path.join(inst_pkg.root,
portage.VDB_PATH, inst_pkg.category, inst_pkg.pf)
portage.writemsg("!!! %s/*DEPEND: %s\n" % \
(pkg_location, atoms), noiselevel=-1)
continue
blocker_atoms = [atom for atom in atoms \
if atom.startswith("!")]
blocker_atoms.sort()
counter = long(inst_pkg.metadata["COUNTER"])
blocker_cache[inst_pkg.cpv] = \
blocker_cache.BlockerData(counter, blocker_atoms)
for cpv in stale_cache:
del blocker_cache[cpv]
blocker_cache.flush()
blocker_parents = digraph()
blocker_atoms = []
for pkg in installed_pkgs:
for blocker_atom in self._blocker_cache[pkg.cpv].atoms:
blocker_atom = blocker_atom[1:]
blocker_atoms.append(blocker_atom)
blocker_parents.add(blocker_atom, pkg)
blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms)
blocking_pkgs = set()
for atom in blocker_atoms.iterAtomsForPackage(new_pkg):
blocking_pkgs.update(blocker_parents.parent_nodes(atom))
# Check for blockers in the other direction.
myuse = new_pkg.metadata["USE"].split()
depstr = " ".join(new_pkg.metadata[k] for k in dep_keys)
try:
portage_dep._dep_check_strict = False
success, atoms = portage.dep_check(depstr,
vardb, settings, myuse=myuse,
trees=dep_check_trees, myroot=new_pkg.root)
finally:
portage_dep._dep_check_strict = True
if not success:
# We should never get this far with invalid deps.
show_invalid_depstring_notice(new_pkg, depstr, atoms)
assert False
blocker_atoms = [atom[1:] for atom in atoms \
if atom.startswith("!")]
if blocker_atoms:
blocker_atoms = InternalPackageSet(initial_atoms=blocker_atoms)
for inst_pkg in installed_pkgs:
try:
blocker_atoms.iterAtomsForPackage(inst_pkg).next()
except (portage_exception.InvalidDependString, StopIteration):
continue
blocking_pkgs.add(inst_pkg)
return blocking_pkgs
def show_invalid_depstring_notice(parent_node, depstring, error_msg):
from formatter import AbstractFormatter, DumbWriter
f = AbstractFormatter(DumbWriter(maxcol=72))
print "\n\n!!! Invalid or corrupt dependency specification: "
print
print error_msg
print
print parent_node
print
print depstring
print
p_type, p_root, p_key, p_status = parent_node
msg = []
if p_status == "nomerge":
category, pf = portage.catsplit(p_key)
pkg_location = os.path.join(p_root, portage.VDB_PATH, category, pf)
msg.append("Portage is unable to process the dependencies of the ")
msg.append("'%s' package. " % p_key)
msg.append("In order to correct this problem, the package ")
msg.append("should be uninstalled, reinstalled, or upgraded. ")
msg.append("As a temporary workaround, the --nodeps option can ")
msg.append("be used to ignore all dependencies. For reference, ")
msg.append("the problematic dependencies can be found in the ")
msg.append("*DEPEND files located in '%s/'." % pkg_location)
else:
msg.append("This package can not be installed. ")
msg.append("Please notify the '%s' package maintainer " % p_key)
msg.append("about this problem.")
for x in msg:
f.add_flowing_data(x)
f.end_paragraph(1)
class PackageVirtualDbapi(portage.dbapi):
"""
A dbapi-like interface class that represents the state of the installed
package database as new packages are installed, replacing any packages
that previously existed in the same slot. The main difference between
this class and fakedbapi is that this one uses Package instances
internally (passed in via cpv_inject() and cpv_remove() calls).
"""
def __init__(self, settings):
portage.dbapi.__init__(self)
self.settings = settings
self._match_cache = {}
self._cp_map = {}
self._cpv_map = {}
def copy(self):
obj = PackageVirtualDbapi(self.settings)
obj._match_cache = self._match_cache.copy()
obj._cp_map = self._cp_map.copy()
for k, v in obj._cp_map.iteritems():
obj._cp_map[k] = v[:]
obj._cpv_map = self._cpv_map.copy()
return obj
def __iter__(self):
return self._cpv_map.itervalues()
def __contains__(self, item):
existing = self._cpv_map.get(item.cpv)
if existing is not None and \
existing == item:
return True
return False
def match_pkgs(self, atom):
return [self._cpv_map[cpv] for cpv in self.match(atom)]
def _clear_cache(self):
if self._categories is not None:
self._categories = None
if self._match_cache:
self._match_cache = {}
def match(self, origdep, use_cache=1):
result = self._match_cache.get(origdep)
if result is not None:
return result[:]
result = portage.dbapi.match(self, origdep, use_cache=use_cache)
self._match_cache[origdep] = result
return result[:]
def cpv_exists(self, cpv):
return cpv in self._cpv_map
def cp_list(self, mycp, use_cache=1):
cachelist = self._match_cache.get(mycp)
# cp_list() doesn't expand old-style virtuals
if cachelist and cachelist[0].startswith(mycp):
return cachelist[:]
cpv_list = self._cp_map.get(mycp)
if cpv_list is None:
cpv_list = []
else:
cpv_list = [pkg.cpv for pkg in cpv_list]
self._cpv_sort_ascending(cpv_list)
if not (not cpv_list and mycp.startswith("virtual/")):
self._match_cache[mycp] = cpv_list
return cpv_list[:]
def cp_all(self):
return list(self._cp_map)
def cpv_all(self):
return list(self._cpv_map)
def cpv_inject(self, pkg):
cp_list = self._cp_map.get(pkg.cp)
if cp_list is None:
cp_list = []
self._cp_map[pkg.cp] = cp_list
e_pkg = self._cpv_map.get(pkg.cpv)
if e_pkg is not None:
if e_pkg == pkg:
return
self.cpv_remove(e_pkg)
for e_pkg in cp_list:
if e_pkg.slot_atom == pkg.slot_atom:
if e_pkg == pkg:
return
self.cpv_remove(e_pkg)
break
cp_list.append(pkg)
self._cpv_map[pkg.cpv] = pkg
self._clear_cache()
def cpv_remove(self, pkg):
old_pkg = self._cpv_map.get(pkg.cpv)
if old_pkg != pkg:
raise KeyError(pkg)
self._cp_map[pkg.cp].remove(pkg)
del self._cpv_map[pkg.cpv]
self._clear_cache()
def aux_get(self, cpv, wants):
metadata = self._cpv_map[cpv].metadata
return [metadata.get(x, "") for x in wants]
def aux_update(self, cpv, values):
self._cpv_map[cpv].metadata.update(values)
self._clear_cache()
class depgraph(object):
pkg_tree_map = {
"ebuild":"porttree",
"binary":"bintree",
"installed":"vartree"}
_mydbapi_keys = Package.metadata_keys
_dep_keys = ["DEPEND", "RDEPEND", "PDEPEND"]
def __init__(self, settings, trees, myopts, myparams, spinner):
self.settings = settings
self.target_root = settings["ROOT"]
self.myopts = myopts
self.myparams = myparams
self.edebug = 0
if settings.get("PORTAGE_DEBUG", "") == "1":
self.edebug = 1
self.spinner = spinner
self.pkgsettings = {}
# Maps slot atom to package for each Package added to the graph.
self._slot_pkg_map = {}
# Maps nodes to the reasons they were selected for reinstallation.
self._reinstall_nodes = {}
self.mydbapi = {}
self.trees = {}
self._trees_orig = trees
self.roots = {}
# Contains a filtered view of preferred packages that are selected
# from available repositories.
self._filtered_trees = {}
# Contains installed packages and new packages that have been added
# to the graph.
self._graph_trees = {}
# All Package instances
self._pkg_cache = self._package_cache(self)
for myroot in trees:
self.trees[myroot] = {}
# Create a RootConfig instance that references
# the FakeVartree instead of the real one.
self.roots[myroot] = RootConfig(
trees[myroot]["vartree"].settings,
self.trees[myroot],
trees[myroot]["root_config"].setconfig)
for tree in ("porttree", "bintree"):
self.trees[myroot][tree] = trees[myroot][tree]
self.trees[myroot]["vartree"] = \
FakeVartree(trees[myroot]["vartree"],
trees[myroot]["porttree"].dbapi,
self._mydbapi_keys, self._pkg_cache)
self.pkgsettings[myroot] = portage.config(
clone=self.trees[myroot]["vartree"].settings)
self._slot_pkg_map[myroot] = {}
vardb = self.trees[myroot]["vartree"].dbapi
preload_installed_pkgs = "--nodeps" not in self.myopts and \
"--buildpkgonly" not in self.myopts
# This fakedbapi instance will model the state that the vdb will
# have after new packages have been installed.
fakedb = PackageVirtualDbapi(vardb.settings)
if preload_installed_pkgs:
for cpv in vardb.cpv_all():
self.spinner.update()
metadata = dict(izip(self._mydbapi_keys,
vardb.aux_get(cpv, self._mydbapi_keys)))
pkg = Package(built=True, cpv=cpv,
installed=True, metadata=metadata,
root=myroot, type_name="installed")
self._pkg_cache[pkg] = pkg
fakedb.cpv_inject(pkg)
self.mydbapi[myroot] = fakedb
def graph_tree():
pass
graph_tree.dbapi = fakedb
self._graph_trees[myroot] = {}
self._filtered_trees[myroot] = {}
# Substitute the graph tree for the vartree in dep_check() since we
# want atom selections to be consistent with package selections
# have already been made.
self._graph_trees[myroot]["porttree"] = graph_tree
self._graph_trees[myroot]["vartree"] = graph_tree
def filtered_tree():
pass
filtered_tree.dbapi = self._dep_check_composite_db(self, myroot)
self._filtered_trees[myroot]["porttree"] = filtered_tree
# Passing in graph_tree as the vartree here could lead to better
# atom selections in some cases by causing atoms for packages that
# have been added to the graph to be preferred over other choices.
# However, it can trigger atom selections that result in
# unresolvable direct circular dependencies. For example, this
# happens with gwydion-dylan which depends on either itself or
# gwydion-dylan-bin. In case gwydion-dylan is not yet installed,
# gwydion-dylan-bin needs to be selected in order to avoid a
# an unresolvable direct circular dependency.
#
# To solve the problem described above, pass in "graph_db" so that
# packages that have been added to the graph are distinguishable
# from other available packages and installed packages. Also, pass
# the parent package into self._select_atoms() calls so that
# unresolvable direct circular dependencies can be detected and
# avoided when possible.
self._filtered_trees[myroot]["graph_db"] = graph_tree.dbapi
self._filtered_trees[myroot]["vartree"] = self.trees[myroot]["vartree"]
dbs = []
portdb = self.trees[myroot]["porttree"].dbapi
bindb = self.trees[myroot]["bintree"].dbapi
vardb = self.trees[myroot]["vartree"].dbapi
# (db, pkg_type, built, installed, db_keys)
if "--usepkgonly" not in self.myopts:
db_keys = list(portdb._aux_cache_keys)
dbs.append((portdb, "ebuild", False, False, db_keys))
if "--usepkg" in self.myopts:
db_keys = list(bindb._aux_cache_keys)
dbs.append((bindb, "binary", True, False, db_keys))
db_keys = self._mydbapi_keys
dbs.append((vardb, "installed", True, True, db_keys))
self._filtered_trees[myroot]["dbs"] = dbs
if "--usepkg" in self.myopts:
self.trees[myroot]["bintree"].populate(
"--getbinpkg" in self.myopts,
"--getbinpkgonly" in self.myopts)
del trees
self.digraph=portage.digraph()
# contains all sets added to the graph
self._sets = {}
# contains atoms given as arguments
self._sets["args"] = InternalPackageSet()
# contains all atoms from all sets added to the graph, including
# atoms given as arguments
self._set_atoms = InternalPackageSet()
self._atom_arg_map = {}
# contains all nodes pulled in by self._set_atoms
self._set_nodes = set()
# Contains only Blocker -> Uninstall edges
self._blocker_uninstalls = digraph()
# Contains only Package -> Blocker edges
self._blocker_parents = digraph()
# Contains only irrelevant Package -> Blocker edges
self._irrelevant_blockers = digraph()
# Contains only unsolvable Package -> Blocker edges
self._unsolvable_blockers = digraph()
self._slot_collision_info = set()
# Slot collision nodes are not allowed to block other packages since
# blocker validation is only able to account for one package per slot.
self._slot_collision_nodes = set()
self._serialized_tasks_cache = None
self._displayed_list = None
self._pprovided_args = []
self._missing_args = []
self._masked_installed = []
self._unsatisfied_deps_for_display = []
self._unsatisfied_blockers_for_display = None
self._circular_deps_for_display = None
self._dep_stack = []
self._unsatisfied_deps = []
self._ignored_deps = []
self._required_set_names = set(["system", "world"])
self._select_atoms = self._select_atoms_highest_available
self._select_package = self._select_pkg_highest_available
self._highest_pkg_cache = {}
def _show_slot_collision_notice(self):
"""Show an informational message advising the user to mask one of the
the packages. In some cases it may be possible to resolve this
automatically, but support for backtracking (removal nodes that have
already been selected) will be required in order to handle all possible
cases."""
if not self._slot_collision_info:
return
self._show_merge_list()
msg = []
msg.append("\n!!! Multiple versions within a single " + \
"package slot have been pulled\n")
msg.append("!!! into the dependency graph, resulting" + \
" in a slot conflict:\n\n")
indent = " "
# Max number of parents shown, to avoid flooding the display.
max_parents = 3
for slot_atom, root in self._slot_collision_info:
msg.append(slot_atom)
msg.append("\n\n")
slot_nodes = []
for node in self._slot_collision_nodes:
if node.slot_atom == slot_atom:
slot_nodes.append(node)
slot_nodes.append(self._slot_pkg_map[root][slot_atom])
for node in slot_nodes:
msg.append(indent)
msg.append(str(node))
parents = self.digraph.parent_nodes(node)
if parents:
omitted_parents = 0
if len(parents) > max_parents:
pruned_list = []
# When generating the pruned list, prefer instances
# of DependencyArg over instances of Package.
for parent in parents:
if isinstance(parent, DependencyArg):
pruned_list.append(parent)
# Prefer Packages instances that themselves have been
# pulled into collision slots.
for parent in parents:
if isinstance(parent, Package) and \
(parent.slot_atom, parent.root) \
in self._slot_collision_info:
pruned_list.append(parent)
for parent in parents:
if len(pruned_list) >= max_parents:
break
if not isinstance(parent, DependencyArg) and \
parent not in pruned_list:
pruned_list.append(parent)
omitted_parents = len(parents) - len(pruned_list)
parents = pruned_list
msg.append(" pulled in by\n")
for parent in parents:
msg.append(2*indent)
msg.append(str(parent))
msg.append("\n")
if omitted_parents:
msg.append(2*indent)
msg.append("(and %d more)\n" % omitted_parents)
else:
msg.append(" (no parents)\n")
msg.append("\n")
msg.append("\n")
sys.stderr.write("".join(msg))
sys.stderr.flush()
if "--quiet" in self.myopts:
return
msg = []
msg.append("It may be possible to solve this problem ")
msg.append("by using package.mask to prevent one of ")
msg.append("those packages from being selected. ")
msg.append("However, it is also possible that conflicting ")
msg.append("dependencies exist such that they are impossible to ")
msg.append("satisfy simultaneously. If such a conflict exists in ")
msg.append("the dependencies of two different packages, then those ")
msg.append("packages can not be installed simultaneously.")
from formatter import AbstractFormatter, DumbWriter
f = AbstractFormatter(DumbWriter(sys.stderr, maxcol=72))
for x in msg:
f.add_flowing_data(x)
f.end_paragraph(1)
msg = []
msg.append("For more information, see MASKED PACKAGES ")
msg.append("section in the emerge man page or refer ")
msg.append("to the Gentoo Handbook.")
for x in msg:
f.add_flowing_data(x)
f.end_paragraph(1)
f.writer.flush()
def _reinstall_for_flags(self, forced_flags,
orig_use, orig_iuse, cur_use, cur_iuse):
"""Return a set of flags that trigger reinstallation, or None if there
are no such flags."""
if "--newuse" in self.myopts:
flags = orig_iuse.symmetric_difference(
cur_iuse).difference(forced_flags)
flags.update(orig_iuse.intersection(orig_use).symmetric_difference(
cur_iuse.intersection(cur_use)))
if flags:
return flags
elif "changed-use" == self.myopts.get("--reinstall"):
flags = orig_iuse.intersection(orig_use).symmetric_difference(
cur_iuse.intersection(cur_use))
if flags:
return flags
return None
def _create_graph(self, allow_unsatisfied=False):
dep_stack = self._dep_stack
while dep_stack:
self.spinner.update()
dep = dep_stack.pop()
if isinstance(dep, Package):
if not self._add_pkg_deps(dep,
allow_unsatisfied=allow_unsatisfied):
return 0
continue
if not self._add_dep(dep, allow_unsatisfied=allow_unsatisfied):
return 0
return 1
def _add_dep(self, dep, allow_unsatisfied=False):
debug = "--debug" in self.myopts
buildpkgonly = "--buildpkgonly" in self.myopts
nodeps = "--nodeps" in self.myopts
empty = "empty" in self.myparams
deep = "deep" in self.myparams
update = "--update" in self.myopts and dep.depth <= 1
if dep.blocker:
if not buildpkgonly and \
not nodeps and \
dep.parent not in self._slot_collision_nodes:
if dep.parent.onlydeps:
# It's safe to ignore blockers if the
# parent is an --onlydeps node.
return 1
# The blocker applies to the root where
# the parent is or will be installed.
blocker = Blocker(atom=dep.atom, root=dep.parent.root)
self._blocker_parents.add(blocker, dep.parent)
return 1
dep_pkg, existing_node = self._select_package(dep.root, dep.atom,
onlydeps=dep.onlydeps)
if not dep_pkg:
if allow_unsatisfied:
self._unsatisfied_deps.append(dep)
return 1
self._unsatisfied_deps_for_display.append(
((dep.root, dep.atom), {"myparent":dep.parent}))
return 0
# In some cases, dep_check will return deps that shouldn't
# be proccessed any further, so they are identified and
# discarded here. Try to discard as few as possible since
# discarded dependencies reduce the amount of information
# available for optimization of merge order.
if dep.priority.satisfied and \
not (existing_node or empty or deep or update):
myarg = None
if dep.root == self.target_root:
try:
myarg = self._iter_atoms_for_pkg(dep_pkg).next()
except StopIteration:
pass
except portage_exception.InvalidDependString:
if not dep_pkg.installed:
# This shouldn't happen since the package
# should have been masked.
raise
if not myarg:
self._ignored_deps.append(dep)
return 1
if not self._add_pkg(dep_pkg, dep.parent,
priority=dep.priority, depth=dep.depth):
return 0
return 1
def _add_pkg(self, pkg, myparent, priority=None, depth=0):
if priority is None:
priority = DepPriority()
"""
Fills the digraph with nodes comprised of packages to merge.
mybigkey is the package spec of the package to merge.
myparent is the package depending on mybigkey ( or None )
addme = Should we add this package to the digraph or are we just looking at it's deps?
Think --onlydeps, we need to ignore packages in that case.
#stuff to add:
#SLOT-aware emerge
#IUSE-aware emerge -> USE DEP aware depgraph
#"no downgrade" emerge
"""
# select the correct /var database that we'll be checking against
vardbapi = self.trees[pkg.root]["vartree"].dbapi
pkgsettings = self.pkgsettings[pkg.root]
args = None
arg_atoms = None
if True:
try:
arg_atoms = list(self._iter_atoms_for_pkg(pkg))
except portage_exception.InvalidDependString, e:
if not pkg.installed:
show_invalid_depstring_notice(
pkg, pkg.metadata["PROVIDE"], str(e))
return 0
del e
else:
args = [arg for arg, atom in arg_atoms]
if not pkg.onlydeps:
if not pkg.installed and \
"empty" not in self.myparams and \
vardbapi.match(pkg.slot_atom):
# Increase the priority of dependencies on packages that
# are being rebuilt. This optimizes merge order so that
# dependencies are rebuilt/updated as soon as possible,
# which is needed especially when emerge is called by
# revdep-rebuild since dependencies may be affected by ABI
# breakage that has rendered them useless. Don't adjust
# priority here when in "empty" mode since all packages
# are being merged in that case.
priority.rebuild = True
existing_node = self._slot_pkg_map[pkg.root].get(pkg.slot_atom)
slot_collision = False
if existing_node:
if pkg.cpv == existing_node.cpv:
# The existing node can be reused.
if args:
for arg in args:
self.digraph.add(existing_node, arg,
priority=priority)
# If a direct circular dependency is not an unsatisfied
# buildtime dependency then drop it here since otherwise
# it can skew the merge order calculation in an unwanted
# way.
if existing_node != myparent or \
(priority.buildtime and not priority.satisfied):
self.digraph.addnode(existing_node, myparent,
priority=priority)
return 1
else:
if pkg in self._slot_collision_nodes:
return 1
# A slot collision has occurred. Sometimes this coincides
# with unresolvable blockers, so the slot collision will be
# shown later if there are no unresolvable blockers.
self._slot_collision_info.add((pkg.slot_atom, pkg.root))
self._slot_collision_nodes.add(pkg)
slot_collision = True
if slot_collision:
# Now add this node to the graph so that self.display()
# can show use flags and --tree portage.output. This node is
# only being partially added to the graph. It must not be
# allowed to interfere with the other nodes that have been
# added. Do not overwrite data for existing nodes in
# self.mydbapi since that data will be used for blocker
# validation.
# Even though the graph is now invalid, continue to process
# dependencies so that things like --fetchonly can still
# function despite collisions.
pass
else:
self._slot_pkg_map[pkg.root][pkg.slot_atom] = pkg
self.mydbapi[pkg.root].cpv_inject(pkg)
self.digraph.addnode(pkg, myparent, priority=priority)
if not pkg.installed:
# Allow this package to satisfy old-style virtuals in case it
# doesn't already. Any pre-existing providers will be preferred
# over this one.
try:
pkgsettings.setinst(pkg.cpv, pkg.metadata)
# For consistency, also update the global virtuals.
settings = self.roots[pkg.root].settings
settings.unlock()
settings.setinst(pkg.cpv, pkg.metadata)
settings.lock()
except portage_exception.InvalidDependString, e:
show_invalid_depstring_notice(
pkg, pkg.metadata["PROVIDE"], str(e))
del e
return 0
if pkg.installed:
# Warn if an installed package is masked and it
# is pulled into the graph.
if not visible(pkgsettings, pkg):
self._masked_installed.append((pkg, pkgsettings))
if args:
self._set_nodes.add(pkg)
# Do this even when addme is False (--onlydeps) so that the
# parent/child relationship is always known in case
# self._show_slot_collision_notice() needs to be called later.
if pkg.onlydeps:
self.digraph.add(pkg, myparent, priority=priority)
if args:
for arg in args:
self.digraph.add(pkg, arg, priority=priority)
""" This section determines whether we go deeper into dependencies or not.
We want to go deeper on a few occasions:
Installing package A, we need to make sure package A's deps are met.
emerge --deep <pkgspec>; we need to recursively check dependencies of pkgspec
If we are in --nodeps (no recursion) mode, we obviously only check 1 level of dependencies.
"""
dep_stack = self._dep_stack
if "recurse" not in self.myparams:
return 1
elif pkg.installed and \
"deep" not in self.myparams:
dep_stack = self._ignored_deps
self.spinner.update()
if args:
depth = 0
pkg.depth = depth
dep_stack.append(pkg)
return 1
def _add_pkg_deps(self, pkg, allow_unsatisfied=False):
mytype = pkg.type_name
myroot = pkg.root
mykey = pkg.cpv
metadata = pkg.metadata
myuse = metadata["USE"].split()
jbigkey = pkg
depth = pkg.depth + 1
edepend={}
depkeys = ["DEPEND","RDEPEND","PDEPEND"]
for k in depkeys:
edepend[k] = metadata[k]
if not pkg.built and \
"--buildpkgonly" in self.myopts and \
"deep" not in self.myparams and \
"empty" not in self.myparams:
edepend["RDEPEND"] = ""
edepend["PDEPEND"] = ""
bdeps_satisfied = False
if mytype in ("installed", "binary"):
if self.myopts.get("--with-bdeps", "n") == "y":
# Pull in build time deps as requested, but marked them as
# "satisfied" since they are not strictly required. This allows
# more freedom in the merge order calculation for solving
# circular dependencies. Don't convert to PDEPEND since that
# could make --with-bdeps=y less effective if it is used to
# adjust merge order to prevent built_with_use() calls from
# failing.
bdeps_satisfied = True
else:
# built packages do not have build time dependencies.
edepend["DEPEND"] = ""
deps = (
("/", edepend["DEPEND"],
DepPriority(buildtime=True, satisfied=bdeps_satisfied)),
(myroot, edepend["RDEPEND"], DepPriority(runtime=True)),
(myroot, edepend["PDEPEND"], DepPriority(runtime_post=True))
)
debug = "--debug" in self.myopts
strict = mytype != "installed"
try:
for dep_root, dep_string, dep_priority in deps:
if pkg.onlydeps:
# Decrease priority so that --buildpkgonly
# hasallzeros() works correctly.
dep_priority = DepPriority()
if not dep_string:
continue
if debug:
print
print "Parent: ", jbigkey
print "Depstring:", dep_string
print "Priority:", dep_priority
vardb = self.roots[dep_root].trees["vartree"].dbapi
try:
selected_atoms = self._select_atoms(dep_root,
dep_string, myuse=myuse, parent=pkg, strict=strict)
except portage_exception.InvalidDependString, e:
show_invalid_depstring_notice(jbigkey, dep_string, str(e))
return 0
if debug:
print "Candidates:", selected_atoms
for atom in selected_atoms:
blocker = atom.startswith("!")
if blocker:
atom = atom[1:]
mypriority = dep_priority.copy()
if not blocker and vardb.match(atom):
mypriority.satisfied = True
if not self._add_dep(Dependency(atom=atom,
blocker=blocker, depth=depth, parent=pkg,
priority=mypriority, root=dep_root),
allow_unsatisfied=allow_unsatisfied):
return 0
if debug:
print "Exiting...", jbigkey
except ValueError, e:
if not e.args or not isinstance(e.args[0], list) or \
len(e.args[0]) < 2:
raise
pkgs = e.args[0]
portage.writemsg("\n\n!!! An atom in the dependencies " + \
"is not fully-qualified. Multiple matches:\n\n", noiselevel=-1)
for cpv in pkgs:
portage.writemsg(" %s\n" % cpv, noiselevel=-1)
portage.writemsg("\n", noiselevel=-1)
if mytype == "binary":
portage.writemsg(
"!!! This binary package cannot be installed: '%s'\n" % \
mykey, noiselevel=-1)
elif mytype == "ebuild":
portdb = self.roots[myroot].trees["porttree"].dbapi
myebuild, mylocation = portdb.findname2(mykey)
portage.writemsg("!!! This ebuild cannot be installed: " + \
"'%s'\n" % myebuild, noiselevel=-1)
portage.writemsg("!!! Please notify the package maintainer " + \
"that atoms must be fully-qualified.\n", noiselevel=-1)
return 0
return 1
def _dep_expand(self, root_config, atom_without_category):
"""
@param root_config: a root config instance
@type root_config: RootConfig
@param atom_without_category: an atom without a category component
@type atom_without_category: String
@rtype: list
@returns: a list of atoms containing categories (possibly empty)
"""
null_cp = portage.dep_getkey(insert_category_into_atom(
atom_without_category, "null"))
cat, atom_pn = portage.catsplit(null_cp)
cp_set = set()
for db, pkg_type, built, installed, db_keys in \
self._filtered_trees[root_config.root]["dbs"]:
cp_set.update(db.cp_all())
for cp in list(cp_set):
cat, pn = portage.catsplit(cp)
if pn != atom_pn:
cp_set.discard(cp)
deps = []
for cp in cp_set:
cat, pn = portage.catsplit(cp)
deps.append(insert_category_into_atom(
atom_without_category, cat))
return deps
def _have_new_virt(self, root, atom_cp):
ret = False
for db, pkg_type, built, installed, db_keys in \
self._filtered_trees[root]["dbs"]:
if db.cp_list(atom_cp):
ret = True
break
return ret
def _iter_atoms_for_pkg(self, pkg):
# TODO: add multiple $ROOT support
if pkg.root != self.target_root:
return
atom_arg_map = self._atom_arg_map
root_config = self.roots[pkg.root]
for atom in self._set_atoms.iterAtomsForPackage(pkg):
atom_cp = portage.dep_getkey(atom)
if atom_cp != pkg.cp and \
self._have_new_virt(pkg.root, atom_cp):
continue
visible_pkgs = root_config.visible_pkgs.match_pkgs(atom)
visible_pkgs.reverse() # descending order
higher_slot = None
for visible_pkg in visible_pkgs:
if visible_pkg.cp != atom_cp:
continue
if pkg >= visible_pkg:
# This is descending order, and we're not
# interested in any versions <= pkg given.
break
if pkg.slot_atom != visible_pkg.slot_atom:
higher_slot = visible_pkg
break
if higher_slot is not None:
continue
for arg in<