2 # Copyright 2007-2012 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."""
9 from itertools import chain
12 import repoman.errors as errors
14 from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \
15 eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \
17 from portage.const import _ENABLE_INHERIT_CHECK
19 class LineCheck(object):
20 """Run a check on a line of an ebuild."""
21 """A regular expression to determine whether to ignore the line"""
23 """True if lines containing nothing more than comments with optional
24 leading whitespace should be ignored"""
30 def check_eapi(self, eapi):
31 """ returns if the check should be run in the given EAPI (default is True) """
34 def check(self, num, line):
35 """Run the check on line and return error if there is one"""
36 if self.re.match(line):
42 class PhaseCheck(LineCheck):
43 """ basic class for function detection """
45 func_end_re = re.compile(r'^\}$')
46 phases_re = re.compile('(%s)' % '|'.join((
47 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare',
48 'src_configure', 'src_compile', 'src_test', 'src_install',
49 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm',
53 def check(self, num, line):
54 m = self.phases_re.match(line)
56 self.in_phase = m.group(1)
57 if self.in_phase != '' and \
58 self.func_end_re.match(line) is not None:
61 return self.phase_check(num, line)
63 def phase_check(self, num, line):
64 """ override this function for your checks """
67 class EbuildHeader(LineCheck):
68 """Ensure ebuilds have proper headers
69 Copyright header errors
74 modification_year - Year the ebuild was last modified
77 repoman_check_name = 'ebuild.badheader'
79 gentoo_copyright = r'^# Copyright ((1999|2\d\d\d)-)?%s Gentoo Foundation$'
80 # Why a regex here, use a string match
81 # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
82 gentoo_license = '# Distributed under the terms of the GNU General Public License v2'
83 cvs_header = re.compile(r'^# \$Header: .*\$$')
84 ignore_comment = False
88 self.modification_year = r'2\d\d\d'
90 self.modification_year = str(time.gmtime(pkg.mtime)[0])
91 self.gentoo_copyright_re = re.compile(
92 self.gentoo_copyright % self.modification_year)
94 def check(self, num, line):
98 if not self.gentoo_copyright_re.match(line):
99 return errors.COPYRIGHT_ERROR
100 elif num == 1 and line.rstrip('\n') != self.gentoo_license:
101 return errors.LICENSE_ERROR
103 if not self.cvs_header.match(line):
104 return errors.CVS_HEADER_ERROR
107 class EbuildWhitespace(LineCheck):
108 """Ensure ebuilds have proper whitespacing"""
110 repoman_check_name = 'ebuild.minorsyn'
112 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
113 ignore_comment = False
114 leading_spaces = re.compile(r'^[\S\t]')
115 trailing_whitespace = re.compile(r'.*([\S]$)')
117 def check(self, num, line):
118 if self.leading_spaces.match(line) is None:
119 return errors.LEADING_SPACES_ERROR
120 if self.trailing_whitespace.match(line) is None:
121 return errors.TRAILING_WHITESPACE_ERROR
123 class EbuildBlankLine(LineCheck):
124 repoman_check_name = 'ebuild.minorsyn'
125 ignore_comment = False
126 blank_line = re.compile(r'^$')
129 self.line_is_blank = False
131 def check(self, num, line):
132 if self.line_is_blank and self.blank_line.match(line):
133 return 'Useless blank line on line: %d'
134 if self.blank_line.match(line):
135 self.line_is_blank = True
137 self.line_is_blank = False
140 if self.line_is_blank:
141 yield 'Useless blank line on last line'
143 class EbuildQuote(LineCheck):
144 """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
146 repoman_check_name = 'ebuild.minorsyn'
147 _message_commands = ["die", "echo", "eerror",
148 "einfo", "elog", "eqawarn", "ewarn"]
149 _message_re = re.compile(r'\s(' + "|".join(_message_commands) + \
151 _ignored_commands = ["local", "export"] + _message_commands
152 ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \
153 r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)')
154 ignore_comment = False
155 var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"]
158 var_names += ["ED", "EPREFIX", "EROOT"]
160 # variables for games.eclass
161 var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
162 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
163 "GAMES_LOGDIR", "GAMES_BINDIR"]
165 var_names = "(%s)" % "|".join(var_names)
166 var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
168 missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
169 r'\}?[^"\'\s]*(\s|$)')
170 cond_begin = re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
171 cond_end = re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
173 def check(self, num, line):
174 if self.var_reference.search(line) is None:
176 # There can be multiple matches / violations on a single line. We
177 # have to make sure none of the matches are violators. Once we've
178 # found one violator, any remaining matches on the same line can
181 while pos <= len(line) - 1:
182 missing_quotes = self.missing_quotes.search(line, pos)
183 if not missing_quotes:
185 # If the last character of the previous match is a whitespace
186 # character, that character may be needed for the next
187 # missing_quotes match, so search overlaps by 1 character.
188 group = missing_quotes.group()
189 pos = missing_quotes.end() - 1
191 # Filter out some false positives that can
192 # get through the missing_quotes regex.
193 if self.var_reference.search(group) is None:
196 # Filter matches that appear to be an
197 # argument to a message command.
198 # For example: false || ewarn "foo $WORKDIR/bar baz"
199 message_match = self._message_re.search(line)
200 if message_match is not None and \
201 message_match.start() < pos and \
202 message_match.end() > pos:
205 # This is an attempt to avoid false positives without getting
206 # too complex, while possibly allowing some (hopefully
207 # unlikely) violations to slip through. We just assume
208 # everything is correct if the there is a ' [[ ' or a ' ]] '
209 # anywhere in the whole line (possibly continued over one
211 if self.cond_begin.search(line) is not None:
213 if self.cond_end.search(line) is not None:
216 # Any remaining matches on the same line can be ignored.
217 return errors.MISSING_QUOTES_ERROR
220 class EbuildAssignment(LineCheck):
221 """Ensure ebuilds don't assign to readonly variables."""
223 repoman_check_name = 'variable.readonly'
225 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
226 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
227 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
228 ignore_comment = False
231 self.previous_line = None
233 def check(self, num, line):
234 match = self.readonly_assignment.match(line)
236 if match and (not self.previous_line or not self.line_continuation.match(self.previous_line)):
237 e = errors.READONLY_ASSIGNMENT_ERROR
238 self.previous_line = line
241 class Eapi3EbuildAssignment(EbuildAssignment):
242 """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables."""
244 readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=')
246 def check_eapi(self, eapi):
247 return eapi_supports_prefix(eapi)
249 class EbuildNestedDie(LineCheck):
250 """Check ebuild for nested die statements (die statements in subshells"""
252 repoman_check_name = 'ebuild.nesteddie'
253 nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
255 def check(self, num, line):
256 if self.nesteddie_re.match(line):
257 return errors.NESTED_DIE_ERROR
260 class EbuildUselessDodoc(LineCheck):
261 """Check ebuild for useless files in dodoc arguments."""
262 repoman_check_name = 'ebuild.minorsyn'
263 uselessdodoc_re = re.compile(
264 r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENCE|LICENSE)($|\s)')
266 def check(self, num, line):
267 match = self.uselessdodoc_re.match(line)
269 return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
272 class EbuildUselessCdS(LineCheck):
273 """Check for redundant cd ${S} statements"""
274 repoman_check_name = 'ebuild.minorsyn'
275 method_re = re.compile(r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)')
276 cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
279 self.check_next_line = False
281 def check(self, num, line):
282 if self.check_next_line:
283 self.check_next_line = False
284 if self.cds_re.match(line):
285 return errors.REDUNDANT_CD_S_ERROR
286 elif self.method_re.match(line):
287 self.check_next_line = True
289 class EapiDefinition(LineCheck):
291 Check that EAPI assignment conforms to PMS section 7.3.1
292 (first non-comment, non-blank line).
294 repoman_check_name = 'EAPI.definition'
295 ignore_comment = True
296 _eapi_re = portage._pms_eapi_re
299 self._cached_eapi = pkg.metadata['EAPI']
300 self._parsed_eapi = None
301 self._eapi_line_num = None
303 def check(self, num, line):
304 if self._eapi_line_num is None and line.strip():
305 self._eapi_line_num = num + 1
306 m = self._eapi_re.match(line)
308 self._parsed_eapi = m.group(2)
311 if self._parsed_eapi is None:
312 if self._cached_eapi != "0":
313 yield "valid EAPI assignment must occur on or before line: %s" % \
315 elif self._parsed_eapi != self._cached_eapi:
316 yield ("bash returned EAPI '%s' which does not match "
317 "assignment on line: %s") % \
318 (self._cached_eapi, self._eapi_line_num)
320 class EbuildPatches(LineCheck):
321 """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety"""
322 repoman_check_name = 'ebuild.patches'
323 re = re.compile(r'^\s*PATCHES=[^\(]')
324 error = errors.PATCHES_ERROR
326 class EbuildQuotedA(LineCheck):
327 """Ensure ebuilds have no quoting around ${A}"""
329 repoman_check_name = 'ebuild.minorsyn'
330 a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
332 def check(self, num, line):
333 match = self.a_quoted.match(line)
335 return "Quoted \"${A}\" on line: %d"
337 class NoOffsetWithHelpers(LineCheck):
338 """ Check that the image location, the alternate root offset, and the
339 offset prefix (D, ROOT, ED, EROOT and EPREFIX) are not used with
342 repoman_check_name = 'variable.usedwithhelpers'
343 # Ignore matches in quoted strings like this:
344 # elog "installed into ${ROOT}usr/share/php5/apc/."
345 re = re.compile(r'^[^#"\']*\b(docinto|docompress|dodir|dohard|exeinto|fowners|fperms|insinto|into)\s+"?\$\{?(D|ROOT|ED|EROOT|EPREFIX)\b.*')
346 error = errors.NO_OFFSET_WITH_HELPERS
348 class ImplicitRuntimeDeps(LineCheck):
350 Detect the case where DEPEND is set and RDEPEND is unset in the ebuild,
351 since this triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4).
354 repoman_check_name = 'RDEPEND.implicit'
355 _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=')
358 self._rdepend = False
361 def check_eapi(self, eapi):
362 # Beginning with EAPI 4, there is no
363 # implicit RDEPEND=$DEPEND assignment
364 # to be concerned with.
365 return eapi_has_implicit_rdepend(eapi)
367 def check(self, num, line):
368 if not self._rdepend:
369 m = self._assignment_re.match(line)
372 elif m.group(1) == "RDEPEND":
374 elif m.group(1) == "DEPEND":
378 if self._depend and not self._rdepend:
379 yield 'RDEPEND is not explicitly assigned'
381 class InheritDeprecated(LineCheck):
382 """Check if ebuild directly or indirectly inherits a deprecated eclass."""
384 repoman_check_name = 'inherit.deprecated'
386 # deprecated eclass : new eclass (False if no new eclass)
387 deprecated_classes = {
388 "bash-completion": "bash-completion-r1",
389 "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",
399 "ruby-gnome2": "ruby-ng-gnome2",
400 "x-modular": "xorg-2",
403 _inherit_re = re.compile(r'^\s*inherit\s(.*)$')
407 self._indirect_deprecated = set(eclass for eclass in \
408 self.deprecated_classes if eclass in pkg.inherited)
410 def check(self, num, line):
412 direct_inherits = None
413 m = self._inherit_re.match(line)
415 direct_inherits = m.group(1)
417 direct_inherits = direct_inherits.split()
419 if not direct_inherits:
422 for eclass in direct_inherits:
423 replacement = self.deprecated_classes.get(eclass)
424 if replacement is None:
426 elif replacement is False:
427 self._indirect_deprecated.discard(eclass)
428 self._errors.append("please migrate from " + \
429 "'%s' (no replacement) on line: %d" % (eclass, num + 1))
431 self._indirect_deprecated.discard(eclass)
432 self._errors.append("please migrate from " + \
433 "'%s' to '%s' on line: %d" % \
434 (eclass, replacement, num + 1))
437 for error in self._errors:
441 for eclass in self._indirect_deprecated:
442 replacement = self.deprecated_classes[eclass]
443 if replacement is False:
444 yield "please migrate from indirect " + \
445 "inherit of '%s' (no replacement)" % (eclass,)
447 yield "please migrate from indirect " + \
448 "inherit of '%s' to '%s'" % \
449 (eclass, replacement)
450 del self._indirect_deprecated
452 class InheritEclass(LineCheck):
454 Base class for checking for missing inherits, as well as excess inherits.
457 eclass: Set to the name of your eclass.
458 funcs: A tuple of functions that this eclass provides.
459 comprehensive: Is the list of functions complete?
460 exempt_eclasses: If these eclasses are inherited, disable the missing
464 def __init__(self, eclass, funcs=None, comprehensive=False,
465 exempt_eclasses=None, ignore_missing=False, **kwargs):
466 self._eclass = eclass
467 self._comprehensive = comprehensive
468 self._exempt_eclasses = exempt_eclasses
469 self._ignore_missing = ignore_missing
471 self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
472 self._func_re = re.compile(r'\b(' + '|'.join(funcs) + r')\b')
475 self.repoman_check_name = 'inherit.missing'
476 # We can't use pkg.inherited because that tells us all the eclass that
477 # have been inherited and not just the ones we inherit directly.
478 self._inherit = False
479 self._func_call = False
480 if self._exempt_eclasses is not None:
481 inherited = pkg.inherited
482 self._disabled = any(x in inherited for x in self._exempt_eclasses)
484 self._disabled = False
486 def check(self, num, line):
487 if not self._inherit:
488 self._inherit = self._inherit_re.match(line)
489 if not self._inherit:
490 if self._disabled or self._ignore_missing:
492 s = self._func_re.search(line)
494 self._func_call = True
495 return '%s.eclass is not inherited, but "%s" found at line: %s' % \
496 (self._eclass, s.group(0), '%d')
497 elif not self._func_call:
498 self._func_call = self._func_re.search(line)
501 if not self._disabled and self._comprehensive and self._inherit and not self._func_call:
502 self.repoman_check_name = 'inherit.unused'
503 yield 'no function called from %s.eclass; please drop' % self._eclass
505 # eclasses that export ${ECLASS}_src_(compile|configure|install)
506 _eclass_export_functions = (
507 'ant-tasks', 'apache-2', 'apache-module', 'aspell-dict',
508 'autotools-utils', 'base', 'bsdmk', 'cannadic',
509 'clutter', 'cmake-utils', 'db', 'distutils', 'elisp',
510 'embassy', 'emboss', 'emul-linux-x86', 'enlightenment',
511 'font-ebdftopcf', 'font', 'fox', 'freebsd', 'freedict',
512 'games', 'games-ggz', 'games-mods', 'gdesklets',
513 'gems', 'gkrellm-plugin', 'gnatbuild', 'gnat', 'gnome2',
514 'gnome-python-common', 'gnustep-base', 'go-mono', 'gpe',
515 'gst-plugins-bad', 'gst-plugins-base', 'gst-plugins-good',
516 'gst-plugins-ugly', 'gtk-sharp-module', 'haskell-cabal',
517 'horde', 'java-ant-2', 'java-pkg-2', 'java-pkg-simple',
518 'java-virtuals-2', 'kde4-base', 'kde4-meta', 'kernel-2',
519 'latex-package', 'linux-mod', 'mozlinguas', 'myspell',
520 'myspell-r2', 'mysql', 'mysql-v2', 'mythtv-plugins',
521 'oasis', 'obs-service', 'office-ext', 'perl-app',
522 'perl-module', 'php-ext-base-r1', 'php-ext-pecl-r2',
523 'php-ext-source-r2', 'php-lib-r1', 'php-pear-lib-r1',
524 'php-pear-r1', 'python-distutils-ng', 'python',
525 'qt4-build', 'qt4-r2', 'rox-0install', 'rox', 'ruby',
526 'ruby-ng', 'scsh', 'selinux-policy-2', 'sgml-catalog',
527 'stardict', 'sword-module', 'tetex-3', 'tetex',
528 'texlive-module', 'toolchain-binutils', 'toolchain',
529 'twisted', 'vdr-plugin-2', 'vdr-plugin', 'vim',
530 'vim-plugin', 'vim-spell', 'virtuoso', 'vmware',
531 'vmware-mod', 'waf-utils', 'webapp', 'xemacs-elisp',
532 'xemacs-packages', 'xfconf', 'x-modular', 'xorg-2',
539 'eaclocal', 'eautoconf', 'eautoheader',
540 'eautomake', 'eautoreconf', '_elibtoolize',
543 'comprehensive': True,
546 # git - An EGIT_BOOTSTRAP variable may be used to call one of
547 # the autotools functions.
548 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of
549 # the autotools functions.
550 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
555 'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop',
556 'eumask_push', 'eumask_pop', 'epatch', 'epatch_user',
557 'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex',
560 'comprehensive': False,
562 # These are "eclasses are the whole ebuild" type thing.
563 'exempt_eclasses': _eclass_export_functions,
568 'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags',
569 'append-((ld|c(pp|xx)?))?flags', 'append-libs',
571 'comprehensive': False
578 'comprehensive': True,
579 'exempt_eclasses': ('autotools',)
587 # These are "eclasses are the whole ebuild" type thing.
588 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool'),
590 'comprehensive': False
597 'comprehensive': True
604 'comprehensive': False
609 'enewuser', 'enewgroup',
610 'egetent', 'egethome', 'egetshell'
612 'comprehensive': True
616 if not _ENABLE_INHERIT_CHECK:
617 # Since the InheritEclass check is experimental, in the stable branch
618 # we emulate the old eprefixify.defined and inherit.autotools checks.
622 'eaclocal', 'eautoconf', 'eautoheader',
623 'eautomake', 'eautoreconf', '_elibtoolize',
626 'comprehensive': True,
627 'ignore_missing': True,
628 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
635 'comprehensive': False
639 class IUseUndefined(LineCheck):
641 Make sure the ebuild defines IUSE (style guideline
642 says to define IUSE even when empty).
645 repoman_check_name = 'IUSE.undefined'
646 _iuse_def_re = re.compile(r'^IUSE=.*')
649 self._iuse_def = None
651 def check(self, num, line):
652 if self._iuse_def is None:
653 self._iuse_def = self._iuse_def_re.match(line)
656 if self._iuse_def is None:
657 yield 'IUSE is not defined'
659 class EMakeParallelDisabled(PhaseCheck):
660 """Check for emake -j1 calls which disable parallelization."""
661 repoman_check_name = 'upstream.workaround'
662 re = re.compile(r'^\s*emake\s+.*-j\s*1\b')
663 error = errors.EMAKE_PARALLEL_DISABLED
665 def phase_check(self, num, line):
666 if self.in_phase == 'src_compile' or self.in_phase == 'src_install':
667 if self.re.match(line):
670 class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
671 """Check for MAKEOPTS=-j1 that disables parallelization."""
672 repoman_check_name = 'upstream.workaround'
673 re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b')
674 error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
676 class NoAsNeeded(LineCheck):
677 """Check for calls to the no-as-needed function."""
678 repoman_check_name = 'upstream.workaround'
679 re = re.compile(r'.*\$\(no-as-needed\)')
680 error = errors.NO_AS_NEEDED
682 class PreserveOldLib(LineCheck):
683 """Check for calls to the preserve_old_lib function."""
684 repoman_check_name = 'upstream.workaround'
685 re = re.compile(r'.*preserve_old_lib')
686 error = errors.PRESERVE_OLD_LIB
688 class SandboxAddpredict(LineCheck):
689 """Check for calls to the addpredict function."""
690 repoman_check_name = 'upstream.workaround'
691 re = re.compile(r'(^|\s)addpredict\b')
692 error = errors.SANDBOX_ADDPREDICT
694 class DeprecatedBindnowFlags(LineCheck):
695 """Check for calls to the deprecated bindnow-flags function."""
696 repoman_check_name = 'ebuild.minorsyn'
697 re = re.compile(r'.*\$\(bindnow-flags\)')
698 error = errors.DEPRECATED_BINDNOW_FLAGS
700 class WantAutoDefaultValue(LineCheck):
701 """Check setting WANT_AUTO* to latest (default value)."""
702 repoman_check_name = 'ebuild.minorsyn'
703 _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest')
705 def check(self, num, line):
706 m = self._re.match(line)
708 return 'WANT_AUTO' + m.group(1) + \
709 ' redundantly set to default value "latest" on line: %d'
711 class SrcCompileEconf(PhaseCheck):
712 repoman_check_name = 'ebuild.minorsyn'
713 configure_re = re.compile(r'\s(econf|./configure)')
715 def check_eapi(self, eapi):
716 return eapi_has_src_prepare_and_src_configure(eapi)
718 def phase_check(self, num, line):
719 if self.in_phase == 'src_compile':
720 m = self.configure_re.match(line)
722 return ("'%s'" % m.group(1)) + \
723 " call should be moved to src_configure from line: %d"
725 class SrcUnpackPatches(PhaseCheck):
726 repoman_check_name = 'ebuild.minorsyn'
727 src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
729 def check_eapi(self, eapi):
730 return eapi_has_src_prepare_and_src_configure(eapi)
732 def phase_check(self, num, line):
733 if self.in_phase == 'src_unpack':
734 m = self.src_prepare_tools_re.search(line)
736 return ("'%s'" % m.group(1)) + \
737 " call should be moved to src_prepare from line: %d"
739 class BuiltWithUse(LineCheck):
740 repoman_check_name = 'ebuild.minorsyn'
741 re = re.compile(r'(^|.*\b)built_with_use\b')
742 error = errors.BUILT_WITH_USE
744 class DeprecatedUseq(LineCheck):
745 """Checks for use of the deprecated useq function"""
746 repoman_check_name = 'ebuild.minorsyn'
747 re = re.compile(r'(^|.*\b)useq\b')
748 error = errors.USEQ_ERROR
750 class DeprecatedHasq(LineCheck):
751 """Checks for use of the deprecated hasq function"""
752 repoman_check_name = 'ebuild.minorsyn'
753 re = re.compile(r'(^|.*\b)hasq\b')
754 error = errors.HASQ_ERROR
757 class Eapi3DeprecatedFuncs(LineCheck):
758 repoman_check_name = 'EAPI.deprecated'
759 deprecated_commands_re = re.compile(r'^\s*(check_license)\b')
761 def check_eapi(self, eapi):
762 return eapi not in ('0', '1', '2')
764 def check(self, num, line):
765 m = self.deprecated_commands_re.match(line)
767 return ("'%s'" % m.group(1)) + \
768 " has been deprecated in EAPI=3 on line: %d"
771 class Eapi4IncompatibleFuncs(LineCheck):
772 repoman_check_name = 'EAPI.incompatible'
773 banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
775 def check_eapi(self, eapi):
776 return not eapi_has_dosed_dohard(eapi)
778 def check(self, num, line):
779 m = self.banned_commands_re.match(line)
781 return ("'%s'" % m.group(1)) + \
782 " has been banned in EAPI=4 on line: %d"
784 class Eapi4GoneVars(LineCheck):
785 repoman_check_name = 'EAPI.incompatible'
786 undefined_vars_re = re.compile(r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))')
788 def check_eapi(self, eapi):
789 # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later.
790 return not eapi_exports_AA(eapi)
792 def check(self, num, line):
793 m = self.undefined_vars_re.match(line)
795 return ("variable '$%s'" % m.group(1)) + \
796 " is gone in EAPI=4 on line: %d"
798 class PortageInternal(LineCheck):
799 repoman_check_name = 'portage.internal'
800 ignore_comment = True
801 # Match when the command is preceded only by leading whitespace or a shell
802 # operator such as (, {, |, ||, or &&. This prevents false postives in
803 # things like elog messages, as reported in bug #413285.
804 re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b')
806 def check(self, num, line):
807 """Run the check on line and return error if there is one"""
808 m = self.re.match(line)
810 return ("'%s'" % m.group(2)) + " called on line: %d"
812 _constant_checks = tuple(chain((c() for c in (
813 EbuildHeader, EbuildWhitespace, EbuildBlankLine, EbuildQuote,
814 EbuildAssignment, Eapi3EbuildAssignment, EbuildUselessDodoc,
815 EbuildUselessCdS, EbuildNestedDie,
816 EbuildPatches, EbuildQuotedA, EapiDefinition,
817 ImplicitRuntimeDeps, IUseUndefined,
818 EMakeParallelDisabled, EMakeParallelDisabledViaMAKEOPTS, NoAsNeeded,
819 DeprecatedBindnowFlags, SrcUnpackPatches, WantAutoDefaultValue,
820 SrcCompileEconf, Eapi3DeprecatedFuncs, NoOffsetWithHelpers,
821 Eapi4IncompatibleFuncs, Eapi4GoneVars, BuiltWithUse,
822 PreserveOldLib, SandboxAddpredict, PortageInternal,
823 DeprecatedUseq, DeprecatedHasq)),
824 (InheritEclass(k, **kwargs) for k, kwargs in _eclass_info.items())))
826 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
827 _ignore_comment_re = re.compile(r'^\s*#')
829 def run_checks(contents, pkg):
830 unicode_escape_codec = codecs.lookup('unicode_escape')
831 unicode_escape = lambda x: unicode_escape_codec.decode(x)[0]
832 checks = _constant_checks
833 here_doc_delim = None
838 for num, line in enumerate(contents):
840 # Check if we're inside a here-document.
841 if here_doc_delim is not None:
842 if here_doc_delim.match(line):
843 here_doc_delim = None
844 if here_doc_delim is None:
845 here_doc = _here_doc_re.match(line)
846 if here_doc is not None:
847 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1))
848 if here_doc_delim is not None:
851 # Unroll multiline escaped strings so that we can check things:
855 # This will merge these lines like so:
856 # inherit foo bar moo cow
858 # A normal line will end in the two bytes: <\> <\n>. So decoding
859 # that will result in python thinking the <\n> is being escaped
860 # and eat the single <\> which makes it hard for us to detect.
861 # Instead, strip the newline (which we know all lines have), and
862 # append a <0>. Then when python escapes it, if the line ended
863 # in a <\>, we'll end up with a <\0> marker to key off of. This
864 # shouldn't be a problem with any valid ebuild ...
865 line_escaped = unicode_escape(line.rstrip('\n') + '0')
869 # Who knows what kind of crazy crap an ebuild will have
870 # in it -- don't allow it to kill us.
873 # Chop off the \ and \n bytes from the previous line.
874 multiline = multiline[:-2] + line
875 if not line_escaped.endswith('\0'):
882 if line_escaped.endswith('\0'):
887 # Finally we have a full line to parse.
888 is_comment = _ignore_comment_re.match(line) is not None
890 if is_comment and lc.ignore_comment:
892 if lc.check_eapi(pkg.metadata['EAPI']):
893 ignore = lc.ignore_line
894 if not ignore or not ignore.match(line):
895 e = lc.check(num, line)
897 yield lc.repoman_check_name, e % (num + 1)
903 yield lc.repoman_check_name, e