Merged from trunk 8481:8492
authorFabian Groffen <grobian@gentoo.org>
Thu, 15 Nov 2007 19:09:10 +0000 (19:09 -0000)
committerFabian Groffen <grobian@gentoo.org>
Thu, 15 Nov 2007 19:09:10 +0000 (19:09 -0000)
   | 8482    | Clean up expansion of set arguments in                       |
   | zmedico | depgraph.select_files().                                     |

   | 8483    | Convert all the checks in repoman.checks to use a new        |
   | zmedico | LineCheck interface that takes a single line as an argument. |
   |         | This has at least a few of benefits: * Eliminates lots of    |
   |         | redundant code * Error messages are ordered by line number   |
   |         | across all checks The performance is slightly worse due to   |
   |         | the increased number of method calls, but it's not really    |
   |         | noticeable in comparison to the time consumed by dependency  |
   |         | checks. Thanks to Petteri Räty <betelgeuse@gentoo.org> for  |
   |         | the initial patch which I only made a few minor              |
   |         | modifications to.                                            |

   | 8484    | Fix WorldSet.cleanPackage() so that it properly preserves    |
   | zmedico | nonatoms (sets) in the world file.                           |

   | 8485    | Make the EbuildQuote.missing_quotes regex accept single      |
   | zmedico | quotes where it accepts double quotes in order to eliminate  |
   |         | some false positives. Thanks to Krzysiek Pawlik              |
   |         | <nelchael@gentoo.org> for reporting.                         |

   | 8486    | Wrap accesses to depgraph._set_atoms.findAtomForPackage() in |
   | zmedico | a new depgraph._get_arg_for_pkg() method.                    |

   | 8487    | Implement mapping of packages to instances of DependencyArg  |
   | zmedico | in depgraph._get_arg_for_pkg(). Among other things, this     |
   |         | provides a way to know whether or not a specific package has |
   |         | been specified as an argument, which determines whether or   |
   |         | not it can be removed from the graph during backtracking.    |

   | 8488    | Fix depgraph._get_arg_for_pkg() so that it compares package  |
   | zmedico | types to help ensure a proper match.                         |

   | 8489    | * Provide reasonable default values for Dependency priority  |
   | zmedico | and depth attributes. * Remove depgraph.create() since it's  |
   |         | no longer needed. * Remove depgraph._get_parent_sets() and   |
   |         | instead get the information from DependencyArg attributes.   |

   | 8490    | Make diefunc() clean up QA_INTERCEPTORS in case it is called |
   | zmedico | from inherit() since sed called below.                       |

   | 8491    | Bug #197797 - Set umask to 022 so that created files (such   |
   | zmedico | as the world file) have sane permissions.                    |

   | 8492    | Make the Package class emulate the interface of it's own     |
   | zmedico | digraph_node tuple so that the Package instance itself can   |
   |         | be added directly to the digraph.                            |

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

bin/emaint
bin/isolated-functions.sh
bin/repoman
pym/_emerge/__init__.py
pym/portage/__init__.py
pym/portage/sets/base.py
pym/portage/sets/files.py
pym/repoman/checks.py

index 2bffd0f420eb097dcaffdcc87816452d98f26609..297050c5ffdb3c8f5369f8ef1b44d3297d180cd0 100755 (executable)
@@ -414,6 +414,10 @@ class CleanResume(object):
 
 def emaint_main(myargv):
 
+       # Similar to emerge, emaint needs a default umask so that created
+       # files (such as the world file) have sane permissions.
+       os.umask(022)
+
        # TODO: Create a system that allows external modules to be added without
        #       the need for hard coding.
        modules = {
index 3991285cf5bd9094ebfab1e4ec3cd5c38a3ccf5e..1449528cd14a7e751cdf2ad4a15f1b7a1f121e38 100755 (executable)
@@ -41,7 +41,12 @@ dump_trace() {
 diefunc() {
        local funcname="$1" lineno="$2" exitcode="$3"
        shift 3
-
+       if [ -n "${QA_INTERCEPTORS}" ] ; then
+               # die was called from inside inherit. We need to clean up
+               # QA_INTERCEPTORS since sed is called below.
+               unset -f ${QA_INTERCEPTORS}
+               unset QA_INTERCEPTORS
+       fi
        local n filespacing=0 linespacing=0
        # setup spacing to make output easier to read
        for ((n = ${#FUNCNAME[@]} - 1; n >= 0; --n)); do
index e2ec992b4f60045ea2f3efa43bef61d4ab7e47eb..70a74ff2a25a78920438153583e36d6bd48c168a 100755 (executable)
@@ -24,7 +24,7 @@ from commands import getstatusoutput
 from fileinput import input
 from grp import getgrnam
 from itertools import izip
-from stat import S_ISDIR, ST_CTIME, ST_GID, ST_MTIME
+from stat import S_ISDIR, ST_CTIME
 
 try:
        import cPickle as pickle
@@ -44,13 +44,11 @@ except ImportError:
 del os.environ["PORTAGE_LEGACY_GLOBALS"]
 
 try:
-       from repoman.checks import EbuildWhitespace, EbuildHeader, EbuildQuote, \
-               EbuildAssignment, EbuildNestedDie, EbuildUselessDodoc, EbuildUselessCdS
+       from repoman.checks import run_checks
 except ImportError:
        from os import path as osp
        sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), 'pym'))
-       from repoman.checks import EbuildWhitespace, EbuildHeader, EbuildQuote, \
-               EbuildAssignment, EbuildNestedDie, EbuildUselessDodoc, EbuildUselessCdS
+       from repoman.checks import run_checks
 
 import portage.checksum
 import portage.const
@@ -1429,35 +1427,16 @@ for x in scanlist:
                                for mybad in mybadrestrict:
                                        fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
                # Syntax Checks
-               path = checkdir + '/' + y + '.ebuild'
-               myear = time.gmtime(os.stat(path)[ST_MTIME])[0]
-               f = open(path, 'rb')
+               relative_path = os.path.join(x, y + ".ebuild")
+               full_path = os.path.join(repodir, relative_path)
+               f = open(full_path, 'rb')
                try:
-                       contents = f.readlines()
+                       for check_name, e in run_checks(f, os.stat(full_path).st_mtime):
+                               stats[check_name] += 1
+                               fails[check_name].append(relative_path + ': %s' % e)
                finally:
                        f.close()
                        del f
-               for check in (EbuildWhitespace, EbuildQuote,
-                       EbuildAssignment, EbuildUselessDodoc, EbuildUselessCdS):
-                       c = check(contents)
-                       errors = c.Run()
-                       for e in errors:
-                               stats[c.repoman_check_name] += 1
-                               fails[c.repoman_check_name].append(x + '/' + y + '.ebuild: %s' % e[1] % e[0])
-               del check
-               check = EbuildHeader(contents, str(myear))
-               errors = check.Run()
-               for e in errors:
-                       stats[check.repoman_check_name] += 1
-                       fails[check.repoman_check_name].append(x + '/' + y + '.ebuild: %s' % e[1] % e[0])
-               del check
-               check = EbuildNestedDie(contents)
-               errors = check.Run()
-               for e in errors:
-                       stats[check.repoman_check_name] += 1
-                       fails[check.repoman_check_name].append(
-                               x + '/' + y + '.ebuild: %s' % e[1] % e[0])
-               del check, errors, path, contents, myear
 
                if options.force:
                        # The dep_check() calls are the most expensive QA test. If --force
index bc0702155619d140d2386be01c303396d1b92ba6..b184b541bd39eab03158f71ed8cf381f08010cd1 100644 (file)
@@ -922,7 +922,9 @@ def iter_atoms(deps):
 class Package(object):
        __slots__ = ("__weakref__", "built", "cpv", "depth",
                "installed", "metadata", "root", "onlydeps", "type_name",
-               "_digraph_node", "_slot_atom")
+               "slot_atom",
+               "digraph_node", "__eq__", "__hash__", "__str__",
+               "__len__", "__getitem__", "__iter__", "__contains__")
        def __init__(self, **kwargs):
                for myattr in self.__slots__:
                        if myattr == "__weakref__":
@@ -930,37 +932,42 @@ class Package(object):
                        myvalue = kwargs.get(myattr, None)
                        setattr(self, myattr, myvalue)
 
-       @property
-       def slot_atom(self):
-               if self._slot_atom is None:
-                       self._slot_atom = "%s:%s" % \
-                               (portage.cpv_getkey(self.cpv), self.metadata["SLOT"])
-               return self._slot_atom
-
-       @property
-       def digraph_node(self):
-               if self._digraph_node is None:
-                       status = "merge"
-                       if self.onlydeps or self.installed:
-                               status = "nomerge"
-                       self._digraph_node = (self.type_name, self.root, self.cpv, status)
-               return self._digraph_node
+               self.slot_atom = "%s:%s" % \
+                       (portage.cpv_getkey(self.cpv), self.metadata["SLOT"])
+
+               status = "merge"
+               if self.onlydeps or self.installed:
+                       status = "nomerge"
+               node = (self.type_name, self.root, self.cpv, status)
+               self.digraph_node = node
+               self.__eq__       = node.__eq__
+               self.__hash__     = node.__hash__
+               self.__str__      = node.__str__
+               self.__len__      = node.__len__
+               self.__getitem__  = node.__getitem__
+               self.__iter__     = node.__iter__
+               self.__contains__ = node.__contains__
 
 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):
@@ -978,6 +985,11 @@ class Dependency(object):
                        myvalue = kwargs.get(myattr, None)
                        setattr(self, myattr, myvalue)
 
+               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
@@ -1232,6 +1244,7 @@ class depgraph(object):
                # 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()
                self.blocker_digraph = digraph()
@@ -1369,8 +1382,7 @@ class depgraph(object):
                                myarg = None
                                if dep.root == self.target_root:
                                        try:
-                                               myarg = self._set_atoms.findAtomForPackage(
-                                                       dep_pkg.cpv, dep_pkg.metadata)
+                                               myarg = self._get_arg_for_pkg(dep_pkg)
                                        except portage.exception.InvalidDependString:
                                                if not dep_pkg.installed:
                                                        # This shouldn't happen since the package
@@ -1386,15 +1398,8 @@ class depgraph(object):
                                return 0
                return 1
 
-       def create(self, *args, **kwargs):
-               if not self._add_pkg(*args, **kwargs):
-                       return 0
-               return self._create_graph()
-
        def _add_pkg(self, pkg, myparent=None,
                priority=None, arg=None, depth=0):
-               if myparent is not None:
-                       myparent = myparent.digraph_node
                """
                Fills the digraph with nodes comprised of packages to merge.
                mybigkey is the package spec of the package to merge.
@@ -1421,7 +1426,7 @@ class depgraph(object):
 
                if not arg and myroot == self.target_root:
                        try:
-                               arg = self._set_atoms.findAtomForPackage(mykey, metadata)
+                               arg = self._get_arg_for_pkg(pkg)
                        except portage.exception.InvalidDependString, e:
                                if mytype != "installed":
                                        show_invalid_depstring_notice(tuple(mybigkey+["merge"]),
@@ -1683,16 +1688,30 @@ class depgraph(object):
                # dependencies.
                self._filtered_trees[root]["atoms"].clear()
 
-       def _get_parent_sets(self, root, atom):
-               refs = []
-               for set_name, atom_set in self._sets.iteritems():
-                       if set_name in refs:
-                               continue
-                       if atom in atom_set:
-                               refs.append(set_name)
-               if len(refs) > 1 and "args" in refs:
-                       refs.remove("args")
-               return refs
+       def _get_arg_for_pkg(self, pkg):
+               """
+               Return a matching DependencyArg instance for the given Package if
+               any exist, otherwise None. An attempt will be made to return the most
+               specific match (PackageArg type is the most specific).
+
+               This will raise an InvalidDependString exception if PROVIDE is invalid.
+               """
+               # TODO: add multiple $ROOT support
+               if pkg.root != self.target_root:
+                       return None
+               atom_arg_map = self._atom_arg_map
+               any_arg = None
+               for atom in self._set_atoms.iterAtomsForPackage(pkg):
+                       refs = atom_arg_map[(atom, pkg.root)]
+                       for arg in refs:
+                               if isinstance(arg, PackageArg):
+                                       # TODO: Implement a better comparison to ensure that
+                                       #       these two packages really are identical.
+                                       if arg.package.type_name != pkg.type_name:
+                                               continue
+                                       return arg
+                               any_arg = arg
+               return any_arg
 
        def select_files(self, myfiles):
                """Given a list of .tbz2s, .ebuilds sets, and deps, create the
@@ -1866,50 +1885,64 @@ class depgraph(object):
                # Create the "args" package set from atoms and
                # packages given as arguments.
                args_set = self._sets["args"]
-               expanded_args = []
                for arg in args:
-                       if isinstance(arg, SetArg):
-                               for atom in arg.set:
-                                       self._set_atoms.add(atom)
-                                       expanded_args.append(AtomArg(arg=arg.arg, atom=atom,
-                                               root_config=root_config))
+                       if not isinstance(arg, (AtomArg, PackageArg)):
                                continue
-                       expanded_args.append(arg)
                        myatom = arg.atom
                        if myatom in args_set:
                                continue
                        args_set.add(myatom)
-                       self._set_atoms.add(myatom)
                        if not oneshot:
                                myfavorites.append(myatom)
-               args = expanded_args
-               del expanded_args
-               pprovideddict = pkgsettings.pprovideddict
+               self._set_atoms.update(chain(*self._sets.itervalues()))
+               atom_arg_map = self._atom_arg_map
                for arg in args:
-                               atom = arg.atom
+                       for atom in arg.set:
+                               atom_key = (atom, myroot)
+                               refs = atom_arg_map.get(atom_key)
+                               if refs is None:
+                                       refs = []
+                                       atom_arg_map[atom_key] = refs
+                                       if arg not in refs:
+                                               refs.append(arg)
+               pprovideddict = pkgsettings.pprovideddict
+               # Order needs to be preserved since a feature of --nodeps
+               # is to allow the user to force a specific merge order.
+               args.reverse()
+               while args:
+                       arg = args.pop()
+                       for atom in arg.set:
                                try:
                                        pprovided = pprovideddict.get(portage.dep_getkey(atom))
                                        if pprovided and portage.match_from_list(atom, pprovided):
                                                # A provided package has been specified on the command line.
-                                               self._pprovided_args.append((arg.arg, arg.atom))
+                                               self._pprovided_args.append((arg, atom))
                                                continue
                                        if isinstance(arg, PackageArg):
-                                               if not self.create(arg.package, arg=arg.arg):
+                                               if not self._add_pkg(arg.package, arg=arg) or \
+                                                       not self._create_graph():
+                                                       sys.stderr.write(("\n\n!!! Problem resolving " + \
+                                                               "dependencies for %s\n") % arg.arg)
                                                        return 0, myfavorites
                                                continue
-                                       self._populate_filtered_repo(myroot, atom)
                                        pkg, existing_node = self._select_package(
                                                myroot, atom, onlydeps=onlydeps)
                                        if not pkg:
-                                               refs = self._get_parent_sets(myroot, atom)
-                                               if len(refs) == 1 and "args" in refs:
+                                               if not (isinstance(arg, SetArg) and \
+                                                       arg.name in ("system", "world")):
                                                        self._show_unsatisfied_dep(myroot, atom)
                                                        return 0, myfavorites
-                                               self._missing_args.append((arg.arg, arg.atom))
+                                               self._missing_args.append((arg, atom))
                                                continue
-                                       if not self.create(pkg):
-                                               sys.stderr.write(("\n\n!!! Problem resolving " + \
-                                                       "dependencies for %s\n") % atom)
+                                       self._dep_stack.append(Dependency(atom=atom, root=myroot))
+                                       if not self._create_graph():
+                                               if isinstance(arg, SetArg):
+                                                       sys.stderr.write(("\n\n!!! Problem resolving " + \
+                                                               "dependencies for %s from %s\n") % \
+                                                               (atom, arg.arg))
+                                               else:
+                                                       sys.stderr.write(("\n\n!!! Problem resolving " + \
+                                                               "dependencies for %s\n") % atom)
                                                return 0, myfavorites
                                except portage.exception.MissingSignature, e:
                                        portage.writemsg("\n\n!!! A missing gpg signature is preventing portage from calculating the\n")
@@ -2283,8 +2316,11 @@ class depgraph(object):
                                        myarg = None
                                        if root == self.target_root:
                                                try:
-                                                       myarg = self._set_atoms.findAtomForPackage(
-                                                               cpv, metadata)
+                                                       myarg = self._get_arg_for_pkg(
+                                                               Package(type_name=pkg_type, root=root,
+                                                                       cpv=cpv, metadata=metadata,
+                                                                       built=built, installed=installed,
+                                                                       onlydeps=onlydeps))
                                                except portage.exception.InvalidDependString:
                                                        if not installed:
                                                                # masked by corruption
@@ -3675,8 +3711,7 @@ class depgraph(object):
                        world_problems = False
                        if "world" in self._sets:
                                for arg, atom in self._missing_args:
-                                       if "world" in self._get_parent_sets(
-                                               self.target_root, atom):
+                                       if arg.name == "world":
                                                world_problems = True
                                                break
 
@@ -3690,15 +3725,21 @@ class depgraph(object):
                                " Ebuilds for the following packages are either all\n")
                        sys.stderr.write(colorize("BAD", "!!!") + \
                                " masked or don't exist:\n")
-                       sys.stderr.write(" ".join(arg[1] for arg in \
+                       sys.stderr.write(" ".join(atom for arg, atom in \
                                self._missing_args) + "\n")
 
                if self._pprovided_args:
                        arg_refs = {}
-                       for arg_atom in self._pprovided_args:
-                               arg, atom = arg_atom
+                       for arg, atom in self._pprovided_args:
+                               if isinstance(arg, SetArg):
+                                       parent = arg.name
+                                       arg_atom = (atom, atom)
+                               else:
+                                       parent = "args"
+                                       arg_atom = (arg.arg, atom)
                                refs = arg_refs.setdefault(arg_atom, [])
-                               refs.extend(self._get_parent_sets(self.target_root, atom))
+                               if parent not in refs:
+                                       refs.append(parent)
                        msg = []
                        msg.append(bad("\nWARNING: "))
                        if len(self._pprovided_args) > 1:
index 8eaddfac7e4f2440ce7637576ea0da187b05b5e0..3004cc2991dc56a4b623b0e94a112356e840b65d 100644 (file)
@@ -456,7 +456,9 @@ class digraph(object):
 
        def clone(self):
                clone = digraph()
-               clone.nodes = copy.deepcopy(self.nodes)
+               clone.nodes = {}
+               for k, v in self.nodes.iteritems():
+                       clone.nodes[k] = (v[0].copy(), v[1].copy())
                clone.order = self.order[:]
                return clone
 
index f360632ede3f3ef4e0fc583fa17751883f167679..50702cff39c9d97c951fe31d9bf538a5b99838ff 100644 (file)
@@ -2,7 +2,7 @@
 # Distributed under the terms of the GNU General Public License v2
 # $Id$
 
-from portage import flatten
+from portage import cpv_getkey, flatten
 from portage.dep import isvalidatom, match_from_list, \
      best_match_to_list, dep_getkey, use_reduce, paren_reduce
 from portage.exception import InvalidAtom
@@ -126,6 +126,33 @@ class PackageSet(object):
                                        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 = cpv_getkey(pkg.cpv)
+               self._load() # make sure the atoms are loaded
+               atoms = self._atommap.get(cp)
+               if atoms:
+                       for atom in atoms:
+                               if match_from_list(atom, cpv_slot_list):
+                                       yield atom
+               if not pkg.metadata["PROVIDE"]:
+                       return
+               provides = flatten(use_reduce(paren_reduce(pkg.metadata["PROVIDE"]),
+                       uselist=pkg.metadata["USE"].split()))
+               for provide in provides:
+                       provided_cp = dep_getkey(provide)
+                       atoms = self._atommap.get(provided_cp)
+                       if atoms:
+                               for atom in atoms:
+                                       if match_from_list(atom.replace(provided_cp, cp),
+                                               cpv_slot_list):
+                                               yield atom
+
 class EditablePackageSet(PackageSet):
 
        def update(self, atoms):
index 242138710b52894efd2edc8070197c888c40d24a..655391cc4db75231d0106756d8abec12f175304f 100644 (file)
@@ -169,6 +169,7 @@ class WorldSet(StaticFileSet):
                                #this doesn't match the package we're unmerging; keep it.
                                newworldlist.append(x)
 
+               newworldlist.extend(self._nonatoms)
                self.replace(newworldlist)
                self.unlock()
 
index b6c430569407d4df160b6f3b590e12f6ebe6680a..916003e7eb318749005e60dad4dedb494ceb7cb1 100644 (file)
@@ -11,38 +11,20 @@ from repoman.errors import COPYRIGHT_ERROR, LICENSE_ERROR, CVS_HEADER_ERROR, \
        LEADING_SPACES_ERROR, READONLY_ASSIGNMENT_ERROR, TRAILING_WHITESPACE_ERROR, \
        MISSING_QUOTES_ERROR, NESTED_DIE_ERROR, REDUNDANT_CD_S_ERROR
 
+class LineCheck(object):
+       """Run a check on a line of an ebuild."""
+       """A regular expression to determine whether to ignore the line"""
+       ignore_line = False
 
-class ContentCheckException(Exception):
-       """Parent class for exceptions relating to invalid content"""
-       
-       def __init__(self, str):
-               Exception.__init__(self, str)
-
-
-class ContentCheck(object):
-       """Given a file-like object, run checks over it's content
-       
-       Args:
-         contents - A file-like object, preferably a StringIO instance
-       
-       Raises:
-               ContentCheckException or a subclass
-       """
-       repoman_check_name = None
-       
-       def __init__(self, contents):
-               self.contents = contents
-               pass
-       
-       def Run(self):
-               """Run the check against the contents, return a sequence of errors
-                  of the form ((line, error),...)
-               """
+       def check(self, num, line):
+               """Run the check on line and return error if there is one"""
                pass
 
-
-class EbuildHeader(ContentCheck):
+class EbuildHeader(LineCheck):
        """Ensure ebuilds have proper headers
+               Copyright header errors
+               CVS header errors
+               License header errors
        
        Args:
                modification_year - Year the ebuild was last modified
@@ -56,35 +38,24 @@ class EbuildHeader(ContentCheck):
        gentoo_license = r'# Distributed under the terms of the GNU General Public License v2'
        cvs_header = re.compile(r'^#\s*\$Header.*\$$')
 
-       def __init__(self, contents, modification_year):
-               ContentCheck.__init__(self, contents)
-               self.modification_year = modification_year
+       def __init__(self, st_mtime):
+               self.modification_year = str(time.gmtime(st_mtime)[0])
                self.gentoo_copyright_re = re.compile(self.gentoo_copyright % self.modification_year)
 
-       def Run(self):
-               """Locate simple header mistakes in an ebuild
-               Copyright header errors
-               CVS header errors
-               License header errors
-               """
-               errors = []
-               for num, line in enumerate(self.contents):
-                       if num == 0:
-                               match = self.gentoo_copyright_re.match(line)
-                               if not match:
-                                       errors.append((num + 1, COPYRIGHT_ERROR))
-                       if num == 1 and line.strip() != self.gentoo_license:
-                               errors.append((num + 1, LICENSE_ERROR))
-                       if num == 2:
-                               match = self.cvs_header.match(line)
-                               if not match:
-                                       errors.append((num + 1, CVS_HEADER_ERROR))
-                       if num > 2:
-                               return errors
-               return errors
+       def check(self, num, line):
+               if num > 2:
+                       return
+               elif num == 0:
+                       if not self.gentoo_copyright_re.match(line):
+                               return COPYRIGHT_ERROR
+               elif num == 1 and line.strip() != self.gentoo_license:
+                       return LICENSE_ERROR
+               elif num == 2:
+                       if not self.cvs_header.match(line):
+                               return CVS_HEADER_ERROR
 
 
-class EbuildWhitespace(ContentCheck):
+class EbuildWhitespace(LineCheck):
        """Ensure ebuilds have proper whitespacing"""
 
        repoman_check_name = 'ebuild.minorsyn'
@@ -93,28 +64,13 @@ class EbuildWhitespace(ContentCheck):
        leading_spaces = re.compile(r'^[\S\t]')
        trailing_whitespace = re.compile(r'.*([\S]$)')  
 
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
-
-       def Run(self):
-               """Locate simple whitespace errors
-               Lines with leading spaces or trailing whitespace
-               """
-               errors = []
-               for num, line in enumerate(self.contents):
-                       match = self.ignore_line.match(line)
-                       if match:
-                               continue
-                       match = self.leading_spaces.match(line)
-                       if not match:
-                               errors.append((num + 1, LEADING_SPACES_ERROR))
-                       match = self.trailing_whitespace.match(line)
-                       if not match:
-                               errors.append((num + 1, TRAILING_WHITESPACE_ERROR))
-               return errors
-
+       def check(self, num, line):
+               if not self.leading_spaces.match(line):
+                       return LEADING_SPACES_ERROR
+               if not self.trailing_whitespace.match(line):
+                       return TRAILING_WHITESPACE_ERROR
 
-class EbuildQuote(ContentCheck):
+class EbuildQuote(LineCheck):
        """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
 
        repoman_check_name = 'ebuild.minorsyn'
@@ -122,63 +78,49 @@ class EbuildQuote(ContentCheck):
        var_names = r'(D|S|T|ROOT|FILESDIR|WORKDIR)'
        var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
                var_names + '\W)')
-       missing_quotes = re.compile(r'(\s|^)[^"\s]*\$\{?' + var_names + \
-               r'\}?[^"\s]*(\s|$)')
+       missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
+               r'\}?[^"\'\s]*(\s|$)')
        cond_begin =  re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
        cond_end =  re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
        
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
-
-       def Run(self):
-               """Locate simple errors in ebuilds:
-               Missing quotes around variables that may contain spaces
-               """
+       def check(self, num, line):
+               if self.var_reference.search(line) is None:
+                       return
+               # There can be multiple matches / violations on a single line. We
+               # have to make sure none of the matches are violators. Once we've
+               # found one violator, any remaining matches on the same line can
+               # be ignored.
+               pos = 0
+               while pos <= len(line) - 1:
+                       missing_quotes = self.missing_quotes.search(line, pos)
+                       if not missing_quotes:
+                               break
+                       # If the last character of the previous match is a whitespace
+                       # character, that character may be needed for the next
+                       # missing_quotes match, so search overlaps by 1 character.
+                       group = missing_quotes.group()
+                       pos = missing_quotes.end() - 1
+
+                       # Filter out some false positives that can
+                       # get through the missing_quotes regex.
+                       if self.var_reference.search(group) is None:
+                               continue
 
-               errors = []
-               for num, line in enumerate(self.contents):
-                       if self.ignore_line.match(line) is not None:
+                       # This is an attempt to avoid false positives without getting
+                       # too complex, while possibly allowing some (hopefully
+                       # unlikely) violations to slip through. We just assume
+                       # everything is correct if the there is a ' [[ ' or a ' ]] '
+                       # anywhere in the whole line (possibly continued over one
+                       # line).
+                       if self.cond_begin.search(line) is not None:
                                continue
-                       if self.var_reference.search(line) is None:
+                       if self.cond_end.search(line) is not None:
                                continue
-                       # There can be multiple matches / violations on a single line. We
-                       # have to make sure none of the matches are violators. Once we've
-                       # found one violator, any remaining matches on the same line can
-                       # be ignored.
-                       pos = 0
-                       while pos <= len(line) - 1:
-                               missing_quotes = self.missing_quotes.search(line, pos)
-                               if not missing_quotes:
-                                       break
-                               # If the last character of the previous match is a whitespace
-                               # character, that character may be needed for the next
-                               # missing_quotes match, so search overlaps by 1 character.
-                               group = missing_quotes.group()
-                               pos = missing_quotes.end() - 1
-
-                               # Filter out some false positives that can
-                               # get through the missing_quotes regex.
-                               if self.var_reference.search(group) is None:
-                                       continue
 
-                               # This is an attempt to avoid false positives without getting
-                               # too complex, while possibly allowing some (hopefully
-                               # unlikely) violations to slip through. We just assume
-                               # everything is correct if the there is a ' [[ ' or a ' ]] '
-                               # anywhere in the whole line (possibly continued over one
-                               # line).
-                               if self.cond_begin.search(line) is not None:
-                                       continue
-                               if self.cond_end.search(line) is not None:
-                                       continue
+                       # Any remaining matches on the same line can be ignored.
+                       return MISSING_QUOTES_ERROR
 
-                               errors.append((num + 1, MISSING_QUOTES_ERROR))
-                               # Any remaining matches on the same line can be ignored.
-                               break
-               return errors
-
-
-class EbuildAssignment(ContentCheck):
+class EbuildAssignment(LineCheck):
        """Ensure ebuilds don't assign to readonly variables."""
 
        repoman_check_name = 'variable.readonly'
@@ -187,80 +129,65 @@ class EbuildAssignment(ContentCheck):
        line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
        ignore_line = re.compile(r'(^$)|(^(\t)*#)')
 
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
-
-       def Run(self):
-               """Locate simple errors in ebuilds:
-               Assigning to read-only variables.
-               """
+       def __init__(self):
+               self.previous_line = None
 
-               errors = []
-               previous_line = None
-               # enumerate is 0 indexed, so add one when necesseary
-               for num, line in enumerate(self.contents):
-                       match = self.ignore_line.match(line)
-                       if match:
-                               continue
-                       match = self.readonly_assignment.match(line)
-                       if match and (not previous_line or not self.line_continuation.match(previous_line)):
-                               errors.append((num + 1, READONLY_ASSIGNMENT_ERROR))
-                       previous_line = line
-               return errors
+       def check(self, num, line):
+               match = self.readonly_assignment.match(line)
+               e = None
+               if match and (not self.previous_line or not self.line_continuation.match(self.previous_line)):
+                       e = READONLY_ASSIGNMENT_ERROR
+               self.previous_line = line
+               return e
 
-class EbuildNestedDie(ContentCheck):
+class EbuildNestedDie(LineCheck):
        """Check ebuild for nested die statements (die statements in subshells"""
        
        repoman_check_name = 'ebuild.nesteddie'
        nesteddie_re = re.compile(r'^[^#]*\([^)]*\bdie\b')
        
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
-
-       def Run(self):
-               errors = []
-               for num, line in enumerate(self.contents):
-                       match = self.nesteddie_re.match(line)
-                       if match:
-                               errors.append((num + 1, NESTED_DIE_ERROR))
-               return errors
+       def check(self, num, line):
+               if self.nesteddie_re.match(line):
+                       return NESTED_DIE_ERROR
 
-class EbuildUselessDodoc(ContentCheck):
+class EbuildUselessDodoc(LineCheck):
        """Check ebuild for useless files in dodoc arguments."""
        repoman_check_name = 'ebuild.minorsyn'
        uselessdodoc_re = re.compile(
                r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENSE)($|\s)')
 
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
+       def check(self, num, line):
+               match = self.uselessdodoc_re.match(line)
+               if match:
+                       return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
 
-       def Run(self):
-               errors = []
-               uselessdodoc_re = self.uselessdodoc_re
-               for num, line in enumerate(self.contents):
-                       match = uselessdodoc_re.match(line)
-                       if match:
-                               errors.append((num + 1, "Useless dodoc '%s'" % \
-                                       (match.group(2), ) + " on line: %d"))
-               return errors
-
-class EbuildUselessCdS(ContentCheck):
+class EbuildUselessCdS(LineCheck):
        """Check for redundant cd ${S} statements"""
        repoman_check_name = 'ebuild.minorsyn'
        method_re = re.compile(r'^\s*src_(compile|install|test)\s*\(\)')
        cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
 
-       def __init__(self, contents):
-               ContentCheck.__init__(self, contents)
-
-       def Run(self):
-               errors = []
-               check_next_line = False
-               for num, line in enumerate(self.contents):
-                       if check_next_line:
-                               check_next_line = False
-                               if self.cds_re.match(line):
-                                       errors.append((num + 1, REDUNDANT_CD_S_ERROR))
-                       elif self.method_re.match(line):
-                               check_next_line = True
-               return errors
+       def __init__(self):
+               self.check_next_line = False
+
+       def check(self, num, line):
+               if self.check_next_line:
+                       self.check_next_line = False
+                       if self.cds_re.match(line):
+                               return REDUNDANT_CD_S_ERROR
+               elif self.method_re.match(line):
+                       self.check_next_line = True
+
+def run_checks(contents, st_mtime):
+       checks = []
+       for c in (EbuildWhitespace, EbuildQuote, EbuildAssignment,
+                       EbuildUselessDodoc, EbuildUselessCdS, EbuildNestedDie):
+               checks.append(c())
+       checks.append(EbuildHeader(st_mtime))
+       for num, line in enumerate(contents):
+               for lc in checks:
+                       ignore = lc.ignore_line
+                       if not ignore or not ignore.match(line):
+                               e = lc.check(num, line)
+                               if e:
+                                       yield lc.repoman_check_name, e % (num + 1)