diff options
Diffstat (limited to 'portage_with_autodep/pym/repoman/checks.py')
-rw-r--r-- | portage_with_autodep/pym/repoman/checks.py | 391 |
1 files changed, 290 insertions, 101 deletions
diff --git a/portage_with_autodep/pym/repoman/checks.py b/portage_with_autodep/pym/repoman/checks.py index 77df603..85aa065 100644 --- a/portage_with_autodep/pym/repoman/checks.py +++ b/portage_with_autodep/pym/repoman/checks.py @@ -1,17 +1,21 @@ # repoman: Checks -# Copyright 2007-2012 Gentoo Foundation +# Copyright 2007-2013 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 """This module contains functions used in Repoman to ascertain the quality and correctness of an ebuild.""" +from __future__ import unicode_literals + +import codecs +from itertools import chain import re import time import repoman.errors as errors import portage from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \ eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \ - eapi_exports_AA, eapi_exports_KV + eapi_exports_AA class LineCheck(object): """Run a check on a line of an ebuild.""" @@ -66,7 +70,7 @@ class EbuildHeader(LineCheck): Copyright header errors CVS header errors License header errors - + Args: modification_year - Year the ebuild was last modified """ @@ -109,7 +113,7 @@ class EbuildWhitespace(LineCheck): ignore_line = re.compile(r'(^$)|(^(\t)*#)') ignore_comment = False leading_spaces = re.compile(r'^[\S\t]') - trailing_whitespace = re.compile(r'.*([\S]$)') + trailing_whitespace = re.compile(r'.*([\S]$)') def check(self, num, line): if self.leading_spaces.match(line) is None: @@ -159,6 +163,9 @@ class EbuildQuote(LineCheck): "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR", "GAMES_LOGDIR", "GAMES_BINDIR"] + # variables for multibuild.eclass + var_names += ["BUILD_DIR"] + var_names = "(%s)" % "|".join(var_names) var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \ var_names + '\W)') @@ -166,7 +173,7 @@ class EbuildQuote(LineCheck): r'\}?[^"\'\s]*(\s|$)') cond_begin = re.compile(r'(^|\s+)\[\[($|\\$|\s+)') cond_end = re.compile(r'(^|\s+)\]\]($|\\$|\s+)') - + def check(self, num, line): if self.var_reference.search(line) is None: return @@ -218,21 +225,13 @@ class EbuildAssignment(LineCheck): """Ensure ebuilds don't assign to readonly variables.""" repoman_check_name = 'variable.readonly' - readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=') - line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$') - ignore_line = re.compile(r'(^$)|(^(\t)*#)') - ignore_comment = False - - def __init__(self): - self.previous_line = None 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)): + if match is not None: e = errors.READONLY_ASSIGNMENT_ERROR - self.previous_line = line return e class Eapi3EbuildAssignment(EbuildAssignment): @@ -244,11 +243,11 @@ class Eapi3EbuildAssignment(EbuildAssignment): return eapi_supports_prefix(eapi) class EbuildNestedDie(LineCheck): - """Check ebuild for nested die statements (die statements in subshells""" - + """Check ebuild for nested die statements (die statements in subshells)""" + repoman_check_name = 'ebuild.nesteddie' nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b') - + def check(self, num, line): if self.nesteddie_re.match(line): return errors.NESTED_DIE_ERROR @@ -293,7 +292,7 @@ class EapiDefinition(LineCheck): _eapi_re = portage._pms_eapi_re def new(self, pkg): - self._cached_eapi = pkg.metadata['EAPI'] + self._cached_eapi = pkg.eapi self._parsed_eapi = None self._eapi_line_num = None @@ -331,24 +330,6 @@ class EbuildQuotedA(LineCheck): if match: return "Quoted \"${A}\" on line: %d" -class EprefixifyDefined(LineCheck): - """ Check that prefix.eclass is inherited if needed""" - - repoman_check_name = 'eprefixify.defined' - - _eprefixify_re = re.compile(r'\beprefixify\b') - _inherit_prefix_re = re.compile(r'^\s*inherit\s(.*\s)?prefix\b') - - def new(self, pkg): - self._prefix_inherited = False - - def check(self, num, line): - if self._eprefixify_re.search(line) is not None: - if not self._prefix_inherited: - return errors.EPREFIXIFY_MISSING_INHERIT - elif self._inherit_prefix_re.search(line) is not None: - self._prefix_inherited = True - class NoOffsetWithHelpers(LineCheck): """ Check that the image location, the alternate root offset, and the offset prefix (D, ROOT, ED, EROOT and EPREFIX) are not used with @@ -401,13 +382,18 @@ class InheritDeprecated(LineCheck): # deprecated eclass : new eclass (False if no new eclass) deprecated_classes = { "bash-completion": "bash-completion-r1", + "boost-utils": False, + "distutils": "distutils-r1", "gems": "ruby-fakegem", "git": "git-2", + "mono": "mono-env", "mozconfig-2": "mozconfig-3", "mozcoreconf": "mozcoreconf-2", "php-ext-pecl-r1": "php-ext-pecl-r2", "php-ext-source-r1": "php-ext-source-r2", "php-pear": "php-pear-r1", + "python": "python-r1 / python-single-r1 / python-any-r1", + "python-distutils-ng": "python-r1 + distutils-r1", "qt3": False, "qt4": "qt4-r2", "ruby": "ruby-ng", @@ -464,63 +450,189 @@ class InheritDeprecated(LineCheck): (eclass, replacement) del self._indirect_deprecated -class InheritAutotools(LineCheck): +class InheritEclass(LineCheck): """ - Make sure appropriate functions are called in - ebuilds that inherit autotools.eclass. - """ - - repoman_check_name = 'inherit.autotools' - _inherit_autotools_re = re.compile(r'^\s*inherit\s(.*\s)?autotools(\s|$)') - _autotools_funcs = ( - "eaclocal", "eautoconf", "eautoheader", - "eautomake", "eautoreconf", "_elibtoolize") - _autotools_func_re = re.compile(r'\b(' + \ - "|".join(_autotools_funcs) + r')\b') - # Exempt eclasses: - # git - An EGIT_BOOTSTRAP variable may be used to call one of - # the autotools functions. - # subversion - An ESVN_BOOTSTRAP variable may be used to call one of - # the autotools functions. - _exempt_eclasses = frozenset(["git", "subversion"]) - - def new(self, pkg): - self._inherit_autotools = None - self._autotools_func_call = None - self._disabled = self._exempt_eclasses.intersection(pkg.inherited) - - def check(self, num, line): - if self._disabled: - return - if self._inherit_autotools is None: - self._inherit_autotools = self._inherit_autotools_re.match(line) - if self._inherit_autotools is not None and \ - self._autotools_func_call is None: - self._autotools_func_call = self._autotools_func_re.search(line) - - def end(self): - if self._inherit_autotools and self._autotools_func_call is None: - yield 'no eauto* function called' + Base class for checking for missing inherits, as well as excess inherits. -class IUseUndefined(LineCheck): - """ - Make sure the ebuild defines IUSE (style guideline - says to define IUSE even when empty). + Args: + eclass: Set to the name of your eclass. + funcs: A tuple of functions that this eclass provides. + comprehensive: Is the list of functions complete? + exempt_eclasses: If these eclasses are inherited, disable the missing + inherit check. """ - repoman_check_name = 'IUSE.undefined' - _iuse_def_re = re.compile(r'^IUSE=.*') + def __init__(self, eclass, funcs=None, comprehensive=False, + exempt_eclasses=None, ignore_missing=False, **kwargs): + self._eclass = eclass + self._comprehensive = comprehensive + self._exempt_eclasses = exempt_eclasses + self._ignore_missing = ignore_missing + inherit_re = eclass + self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re) + # Match when the function is preceded only by leading whitespace, a + # shell operator such as (, {, |, ||, or &&, or optional variable + # setting(s). This prevents false positives in things like elog + # messages, as reported in bug #413285. + self._func_re = re.compile(r'(^|[|&{(])\s*(\w+=.*)?\b(' + '|'.join(funcs) + r')\b') def new(self, pkg): - self._iuse_def = None + self.repoman_check_name = 'inherit.missing' + # We can't use pkg.inherited because that tells us all the eclasses that + # have been inherited and not just the ones we inherit directly. + self._inherit = False + self._func_call = False + if self._exempt_eclasses is not None: + inherited = pkg.inherited + self._disabled = any(x in inherited for x in self._exempt_eclasses) + else: + self._disabled = False + self._eapi = pkg.eapi def check(self, num, line): - if self._iuse_def is None: - self._iuse_def = self._iuse_def_re.match(line) + if not self._inherit: + self._inherit = self._inherit_re.match(line) + if not self._inherit: + if self._disabled or self._ignore_missing: + return + s = self._func_re.search(line) + if s is not None: + func_name = s.group(3) + eapi_func = _eclass_eapi_functions.get(func_name) + if eapi_func is None or not eapi_func(self._eapi): + self._func_call = True + return ('%s.eclass is not inherited, ' + 'but "%s" found at line: %s') % \ + (self._eclass, func_name, '%d') + elif not self._func_call: + self._func_call = self._func_re.search(line) def end(self): - if self._iuse_def is None: - yield 'IUSE is not defined' + if not self._disabled and self._comprehensive and self._inherit and not self._func_call: + self.repoman_check_name = 'inherit.unused' + yield 'no function called from %s.eclass; please drop' % self._eclass + +_eclass_eapi_functions = { + "usex" : lambda eapi: eapi not in ("0", "1", "2", "3", "4", "4-python", "4-slot-abi") +} + +# eclasses that export ${ECLASS}_src_(compile|configure|install) +_eclass_export_functions = ( + 'ant-tasks', 'apache-2', 'apache-module', 'aspell-dict', + 'autotools-utils', 'base', 'bsdmk', 'cannadic', + 'clutter', 'cmake-utils', 'db', 'distutils', 'elisp', + 'embassy', 'emboss', 'emul-linux-x86', 'enlightenment', + 'font-ebdftopcf', 'font', 'fox', 'freebsd', 'freedict', + 'games', 'games-ggz', 'games-mods', 'gdesklets', + 'gems', 'gkrellm-plugin', 'gnatbuild', 'gnat', 'gnome2', + 'gnome-python-common', 'gnustep-base', 'go-mono', 'gpe', + 'gst-plugins-bad', 'gst-plugins-base', 'gst-plugins-good', + 'gst-plugins-ugly', 'gtk-sharp-module', 'haskell-cabal', + 'horde', 'java-ant-2', 'java-pkg-2', 'java-pkg-simple', + 'java-virtuals-2', 'kde4-base', 'kde4-meta', 'kernel-2', + 'latex-package', 'linux-mod', 'mozlinguas', 'myspell', + 'myspell-r2', 'mysql', 'mysql-v2', 'mythtv-plugins', + 'oasis', 'obs-service', 'office-ext', 'perl-app', + 'perl-module', 'php-ext-base-r1', 'php-ext-pecl-r2', + 'php-ext-source-r2', 'php-lib-r1', 'php-pear-lib-r1', + 'php-pear-r1', 'python-distutils-ng', 'python', + 'qt4-build', 'qt4-r2', 'rox-0install', 'rox', 'ruby', + 'ruby-ng', 'scsh', 'selinux-policy-2', 'sgml-catalog', + 'stardict', 'sword-module', 'tetex-3', 'tetex', + 'texlive-module', 'toolchain-binutils', 'toolchain', + 'twisted', 'vdr-plugin-2', 'vdr-plugin', 'vim', + 'vim-plugin', 'vim-spell', 'virtuoso', 'vmware', + 'vmware-mod', 'waf-utils', 'webapp', 'xemacs-elisp', + 'xemacs-packages', 'xfconf', 'x-modular', 'xorg-2', + 'zproduct' +) + +_eclass_info = { + 'autotools': { + 'funcs': ( + 'eaclocal', 'eautoconf', 'eautoheader', + 'eautomake', 'eautoreconf', '_elibtoolize', + 'eautopoint' + ), + 'comprehensive': True, + + # Exempt eclasses: + # git - An EGIT_BOOTSTRAP variable may be used to call one of + # the autotools functions. + # subversion - An ESVN_BOOTSTRAP variable may be used to call one of + # the autotools functions. + 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils') + }, + + 'eutils': { + 'funcs': ( + 'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop', + 'eumask_push', 'eumask_pop', 'epatch', 'epatch_user', + 'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex' + ), + 'comprehensive': False, + + # These are "eclasses are the whole ebuild" type thing. + 'exempt_eclasses': _eclass_export_functions, + }, + + 'flag-o-matic': { + 'funcs': ( + 'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags', + 'append-((ld|c(pp|xx)?))?flags', 'append-libs', + ), + 'comprehensive': False + }, + + 'libtool': { + 'funcs': ( + 'elibtoolize', + ), + 'comprehensive': True, + 'exempt_eclasses': ('autotools',) + }, + + 'multilib': { + 'funcs': ( + 'get_libdir', + ), + + # These are "eclasses are the whole ebuild" type thing. + 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool', + 'multilib-minimal'), + + 'comprehensive': False + }, + + 'multiprocessing': { + 'funcs': ( + 'makeopts_jobs', + ), + 'comprehensive': False + }, + + 'prefix': { + 'funcs': ( + 'eprefixify', + ), + 'comprehensive': True + }, + + 'toolchain-funcs': { + 'funcs': ( + 'gen_usr_ldscript', + ), + 'comprehensive': False + }, + + 'user': { + 'funcs': ( + 'enewuser', 'enewgroup', + 'egetent', 'egethome', 'egetshell', 'esethome' + ), + 'comprehensive': True + } +} class EMakeParallelDisabled(PhaseCheck): """Check for emake -j1 calls which disable parallelization.""" @@ -546,8 +658,8 @@ class NoAsNeeded(LineCheck): error = errors.NO_AS_NEEDED class PreserveOldLib(LineCheck): - """Check for calls to the preserve_old_lib function.""" - repoman_check_name = 'upstream.workaround' + """Check for calls to the deprecated preserve_old_lib function.""" + repoman_check_name = 'ebuild.minorsyn' re = re.compile(r'.*preserve_old_lib') error = errors.PRESERVE_OLD_LIB @@ -665,7 +777,7 @@ class PortageInternal(LineCheck): repoman_check_name = 'portage.internal' ignore_comment = True # Match when the command is preceded only by leading whitespace or a shell - # operator such as (, {, |, ||, or &&. This prevents false postives in + # operator such as (, {, |, ||, or &&. This prevents false positives in # things like elog messages, as reported in bug #413285. re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b') @@ -675,25 +787,64 @@ class PortageInternal(LineCheck): if m is not None: return ("'%s'" % m.group(2)) + " called on line: %d" -_constant_checks = tuple((c() for c in ( - EbuildHeader, EbuildWhitespace, EbuildBlankLine, EbuildQuote, - EbuildAssignment, Eapi3EbuildAssignment, EbuildUselessDodoc, - EbuildUselessCdS, EbuildNestedDie, - EbuildPatches, EbuildQuotedA, EapiDefinition, EprefixifyDefined, - ImplicitRuntimeDeps, InheritAutotools, InheritDeprecated, IUseUndefined, - EMakeParallelDisabled, EMakeParallelDisabledViaMAKEOPTS, NoAsNeeded, - DeprecatedBindnowFlags, SrcUnpackPatches, WantAutoDefaultValue, - SrcCompileEconf, Eapi3DeprecatedFuncs, NoOffsetWithHelpers, - Eapi4IncompatibleFuncs, Eapi4GoneVars, BuiltWithUse, - PreserveOldLib, SandboxAddpredict, PortageInternal, - DeprecatedUseq, DeprecatedHasq))) +class PortageInternalVariableAssignment(LineCheck): + repoman_check_name = 'portage.internal' + internal_assignment = re.compile(r'\s*(export\s+)?(EXTRA_ECONF|EXTRA_EMAKE)\+?=') + + def check(self, num, line): + match = self.internal_assignment.match(line) + e = None + if match is not None: + e = 'Assignment to variable %s' % match.group(2) + e += ' on line: %d' + return e + +_base_check_classes = (InheritEclass, LineCheck, PhaseCheck) +_constant_checks = None + +def _init(experimental_inherit=False): + + global _constant_checks, _eclass_info + + if not experimental_inherit: + # Emulate the old eprefixify.defined and inherit.autotools checks. + _eclass_info = { + 'autotools': { + 'funcs': ( + 'eaclocal', 'eautoconf', 'eautoheader', + 'eautomake', 'eautoreconf', '_elibtoolize', + 'eautopoint' + ), + 'comprehensive': True, + 'ignore_missing': True, + 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils') + }, + + 'prefix': { + 'funcs': ( + 'eprefixify', + ), + 'comprehensive': False + } + } + + _constant_checks = tuple(chain((v() for k, v in globals().items() + if isinstance(v, type) and issubclass(v, LineCheck) and + v not in _base_check_classes), + (InheritEclass(k, **portage._native_kwargs(kwargs)) + for k, kwargs in _eclass_info.items()))) _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$') _ignore_comment_re = re.compile(r'^\s*#') def run_checks(contents, pkg): + unicode_escape_codec = codecs.lookup('unicode_escape') + unicode_escape = lambda x: unicode_escape_codec.decode(x)[0] + if _constant_checks is None: + _init() checks = _constant_checks here_doc_delim = None + multiline = None for lc in checks: lc.new(pkg) @@ -707,14 +858,52 @@ def run_checks(contents, pkg): here_doc = _here_doc_re.match(line) if here_doc is not None: here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1)) + if here_doc_delim is not None: + continue + + # Unroll multiline escaped strings so that we can check things: + # inherit foo bar \ + # moo \ + # cow + # This will merge these lines like so: + # inherit foo bar moo cow + try: + # A normal line will end in the two bytes: <\> <\n>. So decoding + # that will result in python thinking the <\n> is being escaped + # and eat the single <\> which makes it hard for us to detect. + # Instead, strip the newline (which we know all lines have), and + # append a <0>. Then when python escapes it, if the line ended + # in a <\>, we'll end up with a <\0> marker to key off of. This + # shouldn't be a problem with any valid ebuild ... + line_escaped = unicode_escape(line.rstrip('\n') + '0') + except SystemExit: + raise + except: + # Who knows what kind of crazy crap an ebuild will have + # in it -- don't allow it to kill us. + line_escaped = line + if multiline: + # Chop off the \ and \n bytes from the previous line. + multiline = multiline[:-2] + line + if not line_escaped.endswith('\0'): + line = multiline + num = multinum + multiline = None + else: + continue + else: + if line_escaped.endswith('\0'): + multinum = num + multiline = line + continue - if here_doc_delim is None: - # We're not in a here-document. + if not line.endswith("#nowarn\n"): + # Finally we have a full line to parse. is_comment = _ignore_comment_re.match(line) is not None for lc in checks: if is_comment and lc.ignore_comment: continue - if lc.check_eapi(pkg.metadata['EAPI']): + if lc.check_eapi(pkg.eapi): ignore = lc.ignore_line if not ignore or not ignore.match(line): e = lc.check(num, line) |