From cae017cc9e7df6704f8f21b0597c0943996d75f5 Mon Sep 17 00:00:00 2001 From: Fabian Groffen Date: Thu, 15 Nov 2007 19:09:10 +0000 Subject: [PATCH] Merged from trunk 8481:8492 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit | 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 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 | | | 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 | 4 + bin/isolated-functions.sh | 7 +- bin/repoman | 39 ++--- pym/_emerge/__init__.py | 171 +++++++++++++--------- pym/portage/__init__.py | 4 +- pym/portage/sets/base.py | 29 +++- pym/portage/sets/files.py | 1 + pym/repoman/checks.py | 289 ++++++++++++++------------------------ 8 files changed, 265 insertions(+), 279 deletions(-) diff --git a/bin/emaint b/bin/emaint index 2bffd0f42..297050c5f 100755 --- a/bin/emaint +++ b/bin/emaint @@ -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 = { diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh index 3991285cf..1449528cd 100755 --- a/bin/isolated-functions.sh +++ b/bin/isolated-functions.sh @@ -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 diff --git a/bin/repoman b/bin/repoman index e2ec992b4..70a74ff2a 100755 --- a/bin/repoman +++ b/bin/repoman @@ -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 diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index bc0702155..b184b541b 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -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: diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 8eaddfac7..3004cc299 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -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 diff --git a/pym/portage/sets/base.py b/pym/portage/sets/base.py index f360632ed..50702cff3 100644 --- a/pym/portage/sets/base.py +++ b/pym/portage/sets/base.py @@ -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): diff --git a/pym/portage/sets/files.py b/pym/portage/sets/files.py index 242138710..655391cc4 100644 --- a/pym/portage/sets/files.py +++ b/pym/portage/sets/files.py @@ -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() diff --git a/pym/repoman/checks.py b/pym/repoman/checks.py index b6c430569..916003e7e 100644 --- a/pym/repoman/checks.py +++ b/pym/repoman/checks.py @@ -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) -- 2.26.2