From d377e671e1455e2eabf1e3170cb178da6f43bd0e Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Fri, 3 Oct 2008 16:50:31 +0000 Subject: [PATCH] Merged from trunk -r11580:11593 | 11581 | implement set arguments to reconfigure and create package | | genone | sets on the commandline | | 11582 | disable redefintion errors when updating the set definitions | | genone | | | 11583 | Properly process set arguments inside set expressions | | genone | | | 11584 | Add operator logic to sets.conf | | genone | | | 11585 | use ignorelist to avoid potential infite loop | | genone | | | 11586 | update set config documentation to include extend, remove and | | genone | intersect options | | 11587 | document AgeSet handler class | | genone | | | 11588 | account for DTD changes wrt 'revised' element (patch by | | genone | Robert Buchholz ) | | 11589 | also accept glsa-2.dtd as valid for GLSAs (patch by Robert | | genone | Buchholz ) | | 11590 | print dates in a consistent format (patch by Robert Buchholz | | genone | ) | | 11591 | sort summarylist output (patch by Robert Buchholz | | genone | ) | | 11592 | use summarylist for output of test mode when --verbose is | | genone | given (patch by Robert Buchholz ) | | 11593 | add support for slot dependencies (original patch by Robert | | genone | Buchholz ) | svn path=/main/branches/prefix/; revision=11614 --- bin/glsa-check | 11 +++--- doc/config/sets.docbook | 51 +++++++++++++++++++++---- pym/_emerge/__init__.py | 72 +++++++++++++++++++++++++++--------- pym/portage/glsa.py | 69 ++++++++++++++++++++++++++++++---- pym/portage/sets/__init__.py | 62 +++++++++++++++++++++++++++---- 5 files changed, 219 insertions(+), 46 deletions(-) diff --git a/bin/glsa-check b/bin/glsa-check index 23565fc6f..97f393c8e 100644 --- a/bin/glsa-check +++ b/bin/glsa-check @@ -169,6 +169,7 @@ def summarylist(myglsalist, fd1=sys.stdout, fd2=sys.stderr): fd2.write(green("[U]")+" means the system is not affected and\n") fd2.write(red("[N]")+" indicates that the system might be affected.\n\n") + myglsalist.sort() for myid in myglsalist: try: myglsa = Glsa(myid, portage.settings, vardb, portdb) @@ -275,13 +276,13 @@ if mode == "test": sys.stderr.write(("invalid GLSA: %s (error message was: %s)\n" % (myid, e))) continue if myglsa.isVulnerable(): - if verbose: - outputlist.append(str(myglsa.nr)+" ( "+myglsa.title+" ) ") - else: - outputlist.append(str(myglsa.nr)) + outputlist.append(str(myglsa.nr)) if len(outputlist) > 0: sys.stderr.write("This system is affected by the following GLSAs:\n") - sys.stdout.write("\n".join(outputlist)+"\n") + if verbose: + summarylist(outputlist) + else: + sys.stdout.write("\n".join(outputlist)+"\n") else: sys.stderr.write("This system is not affected by any of the listed GLSAs\n") sys.exit(0) diff --git a/doc/config/sets.docbook b/doc/config/sets.docbook index cfce6ecf6..78546c4cb 100644 --- a/doc/config/sets.docbook +++ b/doc/config/sets.docbook @@ -49,12 +49,21 @@ isn't stricly required, but it should always be used as the default handler might be changed in future versions.. That option defines which handler class should be used to - create the set. Other universal options available for single sets are - name (which is usually not needed as the name + create the set. Other universal options available for single sets are: + + name (which is usually not needed as the name of the set is generated from the section name if name - is missing) and world-candidate, which determines if - given package should be added to the world set. Some - handler classes might require additional options for their configuration, + is missing) + world-candidate, which determines if + given package should be added to the world set + extend to include the contents of other package sets + + remove to remove the contents of other package sets + + intersect to only include packages that are also + included in one or more other package sets + + Some handler classes might require additional options for their configuration, these will be covered later in this chapter. @@ -83,8 +92,10 @@ sets each section still requires the class option, but to indicate that the section should generate multiple sets it's also necessary to set the multiset option to - true. The world-candidate - option is also supported like with single sets. + true. The world-candidate, + extend, remove and + intersect options are also supported like with + single sets (they'll apply to all sets generated by the section). As it doesn't make much sense to specify a single name for multiple sets @@ -386,6 +397,32 @@ + + portage.sets.dbapi.AgeSet + + Package sets created by this class will include installed packages that + have been installed before / after a given date. + + + + Single Set Configuration + + In single set configurations this class supports the following options: + + age: Optional, defaults to 7. Specifies + the number of days passed since installation to use as cut-off point. + + mode: Optional, defaults to "older". Must + be either "older" or "newer" to select packages installed either + before resp. after the cut-off-date given by age. + E.g. the defaults will select all installed packages that have been + installed more than one week ago. + + + + + + portage.sets.dbapi.CategorySet diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index 0b404e712..752b2eb67 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -13452,25 +13452,8 @@ def expand_set_arguments(myfiles, myaction, root_config): retval = os.EX_OK setconfig = root_config.setconfig - # display errors that occured while loading the SetConfig instance - for e in setconfig.errors: - print colorize("BAD", "Error during set creation: %s" % e) - sets = setconfig.getSets() - # emerge relies on the existance of sets with names "world" and "system" - required_sets = ("world", "system") - - for s in required_sets: - if s not in sets: - msg = ["emerge: incomplete set configuration, " + \ - "no \"%s\" set defined" % s] - msg.append(" sets defined: %s" % ", ".join(sets)) - for line in msg: - sys.stderr.write(line + "\n") - retval = 1 - unmerge_actions = ("unmerge", "prune", "clean", "depclean") - # In order to know exactly which atoms/sets should be added to the # world file, the depgraph performs set expansion later. It will get # confused about where the atoms came from if it's not allowed to @@ -13485,12 +13468,65 @@ def expand_set_arguments(myfiles, myaction, root_config): myfiles = newargs del newargs newargs = [] - + + # separators for set arguments + ARG_START = "{" + ARG_END = "}" + # WARNING: all operators must be of equal length IS_OPERATOR = "/@" DIFF_OPERATOR = "-@" UNION_OPERATOR = "+@" + for i in range(0, len(myfiles)): + if myfiles[i].startswith(SETPREFIX): + start = 0 + end = 0 + x = myfiles[i][len(SETPREFIX):] + newset = "" + while x: + start = x.find(ARG_START) + end = x.find(ARG_END) + if start > 0 and start < end: + namepart = x[:start] + argpart = x[start+1:end] + + # TODO: implement proper quoting + args = argpart.split(",") + options = {} + for a in args: + if "=" in a: + k, v = a.split("=", 1) + options[k] = v + else: + options[a] = "True" + setconfig.update(namepart, options) + newset += (x[:start-len(namepart)]+namepart) + x = x[end+len(ARG_END):] + else: + newset += x + x = "" + myfiles[i] = SETPREFIX+newset + + sets = setconfig.getSets() + + # display errors that occured while loading the SetConfig instance + for e in setconfig.errors: + print colorize("BAD", "Error during set creation: %s" % e) + + # emerge relies on the existance of sets with names "world" and "system" + required_sets = ("world", "system") + + for s in required_sets: + if s not in sets: + msg = ["emerge: incomplete set configuration, " + \ + "no \"%s\" set defined" % s] + msg.append(" sets defined: %s" % ", ".join(sets)) + for line in msg: + sys.stderr.write(line + "\n") + retval = 1 + unmerge_actions = ("unmerge", "prune", "clean", "depclean") + for a in myfiles: if a.startswith(SETPREFIX): # support simple set operations (intersection, difference and union) diff --git a/pym/portage/glsa.py b/pym/portage/glsa.py index 4dc05f7e1..33d3cf027 100644 --- a/pym/portage/glsa.py +++ b/pym/portage/glsa.py @@ -226,6 +226,8 @@ def makeAtom(pkgname, versionNode): rValue = opMapping[versionNode.getAttribute("range")] \ + pkgname \ + "-" + getText(versionNode, format="strip") + if "slot" in versionNode.attributes and versionNode.getAttribute("slot") != "*": + rValue += ":"+versionNode.getAttribute("slot") return str(rValue) def makeVersion(versionNode): @@ -239,8 +241,11 @@ def makeVersion(versionNode): @rtype: String @return: the version string """ - return opMapping[versionNode.getAttribute("range")] \ + rValue = opMapping[versionNode.getAttribute("range")] \ + getText(versionNode, format="strip") + if "slot" in versionNode.attributes and versionNode.getAttribute("slot") != "*": + rValue += ":"+versionNode.getAttribute("slot") + return rValue def match(atom, dbapi, match_type="default"): """ @@ -283,9 +288,9 @@ def revisionMatch(revisionAtom, dbapi, match_type="default"): @return: a list with the matching versions """ if match_type == "default" or not hasattr(dbapi, "xmatch"): - mylist = dbapi.match(re.sub("-r[0-9]+$", "", revisionAtom[2:])) + mylist = dbapi.match(re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) else: - mylist = dbapi.xmatch(match_type, re.sub("-r[0-9]+$", "", revisionAtom[2:])) + mylist = dbapi.xmatch(match_type, re.sub(r'-r[0-9]+(:[^ ]+)?$', r'\1', revisionAtom[2:])) rValue = [] for v in mylist: r1 = pkgsplit(v)[-1][1:] @@ -353,6 +358,32 @@ def getMinUpgrade(vulnerableList, unaffectedList, portdbapi, vardbapi, minimize= rValue += "-"+c_pv[3] return rValue +def format_date(datestr): + """ + Takes a date (announced, revised) date from a GLSA and formats + it as readable text (i.e. "January 1, 2008"). + + @type date: String + @param date: the date string to reformat + @rtype: String + @return: a reformatted string, or the original string + if it cannot be reformatted. + """ + splitdate = datestr.split("-", 2) + if len(splitdate) != 3: + return datestr + + # This cannot raise an error as we use () instead of [] + splitdate = (int(x) for x in splitdate) + + from datetime import date + try: + d = date(*splitdate) + except ValueError: + return datestr + + # TODO We could format to local date format '%x' here? + return d.strftime("%B %d, %Y") # simple Exception classes to catch specific errors class GlsaTypeException(Exception): @@ -432,7 +463,11 @@ class Glsa: self.DOM = xml.dom.minidom.parse(myfile) if not self.DOM.doctype: raise GlsaTypeException(None) - elif self.DOM.doctype.systemId != "http://www.gentoo.org/dtd/glsa.dtd": + elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa.dtd": + self.dtdversion = 0 + elif self.DOM.doctype.systemId == "http://www.gentoo.org/dtd/glsa-2.dtd": + self.dtdversion = 2 + else: raise GlsaTypeException(self.DOM.doctype.systemId) myroot = self.DOM.getElementsByTagName("glsa")[0] if self.type == "id" and myroot.getAttribute("id") != self.nr: @@ -441,8 +476,26 @@ class Glsa: # the simple (single, required, top-level, #PCDATA) tags first self.title = getText(myroot.getElementsByTagName("title")[0], format="strip") self.synopsis = getText(myroot.getElementsByTagName("synopsis")[0], format="strip") - self.announced = getText(myroot.getElementsByTagName("announced")[0], format="strip") - self.revised = getText(myroot.getElementsByTagName("revised")[0], format="strip") + self.announced = format_date(getText(myroot.getElementsByTagName("announced")[0], format="strip")) + + count = 1 + # Support both formats of revised: + # December 30, 2007: 02 + # 2007-12-30 + revisedEl = myroot.getElementsByTagName("revised")[0] + self.revised = getText(revisedEl, format="strip") + if (revisedEl.attributes.has_key("count")): + count = revisedEl.getAttribute("count") + elif (self.revised.find(":") >= 0): + (self.revised, count) = self.revised.split(":") + + self.revised = format_date(self.revised) + + try: + self.count = int(count) + except ValueError: + # TODO should this rais a GlsaFormatException? + self.count = 1 # now the optional and 0-n toplevel, #PCDATA tags and references try: @@ -499,7 +552,7 @@ class Glsa: outstream.write((width*"=")+"\n") outstream.write(wrap(self.synopsis, width, caption="Synopsis: ")+"\n") outstream.write("Announced on: %s\n" % self.announced) - outstream.write("Last revised on: %s\n\n" % self.revised) + outstream.write("Last revised on: %s : %02d\n\n" % (self.revised, self.count)) if self.glsatype == "ebuild": for k in self.packages.keys(): pkg = self.packages[k] @@ -565,7 +618,7 @@ class Glsa: @rtype: Boolean @returns: True if the GLSA was applied, False if not """ - return (self.nr in get_applied_glsas()) + return (self.nr in get_applied_glsas(self.config)) def inject(self): """ diff --git a/pym/portage/sets/__init__.py b/pym/portage/sets/__init__.py index f88e1b6ee..50ee455e6 100644 --- a/pym/portage/sets/__init__.py +++ b/pym/portage/sets/__init__.py @@ -33,8 +33,30 @@ class SetConfig(SafeConfigParser): self._parsed = False self.active = [] - def _parse(self): - if self._parsed: + def update(self, setname, options): + self.errors = [] + if not setname in self.psets: + options["name"] = setname + + # for the unlikely case that there is already a section with the requested setname + import random + while setname in self.sections(): + setname = "%08d" % random.randint(0, 10**10) + + self.add_section(setname) + for k, v in options.items(): + self.set(setname, k, v) + else: + section = self.psets[setname].creator + if self.has_option(section, "multiset") and self.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(): + self.set(section, k, v) + self._parse(update=True) + + def _parse(self, update=False): + if self._parsed and not update: return for sname in self.sections(): # find classname for current section, default to file based sets @@ -64,7 +86,7 @@ class SetConfig(SafeConfigParser): self.errors.append("Configuration error in section '%s': %s" % (sname, str(e))) continue for x in newsets: - if x in self.psets: + 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 self.has_option(sname, "world-candidate") and not self.getboolean(sname, "world-candidate"): @@ -78,7 +100,7 @@ class SetConfig(SafeConfigParser): setname = self.get(sname, "name") except NoOptionError: setname = sname - if setname in self.psets: + 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: @@ -101,14 +123,38 @@ class SetConfig(SafeConfigParser): def getSetAtoms(self, setname, ignorelist=None): myset = self.getSets()[setname] myatoms = myset.getAtoms() + + extend = set() + remove = set() + intersect = set() + if ignorelist is None: ignorelist = set() + if not setname in ignorelist: + if self.has_option(myset.creator, "extend"): + extend.update(self.get(myset.creator, "extend").split()) + if self.has_option(myset.creator, "remove"): + remove.update(self.get(myset.creator, "remove").split()) + if self.has_option(myset.creator, "intersect"): + intersect.update(self.get(myset.creator, "intersect").split()) + ignorelist.add(setname) for n in myset.getNonAtoms(): - if n[0] == SETPREFIX and n[1:] in self.psets: - if n[1:] not in ignorelist: - myatoms.update(self.getSetAtoms(n[1:], - ignorelist=ignorelist)) + if n.startswith(SETPREFIX) and n[len(SETPREFIX):] in self.psets: + extend.add(n[len(SETPREFIX):]) + + 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): -- 2.26.2