commit: c6c72edf59e98cac6d5d51d3b7348b29c1f4057c Author: Brian Dolbec <dolsen <AT> gentoo <DOT> org> AuthorDate: Sat Jul 15 01:10:13 2017 +0000 Commit: Brian Dolbec <dolsen <AT> gentoo <DOT> org> CommitDate: Sun Nov 26 17:32:20 2017 +0000 URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=c6c72edf
repoman: Remove the no longer used modules/scan/ebuild/checks.py repoman/pym/repoman/modules/scan/ebuild/checks.py | 1044 --------------------- 1 file changed, 1044 deletions(-) diff --git a/repoman/pym/repoman/modules/scan/ebuild/checks.py b/repoman/pym/repoman/modules/scan/ebuild/checks.py deleted file mode 100644 index de03bedd2..000000000 --- a/repoman/pym/repoman/modules/scan/ebuild/checks.py +++ /dev/null @@ -1,1044 +0,0 @@ -# -*- coding:utf-8 -*- -# repoman: Checks -# Copyright 2007-2017 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 - -from itertools import chain -import operator -import re -import time - -# import our initialized portage instance -from repoman._portage 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_has_pkg_pretend) - -from . import errors - - -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 - """True if lines containing nothing more than comments with optional - leading whitespace should be ignored""" - ignore_comment = True - - def new(self, pkg): - pass - - def check_eapi(self, eapi): - """Returns if check should be run in the given EAPI (default: True)""" - return True - - def check(self, num, line): - """Run the check on line and return error if there is one""" - if self.re.match(line): - return self.error - - def end(self): - pass - - -class PhaseCheck(LineCheck): - """ basic class for function detection """ - - func_end_re = re.compile(r'^\}$') - phases_re = re.compile('(%s)' % '|'.join(( - 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare', - 'src_configure', 'src_compile', 'src_test', 'src_install', - 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', - 'pkg_config'))) - in_phase = '' - - def check(self, num, line): - m = self.phases_re.match(line) - if m is not None: - self.in_phase = m.group(1) - if self.in_phase != '' and self.func_end_re.match(line) is not None: - self.in_phase = '' - - return self.phase_check(num, line) - - def phase_check(self, num, line): - """ override this function for your checks """ - pass - - -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 - """ - - repoman_check_name = 'ebuild.badheader' - - gentoo_copyright = r'^# Copyright ((1999|2\d\d\d)-)?%s Gentoo Foundation$' - gentoo_license = ( - '# Distributed under the terms' - ' of the GNU General Public License v2') - id_header_re = re.compile(r'.*\$(Id|Header)(:.*)?\$.*') - blank_line_re = re.compile(r'^$') - ignore_comment = False - - def new(self, pkg): - if pkg.mtime is None: - self.modification_year = r'2\d\d\d' - else: - self.modification_year = str(time.gmtime(pkg.mtime)[0]) - self.gentoo_copyright_re = re.compile( - self.gentoo_copyright % self.modification_year) - - def check(self, num, line): - if num > 2: - return - elif num == 0: - if not self.gentoo_copyright_re.match(line): - return errors.COPYRIGHT_ERROR - elif num == 1 and line.rstrip('\n') != self.gentoo_license: - return errors.LICENSE_ERROR - elif num == 2 and self.id_header_re.match(line): - return errors.ID_HEADER_ERROR - elif num == 2 and not self.blank_line_re.match(line): - return errors.NO_BLANK_LINE_ERROR - - -class EbuildWhitespace(LineCheck): - """Ensure ebuilds have proper whitespacing""" - - repoman_check_name = 'ebuild.minorsyn' - - ignore_line = re.compile(r'(^$)|(^(\t)*#)') - ignore_comment = False - leading_spaces = re.compile(r'^[\S\t]') - trailing_whitespace = re.compile(r'.*([\S]$)') - - def check(self, num, line): - if self.leading_spaces.match(line) is None: - return errors.LEADING_SPACES_ERROR - if self.trailing_whitespace.match(line) is None: - return errors.TRAILING_WHITESPACE_ERROR - - -class EbuildBlankLine(LineCheck): - repoman_check_name = 'ebuild.minorsyn' - ignore_comment = False - blank_line = re.compile(r'^$') - - def new(self, pkg): - self.line_is_blank = False - - def check(self, num, line): - if self.line_is_blank and self.blank_line.match(line): - return 'Useless blank line on line: %d' - if self.blank_line.match(line): - self.line_is_blank = True - else: - self.line_is_blank = False - - def end(self): - if self.line_is_blank: - yield 'Useless blank line on last line' - - -class EbuildQuote(LineCheck): - """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc...""" - - repoman_check_name = 'ebuild.minorsyn' - _message_commands = [ - "die", "echo", "eerror", "einfo", "elog", "eqawarn", "ewarn"] - _message_re = re.compile( - r'\s(' + "|".join(_message_commands) + r')\s+"[^"]*"\s*$') - _ignored_commands = ["local", "export"] + _message_commands - ignore_line = re.compile( - r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + - r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)') - ignore_comment = False - var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"] - - # EAPI=3/Prefix vars - var_names += ["ED", "EPREFIX", "EROOT"] - - # variables for games.eclass - var_names += [ - "Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR", - "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'\$(\{%s\}|%s\W)' % (var_names, var_names)) - missing_quotes = re.compile( - r'(\s|^)[^"\'\s]*\$\{?%s\}?[^"\'\s]*(\s|$)' % var_names) - 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 - # 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 - - # Filter matches that appear to be an - # argument to a message command. - # For example: false || ewarn "foo $WORKDIR/bar baz" - message_match = self._message_re.search(line) - if message_match is not None and \ - message_match.start() < pos and \ - message_match.end() > pos: - break - - # 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 errors.MISSING_QUOTES_ERROR - - -class EbuildAssignment(LineCheck): - """Ensure ebuilds don't assign to readonly variables.""" - - repoman_check_name = 'variable.readonly' - read_only_vars = 'A|CATEGORY|P|P[VNRF]|PVR|D|WORKDIR|FILESDIR|FEATURES|USE' - readonly_assignment = re.compile(r'^\s*(export\s+)?(%s)=' % read_only_vars) - - def check(self, num, line): - match = self.readonly_assignment.match(line) - e = None - if match is not None: - e = errors.READONLY_ASSIGNMENT_ERROR - return e - - -class Eapi3EbuildAssignment(EbuildAssignment): - """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables.""" - - readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=') - - def check_eapi(self, eapi): - return eapi_supports_prefix(eapi) - - -class EbuildNestedDie(LineCheck): - """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 - - -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|LICENCE|LICENSE)($|\s)') - - def check(self, num, line): - match = self.uselessdodoc_re.match(line) - if match: - return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d" - - -class EbuildUselessCdS(LineCheck): - """Check for redundant cd ${S} statements""" - repoman_check_name = 'ebuild.minorsyn' - _src_phases = r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)' - method_re = re.compile(_src_phases) - cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s') - - 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 errors.REDUNDANT_CD_S_ERROR - elif self.method_re.match(line): - self.check_next_line = True - - -class EapiDefinition(LineCheck): - """ - Check that EAPI assignment conforms to PMS section 7.3.1 - (first non-comment, non-blank line). - """ - repoman_check_name = 'EAPI.definition' - ignore_comment = True - _eapi_re = portage._pms_eapi_re - - def new(self, pkg): - self._cached_eapi = pkg.eapi - self._parsed_eapi = None - self._eapi_line_num = None - - def check(self, num, line): - if self._eapi_line_num is None and line.strip(): - self._eapi_line_num = num + 1 - m = self._eapi_re.match(line) - if m is not None: - self._parsed_eapi = m.group(2) - - def end(self): - if self._parsed_eapi is None: - if self._cached_eapi != "0": - yield "valid EAPI assignment must occur on or before line: %s" % \ - self._eapi_line_num - elif self._parsed_eapi != self._cached_eapi: - yield ( - "bash returned EAPI '%s' which does not match " - "assignment on line: %s" % - (self._cached_eapi, self._eapi_line_num)) - - -class EbuildPatches(LineCheck): - """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety""" - repoman_check_name = 'ebuild.patches' - re = re.compile(r'^\s*PATCHES=[^\(]') - error = errors.PATCHES_ERROR - - def check_eapi(self, eapi): - return eapi in ("0", "1", "2", "3", "4", "4-python", - "4-slot-abi", "5", "5-hdepend", "5-progress") - - -class EbuildQuotedA(LineCheck): - """Ensure ebuilds have no quoting around ${A}""" - - repoman_check_name = 'ebuild.minorsyn' - a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"') - - def check(self, num, line): - match = self.a_quoted.match(line) - if match: - return "Quoted \"${A}\" on line: %d" - - -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 - helpers """ - - repoman_check_name = 'variable.usedwithhelpers' - # Ignore matches in quoted strings like this: - # elog "installed into ${ROOT}usr/share/php5/apc/." - _install_funcs = ( - 'docinto|do(compress|dir|hard)' - '|exeinto|fowners|fperms|insinto|into') - _quoted_vars = 'D|ROOT|ED|EROOT|EPREFIX' - re = re.compile( - r'^[^#"\']*\b(%s)\s+"?\$\{?(%s)\b.*' % - (_install_funcs, _quoted_vars)) - error = errors.NO_OFFSET_WITH_HELPERS - - -class ImplicitRuntimeDeps(LineCheck): - """ - Detect the case where DEPEND is set and RDEPEND is unset in the ebuild, - since this triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4). - """ - - repoman_check_name = 'RDEPEND.implicit' - _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=') - - def new(self, pkg): - self._rdepend = False - self._depend = False - - def check_eapi(self, eapi): - # Beginning with EAPI 4, there is no - # implicit RDEPEND=$DEPEND assignment - # to be concerned with. - return eapi_has_implicit_rdepend(eapi) - - def check(self, num, line): - if not self._rdepend: - m = self._assignment_re.match(line) - if m is None: - pass - elif m.group(1) == "RDEPEND": - self._rdepend = True - elif m.group(1) == "DEPEND": - self._depend = True - - def end(self): - if self._depend and not self._rdepend: - yield 'RDEPEND is not explicitly assigned' - - -class InheritDeprecated(LineCheck): - """Check if ebuild directly or indirectly inherits a deprecated eclass.""" - - repoman_check_name = 'inherit.deprecated' - - # deprecated eclass : new eclass (False if no new eclass) - deprecated_eclasses = { - "autotools-multilib": "multilib-minimal", - "autotools-utils": False, - "base": False, - "bash-completion": "bash-completion-r1", - "boost-utils": False, - "clutter": "gnome2", - "confutils": False, - "distutils": "distutils-r1", - "fdo-mime": "xdg-utils", - "games": False, - "gems": "ruby-fakegem", - "git-2": "git-r3", - "gpe": False, - "gst-plugins-bad": "gstreamer", - "gst-plugins-base": "gstreamer", - "gst-plugins-good": "gstreamer", - "gst-plugins-ugly": "gstreamer", - "gst-plugins10": "gstreamer", - "mono": "mono-env", - "python": "python-r1 / python-single-r1 / python-any-r1", - "ruby": "ruby-ng", - "x-modular": "xorg-2", - "xfconf": False, - } - - _inherit_re = re.compile(r'^\s*inherit\s(.*)$') - - def new(self, pkg): - self._errors = [] - - def check(self, num, line): - direct_inherits = None - m = self._inherit_re.match(line) - if m is not None: - direct_inherits = m.group(1) - if direct_inherits: - direct_inherits = direct_inherits.split() - - if not direct_inherits: - return - - for eclass in direct_inherits: - replacement = self.deprecated_eclasses.get(eclass) - if replacement is None: - pass - elif replacement is False: - self._errors.append( - "please migrate from " - "'%s' (no replacement) on line: %d" % (eclass, num + 1)) - else: - self._errors.append( - "please migrate from " - "'%s' to '%s' on line: %d" % (eclass, replacement, num + 1)) - - def end(self): - for error in self._errors: - yield error - del self._errors - - - -class InheritEclass(LineCheck): - """ - Base class for checking for missing inherits, as well as excess inherits. - - 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. - """ - - 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.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 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 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 - -_usex_supported_eapis = ("0", "1", "2", "3", "4", "4-python", "4-slot-abi") -_in_iuse_supported_eapis = ("0", "1", "2", "3", "4", "4-python", "4-slot-abi", - "5", "5-hdepend", "5-progress") -_get_libdir_supported_eapis = _in_iuse_supported_eapis -_eclass_eapi_functions = { - "usex": lambda eapi: eapi not in _usex_supported_eapis, - "in_iuse": lambda eapi: eapi not in _in_iuse_supported_eapis, - "get_libdir": lambda eapi: eapi not in _get_libdir_supported_eapis, -} - -# 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.""" - repoman_check_name = 'upstream.workaround' - re = re.compile(r'^\s*emake\s+.*-j\s*1\b') - error = errors.EMAKE_PARALLEL_DISABLED - - def phase_check(self, num, line): - if self.in_phase == 'src_compile' or self.in_phase == 'src_install': - if self.re.match(line): - return self.error - - -class EMakeParallelDisabledViaMAKEOPTS(LineCheck): - """Check for MAKEOPTS=-j1 that disables parallelization.""" - repoman_check_name = 'upstream.workaround' - re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b') - error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS - - -class UriUseHttps(LineCheck): - """Check that we use https:// for known good sites.""" - repoman_check_name = 'uri.https' - _SITES = ( - '([-._a-zA-Z0-9]*\.)?apache\.org', - '((alioth|packages(\.qa)?|people|www)\.)?debian\.org', - # Most FDO sites support https, but not all (like tango). - # List the most common ones here for now. - '((anongit|bugs|cgit|dri|patchwork|people|specifications|www|xcb|xorg)\.)?freedesktop\.org', - '((bugs|dev|wiki|www)\.)?gentoo\.org', - '((wiki)\.)?github\.(io|com)', - 'savannah\.(non)?gnu\.org', - '((gcc|www)\.)?gnu\.org', - 'curl\.haxx\.se', - '((bugzilla|git|mirrors|patchwork|planet|www(\.wiki)?)\.)?kernel\.org', - '((bugs|wiki|www)\.)?linuxfoundation\.org', - '((docs|pypi|www)\.)?python\.org', - '(sf|sourceforge)\.net', - '(www\.)?(enlightenment|sourceware|x)\.org', - ) - # Try to anchor the end of the URL so we don't get false positives - # with http://github.com.foo.bar.com/. Unlikely, but possible. - re = re.compile(r'.*\bhttp://(%s)(\s|["\'/]|$)' % r'|'.join(_SITES)) - error = errors.URI_HTTPS - - -class NoAsNeeded(LineCheck): - """Check for calls to the no-as-needed function.""" - repoman_check_name = 'upstream.workaround' - re = re.compile(r'.*\$\(no-as-needed\)') - error = errors.NO_AS_NEEDED - - -class PreserveOldLib(LineCheck): - """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 - - -class SandboxAddpredict(LineCheck): - """Check for calls to the addpredict function.""" - repoman_check_name = 'upstream.workaround' - re = re.compile(r'(^|\s)addpredict\b') - error = errors.SANDBOX_ADDPREDICT - - -class DeprecatedBindnowFlags(LineCheck): - """Check for calls to the deprecated bindnow-flags function.""" - repoman_check_name = 'ebuild.minorsyn' - re = re.compile(r'.*\$\(bindnow-flags\)') - error = errors.DEPRECATED_BINDNOW_FLAGS - - -class WantAutoDefaultValue(LineCheck): - """Check setting WANT_AUTO* to latest (default value).""" - repoman_check_name = 'ebuild.minorsyn' - _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest') - - def check(self, num, line): - m = self._re.match(line) - if m is not None: - return 'WANT_AUTO' + m.group(1) + \ - ' redundantly set to default value "latest" on line: %d' - - -class SrcCompileEconf(PhaseCheck): - repoman_check_name = 'ebuild.minorsyn' - configure_re = re.compile(r'\s(econf|./configure)') - - def check_eapi(self, eapi): - return eapi_has_src_prepare_and_src_configure(eapi) - - def phase_check(self, num, line): - if self.in_phase == 'src_compile': - m = self.configure_re.match(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " call should be moved to src_configure from line: %d" - - -class SrcUnpackPatches(PhaseCheck): - repoman_check_name = 'ebuild.minorsyn' - src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s') - - def check_eapi(self, eapi): - return eapi_has_src_prepare_and_src_configure(eapi) - - def phase_check(self, num, line): - if self.in_phase == 'src_unpack': - m = self.src_prepare_tools_re.search(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " call should be moved to src_prepare from line: %d" - - -class BuiltWithUse(LineCheck): - repoman_check_name = 'ebuild.minorsyn' - re = re.compile(r'(^|.*\b)built_with_use\b') - error = errors.BUILT_WITH_USE - - -class DeprecatedUseq(LineCheck): - """Checks for use of the deprecated useq function""" - repoman_check_name = 'ebuild.minorsyn' - re = re.compile(r'(^|.*\b)useq\b') - error = errors.USEQ_ERROR - - -class DeprecatedHasq(LineCheck): - """Checks for use of the deprecated hasq function""" - repoman_check_name = 'ebuild.minorsyn' - re = re.compile(r'(^|.*\b)hasq\b') - error = errors.HASQ_ERROR - - -# EAPI <2 checks -class UndefinedSrcPrepareSrcConfigurePhases(LineCheck): - repoman_check_name = 'EAPI.incompatible' - src_configprepare_re = re.compile(r'\s*(src_configure|src_prepare)\s*\(\)') - - def check_eapi(self, eapi): - return not eapi_has_src_prepare_and_src_configure(eapi) - - def check(self, num, line): - m = self.src_configprepare_re.match(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " phase is not defined in EAPI < 2 on line: %d" - - -# EAPI-3 checks -class Eapi3DeprecatedFuncs(LineCheck): - repoman_check_name = 'EAPI.deprecated' - deprecated_commands_re = re.compile(r'^\s*(check_license)\b') - - def check_eapi(self, eapi): - return eapi not in ('0', '1', '2') - - def check(self, num, line): - m = self.deprecated_commands_re.match(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " has been deprecated in EAPI=3 on line: %d" - - -# EAPI <4 checks -class UndefinedPkgPretendPhase(LineCheck): - repoman_check_name = 'EAPI.incompatible' - pkg_pretend_re = re.compile(r'\s*(pkg_pretend)\s*\(\)') - - def check_eapi(self, eapi): - return not eapi_has_pkg_pretend(eapi) - - def check(self, num, line): - m = self.pkg_pretend_re.match(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " phase is not defined in EAPI < 4 on line: %d" - - -# EAPI-4 checks -class Eapi4IncompatibleFuncs(LineCheck): - repoman_check_name = 'EAPI.incompatible' - banned_commands_re = re.compile(r'^\s*(dosed|dohard)') - - def check_eapi(self, eapi): - return not eapi_has_dosed_dohard(eapi) - - def check(self, num, line): - m = self.banned_commands_re.match(line) - if m is not None: - return ("'%s'" % m.group(1)) + \ - " has been banned in EAPI=4 on line: %d" - - -class Eapi4GoneVars(LineCheck): - repoman_check_name = 'EAPI.incompatible' - undefined_vars_re = re.compile( - r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))') - - def check_eapi(self, eapi): - # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later. - return not eapi_exports_AA(eapi) - - def check(self, num, line): - m = self.undefined_vars_re.match(line) - if m is not None: - return ("variable '$%s'" % m.group(1)) + \ - " is gone in EAPI=4 on line: %d" - - -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 positives in - # things like elog messages, as reported in bug #413285. - - internal_portage_func_or_var = ( - 'ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib') - re = re.compile( - r'^(\s*|.*[|&{(]+\s*)\b(%s)\b' % internal_portage_func_or_var) - - def check(self, num, line): - """Run the check on line and return error if there is one""" - m = self.re.match(line) - if m is not None: - return ("'%s'" % m.group(2)) + " called on line: %d" - - -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 - - -class EbuildNonRelativeDosym(LineCheck): - """Check ebuild for dosym using absolute paths instead of relative.""" - repoman_check_name = 'ebuild.absdosym' - regex = re.compile( - r'^\s*dosym\s+["\']?(/(bin|etc|lib|opt|sbin|srv|usr|var)\S*)') - - def check(self, num, line): - match = self.regex.match(line) - if match: - return "dosym '%s'... could use relative path" % (match.group(1), ) + " on line: %d" - - -_base_check_classes = (InheritEclass, LineCheck, PhaseCheck) -_constant_checks = None - - -def checks_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, **kwargs) - for k, kwargs in _eclass_info.items()))) - - -_here_doc_re = re.compile(r'.*<<[-]?(\w+)\s*(>\s*\S+\s*)?$') -_ignore_comment_re = re.compile(r'^\s*#') -_continuation_re = re.compile(r'(\\)*$') - - -def run_checks(contents, pkg): - if _constant_checks is None: - checks_init() - checks = _constant_checks - here_doc_delim = None - multiline = None - - for lc in checks: - lc.new(pkg) - - multinum = 0 - for num, line in enumerate(contents): - - # Check if we're inside a here-document. - if here_doc_delim is not None: - if here_doc_delim.match(line): - here_doc_delim = None - if here_doc_delim is None: - 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 - # A line ending with an even number of backslashes does not count, - # because the last backslash is escaped. Therefore, search for an - # odd number of backslashes. - line_escaped = operator.sub(*_continuation_re.search(line).span()) % 2 == 1 - if multiline: - # Chop off the \ and \n bytes from the previous line. - multiline = multiline[:-2] + line - if not line_escaped: - line = multiline - num = multinum - multiline = None - else: - continue - else: - if line_escaped: - multinum = num - multiline = line - continue - - 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.eapi): - 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) - - for lc in checks: - i = lc.end() - if i is not None: - for e in i: - yield lc.repoman_check_name, e