Merged from trunk -r11580:11593
authorFabian Groffen <grobian@gentoo.org>
Fri, 3 Oct 2008 16:50:31 +0000 (16:50 -0000)
committerFabian Groffen <grobian@gentoo.org>
Fri, 3 Oct 2008 16:50:31 +0000 (16:50 -0000)
   | 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 <rbu@gentoo.org>)                             |

   | 11589  | also accept glsa-2.dtd as valid for GLSAs (patch by Robert    |
   | genone | Buchholz <rbu@gentoo.org>)                                    |

   | 11590  | print dates in a consistent format (patch by Robert Buchholz  |
   | genone | <rbu@gentoo.org>)                                             |

   | 11591  | sort summarylist output (patch by Robert Buchholz             |
   | genone | <rbu@gentoo.org>)                                             |

   | 11592  | use summarylist for output of test mode when --verbose is     |
   | genone | given (patch by Robert Buchholz <rbu@gentoo.org>)             |

   | 11593  | add support for slot dependencies (original patch by Robert   |
   | genone | Buchholz <rbu@gentoo.org>)                                    |

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

bin/glsa-check
doc/config/sets.docbook
pym/_emerge/__init__.py
pym/portage/glsa.py
pym/portage/sets/__init__.py

index 23565fc6fd9d3fa7e567920b6c12d311c17dfe0b..97f393c8ee97e84eb44a184e3a5cb17ef849591b 100644 (file)
@@ -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)
index cfce6ecf686dd567db9e05936df89137fd378e40..78546c4cb053a174bb38af9f7090243a2e32dd4e 100644 (file)
                        isn't stricly required, but it should always be used as the default 
                        handler might be changed in future versions.</para></footnote>.
                        That option defines which handler class should be used to 
-                       create the set. Other universal options available for single sets are
-                       <varname>name</varname> (which is usually not needed as the name
+                       create the set. Other universal options available for single sets are:
+                       <itemizedlist>
+                       <listitem><varname>name</varname> (which is usually not needed as the name
                        of the set is generated from the section name if <varname>name</varname>
-                       is missing) and <varname>world-candidate</varname>, which determines if
-                       given package should be added to the <varname>world</varname> set. Some
-                       handler classes might require additional options for their configuration,
+                       is missing)</listitem>
+                       <listitem><varname>world-candidate</varname>, which determines if
+                       given package should be added to the <varname>world</varname> set</listitem>
+                       <listitem><varname>extend</varname> to include the contents of other package sets
+                       </listitem>
+                       <listitem><varname>remove</varname> to remove the contents of other package sets
+                       </listitem>
+                       <listitem><varname>intersect</varname> to only include packages that are also 
+                       included in one or more other package sets</listitem>
+                       </itemizedlist>
+                       Some handler classes might require additional options for their configuration,
                        these will be covered later in this chapter.
                        </para>
                        <para>
                        sets each section still requires the <varname>class</varname> option,
                        but to indicate that the section should generate multiple sets it's
                        also necessary to set the <varname>multiset</varname> option to 
-                       <parameter>true</parameter>. The <varname>world-candidate</varname>
-                       option is also supported like with single sets.
+                       <parameter>true</parameter>. The <varname>world-candidate</varname>,
+                       <varname>extend</varname>, <varname>remove</varname> and 
+                       <varname>intersect</varname> options are also supported like with 
+                       single sets (they'll apply to all sets generated by the section).
                        </para>
                        <para>
                        As it doesn't make much sense to specify a single name for multiple sets
                        </sect3>
                </sect2>
                
+               <sect2 id='config-set-classes-AgeSet'>
+               <title>portage.sets.dbapi.AgeSet</title>
+               <para>
+               Package sets created by this class will include installed packages that
+               have been installed before / after a given date.
+               </para>
+                       
+                       <sect3>
+                       <title>Single Set Configuration</title>
+                       <para>
+                       In single set configurations this class supports the following options:
+                       <itemizedlist>
+                       <listitem><varname>age</varname>: Optional, defaults to 7. Specifies 
+                               the number of days passed since installation to use as cut-off point.
+                       </listitem>
+                       <listitem><varname>mode</varname>: Optional, defaults to "older". Must 
+                               be either "older" or "newer" to select packages installed either 
+                               before resp. after the cut-off-date given by <varname>age</varname>.
+                               E.g. the defaults will select all installed packages that have been 
+                               installed more than one week ago.
+                       </listitem>
+                       </itemizedlist>
+                       </para>
+                       </sect3>
+               </sect2>
+               
                <sect2 id='config-set-classes-CategorySet'>
                <title>portage.sets.dbapi.CategorySet</title>
                <para>
index 0b404e712a62b9fc0e79da2abb31c65bab226d44..752b2eb679eb5957794c36888fa5a6268977b97e 100644 (file)
@@ -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)
index 4dc05f7e120cdbff85ae1ebbcd87f30557644631..33d3cf027cf6a796e50eb599d580bc14c19d3708 100644 (file)
@@ -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:
+               # <revised>December 30, 2007: 02</revised>
+               # <revised count="2">2007-12-30</revised>
+               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):
                """
index f88e1b6ee8fcab466b7fa9674d750cd51a0d0f06..50ee455e60c77d867d24487bdce89db53160b808 100644 (file)
@@ -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):