| # Copyright 2007 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| # $Id$ |
| |
| __all__ = ["SETPREFIX", "get_boolean", "SetConfigError", |
| "SetConfig", "load_default_config"] |
| |
| import os |
| from ConfigParser import SafeConfigParser, NoOptionError |
| from portage import load_mod |
| from portage.const import USER_CONFIG_PATH, GLOBAL_CONFIG_PATH |
| from portage.exception import PackageSetNotFound |
| |
| SETPREFIX = "@" |
| |
| def get_boolean(options, name, default): |
| if not name in options: |
| return default |
| elif options[name].lower() in ("1", "yes", "on", "true"): |
| return True |
| elif options[name].lower() in ("0", "no", "off", "false"): |
| return False |
| else: |
| raise SetConfigError("invalid value '%s' for option '%s'" % (options[name], name)) |
| |
| class SetConfigError(Exception): |
| pass |
| |
| class SetConfig(object): |
| def __init__(self, paths, settings, trees): |
| self._parser = SafeConfigParser() |
| self._parser.read(paths) |
| self.errors = [] |
| self.psets = {} |
| self.trees = trees |
| self.settings = settings |
| self._parsed = False |
| self.active = [] |
| |
| def update(self, setname, options): |
| parser = self._parser |
| self.errors = [] |
| if not setname in self.psets: |
| options["name"] = setname |
| options["world-candidate"] = "False" |
| |
| # for the unlikely case that there is already a section with the requested setname |
| import random |
| while setname in parser.sections(): |
| setname = "%08d" % random.randint(0, 10**10) |
| |
| parser.add_section(setname) |
| for k, v in options.items(): |
| parser.set(setname, k, v) |
| else: |
| section = self.psets[setname].creator |
| if parser.has_option(section, "multiset") and \ |
| parser.getboolean(section, "multiset"): |
| self.errors.append("Invalid request to reconfigure set '%s' generated by multiset section '%s'" % (setname, section)) |
| return |
| for k, v in options.items(): |
| parser.set(section, k, v) |
| self._parse(update=True) |
| |
| def _parse(self, update=False): |
| if self._parsed and not update: |
| return |
| parser = self._parser |
| for sname in parser.sections(): |
| # find classname for current section, default to file based sets |
| if not parser.has_option(sname, "class"): |
| classname = "portage.sets.files.StaticFileSet" |
| else: |
| classname = parser.get(sname, "class") |
| |
| # try to import the specified class |
| try: |
| setclass = load_mod(classname) |
| except (ImportError, AttributeError): |
| try: |
| setclass = load_mod("portage.sets."+classname) |
| except (ImportError, AttributeError): |
| self.errors.append("Could not import '%s' for section '%s'" % (classname, sname)) |
| continue |
| # prepare option dict for the current section |
| optdict = {} |
| for oname in parser.options(sname): |
| optdict[oname] = parser.get(sname, oname) |
| |
| # create single or multiple instances of the given class depending on configuration |
| if parser.has_option(sname, "multiset") and \ |
| parser.getboolean(sname, "multiset"): |
| if hasattr(setclass, "multiBuilder"): |
| newsets = {} |
| try: |
| newsets = setclass.multiBuilder(optdict, self.settings, self.trees) |
| except SetConfigError, e: |
| self.errors.append("Configuration error in section '%s': %s" % (sname, str(e))) |
| continue |
| for x in newsets: |
| if x in self.psets and not update: |
| self.errors.append("Redefinition of set '%s' (sections: '%s', '%s')" % (x, self.psets[x].creator, sname)) |
| newsets[x].creator = sname |
| if parser.has_option(sname, "world-candidate") and \ |
| not parser.getboolean(sname, "world-candidate"): |
| newsets[x].world_candidate = False |
| self.psets.update(newsets) |
| else: |
| self.errors.append("Section '%s' is configured as multiset, but '%s' doesn't support that configuration" % (sname, classname)) |
| continue |
| else: |
| try: |
| setname = parser.get(sname, "name") |
| except NoOptionError: |
| setname = sname |
| if setname in self.psets and not update: |
| self.errors.append("Redefinition of set '%s' (sections: '%s', '%s')" % (setname, self.psets[setname].creator, sname)) |
| if hasattr(setclass, "singleBuilder"): |
| try: |
| self.psets[setname] = setclass.singleBuilder(optdict, self.settings, self.trees) |
| self.psets[setname].creator = sname |
| if parser.has_option(sname, "world-candidate") and \ |
| not parser.getboolean(sname, "world-candidate"): |
| self.psets[setname].world_candidate = False |
| except SetConfigError, e: |
| self.errors.append("Configuration error in section '%s': %s" % (sname, str(e))) |
| continue |
| else: |
| self.errors.append("'%s' does not support individual set creation, section '%s' must be configured as multiset" % (classname, sname)) |
| continue |
| self._parsed = True |
| |
| def getSets(self): |
| self._parse() |
| return self.psets.copy() |
| |
| def getSetAtoms(self, setname, ignorelist=None): |
| """ |
| This raises PackageSetNotFound if the give setname does not exist. |
| """ |
| self._parse() |
| try: |
| myset = self.psets[setname] |
| except KeyError: |
| raise PackageSetNotFound(setname) |
| myatoms = myset.getAtoms() |
| parser = self._parser |
| extend = set() |
| remove = set() |
| intersect = set() |
| |
| if ignorelist is None: |
| ignorelist = set() |
| if not setname in ignorelist: |
| if parser.has_option(myset.creator, "extend"): |
| extend.update(parser.get(myset.creator, "extend").split()) |
| if parser.has_option(myset.creator, "remove"): |
| remove.update(parser.get(myset.creator, "remove").split()) |
| if parser.has_option(myset.creator, "intersect"): |
| intersect.update(parser.get(myset.creator, "intersect").split()) |
| |
| ignorelist.add(setname) |
| for n in myset.getNonAtoms(): |
| if n.startswith(SETPREFIX): |
| s = n[len(SETPREFIX):] |
| if s in self.psets: |
| extend.add(n[len(SETPREFIX):]) |
| else: |
| raise PackageSetNotFound(s) |
| |
| for s in ignorelist: |
| extend.discard(s) |
| remove.discard(s) |
| intersect.discard(s) |
| |
| for s in extend: |
| myatoms.update(self.getSetAtoms(s, ignorelist=ignorelist)) |
| for s in remove: |
| myatoms.difference_update(self.getSetAtoms(s, ignorelist=ignorelist)) |
| for s in intersect: |
| myatoms.intersection_update(self.getSetAtoms(s, ignorelist=ignorelist)) |
| |
| return myatoms |
| |
| def load_default_config(settings, trees): |
| setconfigpaths = [os.path.join(GLOBAL_CONFIG_PATH, "sets.conf")] |
| setconfigpaths.append(os.path.join(settings["PORTDIR"], "sets.conf")) |
| setconfigpaths += [os.path.join(x, "sets.conf") for x in settings["PORTDIR_OVERLAY"].split()] |
| setconfigpaths.append(os.path.join(settings["PORTAGE_CONFIGROOT"], |
| USER_CONFIG_PATH.lstrip(os.path.sep), "sets.conf")) |
| return SetConfig(setconfigpaths, settings, trees) |
| |
| # adhoc test code |
| if __name__ == "__main__": |
| import portage |
| sc = load_default_config(portage.settings, portage.db["/"]) |
| l, e = sc.getSets() |
| for x in l: |
| print x+":" |
| print "DESCRIPTION = %s" % l[x].getMetadata("Description") |
| for n in sorted(l[x].getAtoms()): |
| print "- "+n |
| print |