2 # Copyright 2007-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 """This module contains functions used in Repoman to ascertain the quality
6 and correctness of an ebuild."""
8 from __future__ import unicode_literals
11 from itertools import chain
14 import repoman.errors as errors
16 from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \
17 eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \
19 from portage.const import _ENABLE_INHERIT_CHECK
21 class LineCheck(object):
22 """Run a check on a line of an ebuild."""
23 """A regular expression to determine whether to ignore the line"""
25 """True if lines containing nothing more than comments with optional
26 leading whitespace should be ignored"""
32 def check_eapi(self, eapi):
33 """ returns if the check should be run in the given EAPI (default is True) """
36 def check(self, num, line):
37 """Run the check on line and return error if there is one"""
38 if self.re.match(line):
44 class PhaseCheck(LineCheck):
45 """ basic class for function detection """
47 func_end_re = re.compile(r'^\}$')
48 phases_re = re.compile('(%s)' % '|'.join((
49 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare',
50 'src_configure', 'src_compile', 'src_test', 'src_install',
51 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm',
55 def check(self, num, line):
56 m = self.phases_re.match(line)
58 self.in_phase = m.group(1)
59 if self.in_phase != '' and \
60 self.func_end_re.match(line) is not None:
63 return self.phase_check(num, line)
65 def phase_check(self, num, line):
66 """ override this function for your checks """
69 class EbuildHeader(LineCheck):
70 """Ensure ebuilds have proper headers
71 Copyright header errors
76 modification_year - Year the ebuild was last modified
79 repoman_check_name = 'ebuild.badheader'
81 gentoo_copyright = r'^# Copyright ((1999|2\d\d\d)-)?%s Gentoo Foundation$'
82 # Why a regex here, use a string match
83 # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
84 gentoo_license = '# Distributed under the terms of the GNU General Public License v2'
85 cvs_header = re.compile(r'^# \$Header: .*\$$')
86 ignore_comment = False
90 self.modification_year = r'2\d\d\d'
92 self.modification_year = str(time.gmtime(pkg.mtime)[0])
93 self.gentoo_copyright_re = re.compile(
94 self.gentoo_copyright % self.modification_year)
96 def check(self, num, line):
100 if not self.gentoo_copyright_re.match(line):
101 return errors.COPYRIGHT_ERROR
102 elif num == 1 and line.rstrip('\n') != self.gentoo_license:
103 return errors.LICENSE_ERROR
105 if not self.cvs_header.match(line):
106 return errors.CVS_HEADER_ERROR
109 class EbuildWhitespace(LineCheck):
110 """Ensure ebuilds have proper whitespacing"""
112 repoman_check_name = 'ebuild.minorsyn'
114 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
115 ignore_comment = False
116 leading_spaces = re.compile(r'^[\S\t]')
117 trailing_whitespace = re.compile(r'.*([\S]$)')
119 def check(self, num, line):
120 if self.leading_spaces.match(line) is None:
121 return errors.LEADING_SPACES_ERROR
122 if self.trailing_whitespace.match(line) is None:
123 return errors.TRAILING_WHITESPACE_ERROR
125 class EbuildBlankLine(LineCheck):
126 repoman_check_name = 'ebuild.minorsyn'
127 ignore_comment = False
128 blank_line = re.compile(r'^$')
131 self.line_is_blank = False
133 def check(self, num, line):
134 if self.line_is_blank and self.blank_line.match(line):
135 return 'Useless blank line on line: %d'
136 if self.blank_line.match(line):
137 self.line_is_blank = True
139 self.line_is_blank = False
142 if self.line_is_blank:
143 yield 'Useless blank line on last line'
145 class EbuildQuote(LineCheck):
146 """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
148 repoman_check_name = 'ebuild.minorsyn'
149 _message_commands = ["die", "echo", "eerror",
150 "einfo", "elog", "eqawarn", "ewarn"]
151 _message_re = re.compile(r'\s(' + "|".join(_message_commands) + \
153 _ignored_commands = ["local", "export"] + _message_commands
154 ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \
155 r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)')
156 ignore_comment = False
157 var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"]
160 var_names += ["ED", "EPREFIX", "EROOT"]
162 # variables for games.eclass
163 var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
164 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
165 "GAMES_LOGDIR", "GAMES_BINDIR"]
167 # variables for multibuild.eclass
168 var_names += ["BUILD_DIR"]
170 var_names = "(%s)" % "|".join(var_names)
171 var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
173 missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
174 r'\}?[^"\'\s]*(\s|$)')
175 cond_begin = re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
176 cond_end = re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
178 def check(self, num, line):
179 if self.var_reference.search(line) is None:
181 # There can be multiple matches / violations on a single line. We
182 # have to make sure none of the matches are violators. Once we've
183 # found one violator, any remaining matches on the same line can
186 while pos <= len(line) - 1:
187 missing_quotes = self.missing_quotes.search(line, pos)
188 if not missing_quotes:
190 # If the last character of the previous match is a whitespace
191 # character, that character may be needed for the next
192 # missing_quotes match, so search overlaps by 1 character.
193 group = missing_quotes.group()
194 pos = missing_quotes.end() - 1
196 # Filter out some false positives that can
197 # get through the missing_quotes regex.
198 if self.var_reference.search(group) is None:
201 # Filter matches that appear to be an
202 # argument to a message command.
203 # For example: false || ewarn "foo $WORKDIR/bar baz"
204 message_match = self._message_re.search(line)
205 if message_match is not None and \
206 message_match.start() < pos and \
207 message_match.end() > pos:
210 # This is an attempt to avoid false positives without getting
211 # too complex, while possibly allowing some (hopefully
212 # unlikely) violations to slip through. We just assume
213 # everything is correct if the there is a ' [[ ' or a ' ]] '
214 # anywhere in the whole line (possibly continued over one
216 if self.cond_begin.search(line) is not None:
218 if self.cond_end.search(line) is not None:
221 # Any remaining matches on the same line can be ignored.
222 return errors.MISSING_QUOTES_ERROR
225 class EbuildAssignment(LineCheck):
226 """Ensure ebuilds don't assign to readonly variables."""
228 repoman_check_name = 'variable.readonly'
229 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
231 def check(self, num, line):
232 match = self.readonly_assignment.match(line)
234 if match is not None:
235 e = errors.READONLY_ASSIGNMENT_ERROR
238 class Eapi3EbuildAssignment(EbuildAssignment):
239 """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables."""
241 readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=')
243 def check_eapi(self, eapi):
244 return eapi_supports_prefix(eapi)
246 class EbuildNestedDie(LineCheck):
247 """Check ebuild for nested die statements (die statements in subshells)"""
249 repoman_check_name = 'ebuild.nesteddie'
250 nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
252 def check(self, num, line):
253 if self.nesteddie_re.match(line):
254 return errors.NESTED_DIE_ERROR
257 class EbuildUselessDodoc(LineCheck):
258 """Check ebuild for useless files in dodoc arguments."""
259 repoman_check_name = 'ebuild.minorsyn'
260 uselessdodoc_re = re.compile(
261 r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENCE|LICENSE)($|\s)')
263 def check(self, num, line):
264 match = self.uselessdodoc_re.match(line)
266 return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
269 class EbuildUselessCdS(LineCheck):
270 """Check for redundant cd ${S} statements"""
271 repoman_check_name = 'ebuild.minorsyn'
272 method_re = re.compile(r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)')
273 cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
276 self.check_next_line = False
278 def check(self, num, line):
279 if self.check_next_line:
280 self.check_next_line = False
281 if self.cds_re.match(line):
282 return errors.REDUNDANT_CD_S_ERROR
283 elif self.method_re.match(line):
284 self.check_next_line = True
286 class EapiDefinition(LineCheck):
288 Check that EAPI assignment conforms to PMS section 7.3.1
289 (first non-comment, non-blank line).
291 repoman_check_name = 'EAPI.definition'
292 ignore_comment = True
293 _eapi_re = portage._pms_eapi_re
296 self._cached_eapi = pkg.eapi
297 self._parsed_eapi = None
298 self._eapi_line_num = None
300 def check(self, num, line):
301 if self._eapi_line_num is None and line.strip():
302 self._eapi_line_num = num + 1
303 m = self._eapi_re.match(line)
305 self._parsed_eapi = m.group(2)
308 if self._parsed_eapi is None:
309 if self._cached_eapi != "0":
310 yield "valid EAPI assignment must occur on or before line: %s" % \
312 elif self._parsed_eapi != self._cached_eapi:
313 yield ("bash returned EAPI '%s' which does not match "
314 "assignment on line: %s") % \
315 (self._cached_eapi, self._eapi_line_num)
317 class EbuildPatches(LineCheck):
318 """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety"""
319 repoman_check_name = 'ebuild.patches'
320 re = re.compile(r'^\s*PATCHES=[^\(]')
321 error = errors.PATCHES_ERROR
323 class EbuildQuotedA(LineCheck):
324 """Ensure ebuilds have no quoting around ${A}"""
326 repoman_check_name = 'ebuild.minorsyn'
327 a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
329 def check(self, num, line):
330 match = self.a_quoted.match(line)
332 return "Quoted \"${A}\" on line: %d"
334 class NoOffsetWithHelpers(LineCheck):
335 """ Check that the image location, the alternate root offset, and the
336 offset prefix (D, ROOT, ED, EROOT and EPREFIX) are not used with
339 repoman_check_name = 'variable.usedwithhelpers'
340 # Ignore matches in quoted strings like this:
341 # elog "installed into ${ROOT}usr/share/php5/apc/."
342 re = re.compile(r'^[^#"\']*\b(docinto|docompress|dodir|dohard|exeinto|fowners|fperms|insinto|into)\s+"?\$\{?(D|ROOT|ED|EROOT|EPREFIX)\b.*')
343 error = errors.NO_OFFSET_WITH_HELPERS
345 class ImplicitRuntimeDeps(LineCheck):
347 Detect the case where DEPEND is set and RDEPEND is unset in the ebuild,
348 since this triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4).
351 repoman_check_name = 'RDEPEND.implicit'
352 _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=')
355 self._rdepend = False
358 def check_eapi(self, eapi):
359 # Beginning with EAPI 4, there is no
360 # implicit RDEPEND=$DEPEND assignment
361 # to be concerned with.
362 return eapi_has_implicit_rdepend(eapi)
364 def check(self, num, line):
365 if not self._rdepend:
366 m = self._assignment_re.match(line)
369 elif m.group(1) == "RDEPEND":
371 elif m.group(1) == "DEPEND":
375 if self._depend and not self._rdepend:
376 yield 'RDEPEND is not explicitly assigned'
378 class InheritDeprecated(LineCheck):
379 """Check if ebuild directly or indirectly inherits a deprecated eclass."""
381 repoman_check_name = 'inherit.deprecated'
383 # deprecated eclass : new eclass (False if no new eclass)
384 deprecated_classes = {
385 "bash-completion": "bash-completion-r1",
386 "boost-utils": False,
387 "distutils": "distutils-r1",
388 "gems": "ruby-fakegem",
391 "mozconfig-2": "mozconfig-3",
392 "mozcoreconf": "mozcoreconf-2",
393 "php-ext-pecl-r1": "php-ext-pecl-r2",
394 "php-ext-source-r1": "php-ext-source-r2",
395 "php-pear": "php-pear-r1",
396 "python": "python-r1 / python-single-r1 / python-any-r1",
397 "python-distutils-ng": "python-r1 + distutils-r1",
401 "ruby-gnome2": "ruby-ng-gnome2",
402 "x-modular": "xorg-2",
405 _inherit_re = re.compile(r'^\s*inherit\s(.*)$')
409 self._indirect_deprecated = set(eclass for eclass in \
410 self.deprecated_classes if eclass in pkg.inherited)
412 def check(self, num, line):
414 direct_inherits = None
415 m = self._inherit_re.match(line)
417 direct_inherits = m.group(1)
419 direct_inherits = direct_inherits.split()
421 if not direct_inherits:
424 for eclass in direct_inherits:
425 replacement = self.deprecated_classes.get(eclass)
426 if replacement is None:
428 elif replacement is False:
429 self._indirect_deprecated.discard(eclass)
430 self._errors.append("please migrate from " + \
431 "'%s' (no replacement) on line: %d" % (eclass, num + 1))
433 self._indirect_deprecated.discard(eclass)
434 self._errors.append("please migrate from " + \
435 "'%s' to '%s' on line: %d" % \
436 (eclass, replacement, num + 1))
439 for error in self._errors:
443 for eclass in self._indirect_deprecated:
444 replacement = self.deprecated_classes[eclass]
445 if replacement is False:
446 yield "please migrate from indirect " + \
447 "inherit of '%s' (no replacement)" % (eclass,)
449 yield "please migrate from indirect " + \
450 "inherit of '%s' to '%s'" % \
451 (eclass, replacement)
452 del self._indirect_deprecated
454 class InheritEclass(LineCheck):
456 Base class for checking for missing inherits, as well as excess inherits.
459 eclass: Set to the name of your eclass.
460 funcs: A tuple of functions that this eclass provides.
461 comprehensive: Is the list of functions complete?
462 exempt_eclasses: If these eclasses are inherited, disable the missing
466 def __init__(self, eclass, funcs=None, comprehensive=False,
467 exempt_eclasses=None, ignore_missing=False, **kwargs):
468 self._eclass = eclass
469 self._comprehensive = comprehensive
470 self._exempt_eclasses = exempt_eclasses
471 self._ignore_missing = ignore_missing
473 self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
474 # Match when the function is preceded only by leading whitespace, a
475 # shell operator such as (, {, |, ||, or &&, or optional variable
476 # setting(s). This prevents false positives in things like elog
477 # messages, as reported in bug #413285.
478 self._func_re = re.compile(r'(^|[|&{(])\s*(\w+=.*)?\b(' + '|'.join(funcs) + r')\b')
481 self.repoman_check_name = 'inherit.missing'
482 # We can't use pkg.inherited because that tells us all the eclasses that
483 # have been inherited and not just the ones we inherit directly.
484 self._inherit = False
485 self._func_call = False
486 if self._exempt_eclasses is not None:
487 inherited = pkg.inherited
488 self._disabled = any(x in inherited for x in self._exempt_eclasses)
490 self._disabled = False
491 self._eapi = pkg.eapi
493 def check(self, num, line):
494 if not self._inherit:
495 self._inherit = self._inherit_re.match(line)
496 if not self._inherit:
497 if self._disabled or self._ignore_missing:
499 s = self._func_re.search(line)
501 func_name = s.group(3)
502 eapi_func = _eclass_eapi_functions.get(func_name)
503 if eapi_func is None or not eapi_func(self._eapi):
504 self._func_call = True
505 return ('%s.eclass is not inherited, '
506 'but "%s" found at line: %s') % \
507 (self._eclass, func_name, '%d')
508 elif not self._func_call:
509 self._func_call = self._func_re.search(line)
512 if not self._disabled and self._comprehensive and self._inherit and not self._func_call:
513 self.repoman_check_name = 'inherit.unused'
514 yield 'no function called from %s.eclass; please drop' % self._eclass
516 _eclass_eapi_functions = {
517 "usex" : lambda eapi: eapi not in ("0", "1", "2", "3", "4", "4-python", "4-slot-abi")
520 # eclasses that export ${ECLASS}_src_(compile|configure|install)
521 _eclass_export_functions = (
522 'ant-tasks', 'apache-2', 'apache-module', 'aspell-dict',
523 'autotools-utils', 'base', 'bsdmk', 'cannadic',
524 'clutter', 'cmake-utils', 'db', 'distutils', 'elisp',
525 'embassy', 'emboss', 'emul-linux-x86', 'enlightenment',
526 'font-ebdftopcf', 'font', 'fox', 'freebsd', 'freedict',
527 'games', 'games-ggz', 'games-mods', 'gdesklets',
528 'gems', 'gkrellm-plugin', 'gnatbuild', 'gnat', 'gnome2',
529 'gnome-python-common', 'gnustep-base', 'go-mono', 'gpe',
530 'gst-plugins-bad', 'gst-plugins-base', 'gst-plugins-good',
531 'gst-plugins-ugly', 'gtk-sharp-module', 'haskell-cabal',
532 'horde', 'java-ant-2', 'java-pkg-2', 'java-pkg-simple',
533 'java-virtuals-2', 'kde4-base', 'kde4-meta', 'kernel-2',
534 'latex-package', 'linux-mod', 'mozlinguas', 'myspell',
535 'myspell-r2', 'mysql', 'mysql-v2', 'mythtv-plugins',
536 'oasis', 'obs-service', 'office-ext', 'perl-app',
537 'perl-module', 'php-ext-base-r1', 'php-ext-pecl-r2',
538 'php-ext-source-r2', 'php-lib-r1', 'php-pear-lib-r1',
539 'php-pear-r1', 'python-distutils-ng', 'python',
540 'qt4-build', 'qt4-r2', 'rox-0install', 'rox', 'ruby',
541 'ruby-ng', 'scsh', 'selinux-policy-2', 'sgml-catalog',
542 'stardict', 'sword-module', 'tetex-3', 'tetex',
543 'texlive-module', 'toolchain-binutils', 'toolchain',
544 'twisted', 'vdr-plugin-2', 'vdr-plugin', 'vim',
545 'vim-plugin', 'vim-spell', 'virtuoso', 'vmware',
546 'vmware-mod', 'waf-utils', 'webapp', 'xemacs-elisp',
547 'xemacs-packages', 'xfconf', 'x-modular', 'xorg-2',
554 'eaclocal', 'eautoconf', 'eautoheader',
555 'eautomake', 'eautoreconf', '_elibtoolize',
558 'comprehensive': True,
561 # git - An EGIT_BOOTSTRAP variable may be used to call one of
562 # the autotools functions.
563 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of
564 # the autotools functions.
565 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
570 'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop',
571 'eumask_push', 'eumask_pop', 'epatch', 'epatch_user',
572 'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex'
574 'comprehensive': False,
576 # These are "eclasses are the whole ebuild" type thing.
577 'exempt_eclasses': _eclass_export_functions,
582 'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags',
583 'append-((ld|c(pp|xx)?))?flags', 'append-libs',
585 'comprehensive': False
592 'comprehensive': True,
593 'exempt_eclasses': ('autotools',)
601 # These are "eclasses are the whole ebuild" type thing.
602 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool',
605 'comprehensive': False
612 'comprehensive': False
619 'comprehensive': True
626 'comprehensive': False
631 'enewuser', 'enewgroup',
632 'egetent', 'egethome', 'egetshell', 'esethome'
634 'comprehensive': True
638 if not _ENABLE_INHERIT_CHECK:
639 # Since the InheritEclass check is experimental, in the stable branch
640 # we emulate the old eprefixify.defined and inherit.autotools checks.
644 'eaclocal', 'eautoconf', 'eautoheader',
645 'eautomake', 'eautoreconf', '_elibtoolize',
648 'comprehensive': True,
649 'ignore_missing': True,
650 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
657 'comprehensive': False
661 class EMakeParallelDisabled(PhaseCheck):
662 """Check for emake -j1 calls which disable parallelization."""
663 repoman_check_name = 'upstream.workaround'
664 re = re.compile(r'^\s*emake\s+.*-j\s*1\b')
665 error = errors.EMAKE_PARALLEL_DISABLED
667 def phase_check(self, num, line):
668 if self.in_phase == 'src_compile' or self.in_phase == 'src_install':
669 if self.re.match(line):
672 class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
673 """Check for MAKEOPTS=-j1 that disables parallelization."""
674 repoman_check_name = 'upstream.workaround'
675 re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b')
676 error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
678 class NoAsNeeded(LineCheck):
679 """Check for calls to the no-as-needed function."""
680 repoman_check_name = 'upstream.workaround'
681 re = re.compile(r'.*\$\(no-as-needed\)')
682 error = errors.NO_AS_NEEDED
684 class PreserveOldLib(LineCheck):
685 """Check for calls to the deprecated preserve_old_lib function."""
686 repoman_check_name = 'ebuild.minorsyn'
687 re = re.compile(r'.*preserve_old_lib')
688 error = errors.PRESERVE_OLD_LIB
690 class SandboxAddpredict(LineCheck):
691 """Check for calls to the addpredict function."""
692 repoman_check_name = 'upstream.workaround'
693 re = re.compile(r'(^|\s)addpredict\b')
694 error = errors.SANDBOX_ADDPREDICT
696 class DeprecatedBindnowFlags(LineCheck):
697 """Check for calls to the deprecated bindnow-flags function."""
698 repoman_check_name = 'ebuild.minorsyn'
699 re = re.compile(r'.*\$\(bindnow-flags\)')
700 error = errors.DEPRECATED_BINDNOW_FLAGS
702 class WantAutoDefaultValue(LineCheck):
703 """Check setting WANT_AUTO* to latest (default value)."""
704 repoman_check_name = 'ebuild.minorsyn'
705 _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest')
707 def check(self, num, line):
708 m = self._re.match(line)
710 return 'WANT_AUTO' + m.group(1) + \
711 ' redundantly set to default value "latest" on line: %d'
713 class SrcCompileEconf(PhaseCheck):
714 repoman_check_name = 'ebuild.minorsyn'
715 configure_re = re.compile(r'\s(econf|./configure)')
717 def check_eapi(self, eapi):
718 return eapi_has_src_prepare_and_src_configure(eapi)
720 def phase_check(self, num, line):
721 if self.in_phase == 'src_compile':
722 m = self.configure_re.match(line)
724 return ("'%s'" % m.group(1)) + \
725 " call should be moved to src_configure from line: %d"
727 class SrcUnpackPatches(PhaseCheck):
728 repoman_check_name = 'ebuild.minorsyn'
729 src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
731 def check_eapi(self, eapi):
732 return eapi_has_src_prepare_and_src_configure(eapi)
734 def phase_check(self, num, line):
735 if self.in_phase == 'src_unpack':
736 m = self.src_prepare_tools_re.search(line)
738 return ("'%s'" % m.group(1)) + \
739 " call should be moved to src_prepare from line: %d"
741 class BuiltWithUse(LineCheck):
742 repoman_check_name = 'ebuild.minorsyn'
743 re = re.compile(r'(^|.*\b)built_with_use\b')
744 error = errors.BUILT_WITH_USE
746 class DeprecatedUseq(LineCheck):
747 """Checks for use of the deprecated useq function"""
748 repoman_check_name = 'ebuild.minorsyn'
749 re = re.compile(r'(^|.*\b)useq\b')
750 error = errors.USEQ_ERROR
752 class DeprecatedHasq(LineCheck):
753 """Checks for use of the deprecated hasq function"""
754 repoman_check_name = 'ebuild.minorsyn'
755 re = re.compile(r'(^|.*\b)hasq\b')
756 error = errors.HASQ_ERROR
759 class Eapi3DeprecatedFuncs(LineCheck):
760 repoman_check_name = 'EAPI.deprecated'
761 deprecated_commands_re = re.compile(r'^\s*(check_license)\b')
763 def check_eapi(self, eapi):
764 return eapi not in ('0', '1', '2')
766 def check(self, num, line):
767 m = self.deprecated_commands_re.match(line)
769 return ("'%s'" % m.group(1)) + \
770 " has been deprecated in EAPI=3 on line: %d"
773 class Eapi4IncompatibleFuncs(LineCheck):
774 repoman_check_name = 'EAPI.incompatible'
775 banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
777 def check_eapi(self, eapi):
778 return not eapi_has_dosed_dohard(eapi)
780 def check(self, num, line):
781 m = self.banned_commands_re.match(line)
783 return ("'%s'" % m.group(1)) + \
784 " has been banned in EAPI=4 on line: %d"
786 class Eapi4GoneVars(LineCheck):
787 repoman_check_name = 'EAPI.incompatible'
788 undefined_vars_re = re.compile(r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))')
790 def check_eapi(self, eapi):
791 # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later.
792 return not eapi_exports_AA(eapi)
794 def check(self, num, line):
795 m = self.undefined_vars_re.match(line)
797 return ("variable '$%s'" % m.group(1)) + \
798 " is gone in EAPI=4 on line: %d"
800 class PortageInternal(LineCheck):
801 repoman_check_name = 'portage.internal'
802 ignore_comment = True
803 # Match when the command is preceded only by leading whitespace or a shell
804 # operator such as (, {, |, ||, or &&. This prevents false positives in
805 # things like elog messages, as reported in bug #413285.
806 re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b')
808 def check(self, num, line):
809 """Run the check on line and return error if there is one"""
810 m = self.re.match(line)
812 return ("'%s'" % m.group(2)) + " called on line: %d"
814 class PortageInternalVariableAssignment(LineCheck):
815 repoman_check_name = 'portage.internal'
816 internal_assignment = re.compile(r'\s*(export\s+)?(EXTRA_ECONF|EXTRA_EMAKE)\+?=')
818 def check(self, num, line):
819 match = self.internal_assignment.match(line)
821 if match is not None:
822 e = 'Assignment to variable %s' % match.group(2)
826 _base_check_classes = (InheritEclass, LineCheck, PhaseCheck)
827 _constant_checks = tuple(chain((v() for k, v in globals().items()
828 if isinstance(v, type) and issubclass(v, LineCheck) and v not in _base_check_classes),
829 (InheritEclass(k, **portage._native_kwargs(kwargs))
830 for k, kwargs in _eclass_info.items())))
832 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
833 _ignore_comment_re = re.compile(r'^\s*#')
835 def run_checks(contents, pkg):
836 unicode_escape_codec = codecs.lookup('unicode_escape')
837 unicode_escape = lambda x: unicode_escape_codec.decode(x)[0]
838 checks = _constant_checks
839 here_doc_delim = None
844 for num, line in enumerate(contents):
846 # Check if we're inside a here-document.
847 if here_doc_delim is not None:
848 if here_doc_delim.match(line):
849 here_doc_delim = None
850 if here_doc_delim is None:
851 here_doc = _here_doc_re.match(line)
852 if here_doc is not None:
853 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1))
854 if here_doc_delim is not None:
857 # Unroll multiline escaped strings so that we can check things:
861 # This will merge these lines like so:
862 # inherit foo bar moo cow
864 # A normal line will end in the two bytes: <\> <\n>. So decoding
865 # that will result in python thinking the <\n> is being escaped
866 # and eat the single <\> which makes it hard for us to detect.
867 # Instead, strip the newline (which we know all lines have), and
868 # append a <0>. Then when python escapes it, if the line ended
869 # in a <\>, we'll end up with a <\0> marker to key off of. This
870 # shouldn't be a problem with any valid ebuild ...
871 line_escaped = unicode_escape(line.rstrip('\n') + '0')
875 # Who knows what kind of crazy crap an ebuild will have
876 # in it -- don't allow it to kill us.
879 # Chop off the \ and \n bytes from the previous line.
880 multiline = multiline[:-2] + line
881 if not line_escaped.endswith('\0'):
888 if line_escaped.endswith('\0'):
893 if not line.endswith("#nowarn\n"):
894 # Finally we have a full line to parse.
895 is_comment = _ignore_comment_re.match(line) is not None
897 if is_comment and lc.ignore_comment:
899 if lc.check_eapi(pkg.eapi):
900 ignore = lc.ignore_line
901 if not ignore or not ignore.match(line):
902 e = lc.check(num, line)
904 yield lc.repoman_check_name, e % (num + 1)
910 yield lc.repoman_check_name, e