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, \
18 class LineCheck(object):
19 """Run a check on a line of an ebuild."""
20 """A regular expression to determine whether to ignore the line"""
22 """True if lines containing nothing more than comments with optional
23 leading whitespace should be ignored"""
29 def check_eapi(self, eapi):
30 """ returns if the check should be run in the given EAPI (default is True) """
33 def check(self, num, line):
34 """Run the check on line and return error if there is one"""
35 if self.re.match(line):
41 class PhaseCheck(LineCheck):
42 """ basic class for function detection """
44 func_end_re = re.compile(r'^\}$')
45 phases_re = re.compile('(%s)' % '|'.join((
46 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare',
47 'src_configure', 'src_compile', 'src_test', 'src_install',
48 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm',
52 def check(self, num, line):
53 m = self.phases_re.match(line)
55 self.in_phase = m.group(1)
56 if self.in_phase != '' and \
57 self.func_end_re.match(line) is not None:
60 return self.phase_check(num, line)
62 def phase_check(self, num, line):
63 """ override this function for your checks """
66 class EbuildHeader(LineCheck):
67 """Ensure ebuilds have proper headers
68 Copyright header errors
73 modification_year - Year the ebuild was last modified
76 repoman_check_name = 'ebuild.badheader'
78 gentoo_copyright = r'^# Copyright ((1999|2\d\d\d)-)?%s Gentoo Foundation$'
79 # Why a regex here, use a string match
80 # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
81 gentoo_license = '# Distributed under the terms of the GNU General Public License v2'
82 cvs_header = re.compile(r'^# \$Header: .*\$$')
83 ignore_comment = False
87 self.modification_year = r'2\d\d\d'
89 self.modification_year = str(time.gmtime(pkg.mtime)[0])
90 self.gentoo_copyright_re = re.compile(
91 self.gentoo_copyright % self.modification_year)
93 def check(self, num, line):
97 if not self.gentoo_copyright_re.match(line):
98 return errors.COPYRIGHT_ERROR
99 elif num == 1 and line.rstrip('\n') != self.gentoo_license:
100 return errors.LICENSE_ERROR
102 if not self.cvs_header.match(line):
103 return errors.CVS_HEADER_ERROR
106 class EbuildWhitespace(LineCheck):
107 """Ensure ebuilds have proper whitespacing"""
109 repoman_check_name = 'ebuild.minorsyn'
111 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
112 ignore_comment = False
113 leading_spaces = re.compile(r'^[\S\t]')
114 trailing_whitespace = re.compile(r'.*([\S]$)')
116 def check(self, num, line):
117 if self.leading_spaces.match(line) is None:
118 return errors.LEADING_SPACES_ERROR
119 if self.trailing_whitespace.match(line) is None:
120 return errors.TRAILING_WHITESPACE_ERROR
122 class EbuildBlankLine(LineCheck):
123 repoman_check_name = 'ebuild.minorsyn'
124 ignore_comment = False
125 blank_line = re.compile(r'^$')
128 self.line_is_blank = False
130 def check(self, num, line):
131 if self.line_is_blank and self.blank_line.match(line):
132 return 'Useless blank line on line: %d'
133 if self.blank_line.match(line):
134 self.line_is_blank = True
136 self.line_is_blank = False
139 if self.line_is_blank:
140 yield 'Useless blank line on last line'
142 class EbuildQuote(LineCheck):
143 """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
145 repoman_check_name = 'ebuild.minorsyn'
146 _message_commands = ["die", "echo", "eerror",
147 "einfo", "elog", "eqawarn", "ewarn"]
148 _message_re = re.compile(r'\s(' + "|".join(_message_commands) + \
150 _ignored_commands = ["local", "export"] + _message_commands
151 ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \
152 r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)')
153 ignore_comment = False
154 var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"]
157 var_names += ["ED", "EPREFIX", "EROOT"]
159 # variables for games.eclass
160 var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
161 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
162 "GAMES_LOGDIR", "GAMES_BINDIR"]
164 var_names = "(%s)" % "|".join(var_names)
165 var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
167 missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
168 r'\}?[^"\'\s]*(\s|$)')
169 cond_begin = re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
170 cond_end = re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
172 def check(self, num, line):
173 if self.var_reference.search(line) is None:
175 # There can be multiple matches / violations on a single line. We
176 # have to make sure none of the matches are violators. Once we've
177 # found one violator, any remaining matches on the same line can
180 while pos <= len(line) - 1:
181 missing_quotes = self.missing_quotes.search(line, pos)
182 if not missing_quotes:
184 # If the last character of the previous match is a whitespace
185 # character, that character may be needed for the next
186 # missing_quotes match, so search overlaps by 1 character.
187 group = missing_quotes.group()
188 pos = missing_quotes.end() - 1
190 # Filter out some false positives that can
191 # get through the missing_quotes regex.
192 if self.var_reference.search(group) is None:
195 # Filter matches that appear to be an
196 # argument to a message command.
197 # For example: false || ewarn "foo $WORKDIR/bar baz"
198 message_match = self._message_re.search(line)
199 if message_match is not None and \
200 message_match.start() < pos and \
201 message_match.end() > pos:
204 # This is an attempt to avoid false positives without getting
205 # too complex, while possibly allowing some (hopefully
206 # unlikely) violations to slip through. We just assume
207 # everything is correct if the there is a ' [[ ' or a ' ]] '
208 # anywhere in the whole line (possibly continued over one
210 if self.cond_begin.search(line) is not None:
212 if self.cond_end.search(line) is not None:
215 # Any remaining matches on the same line can be ignored.
216 return errors.MISSING_QUOTES_ERROR
219 class EbuildAssignment(LineCheck):
220 """Ensure ebuilds don't assign to readonly variables."""
222 repoman_check_name = 'variable.readonly'
224 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
225 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
226 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
227 ignore_comment = False
230 self.previous_line = None
232 def check(self, num, line):
233 match = self.readonly_assignment.match(line)
235 if match and (not self.previous_line or not self.line_continuation.match(self.previous_line)):
236 e = errors.READONLY_ASSIGNMENT_ERROR
237 self.previous_line = line
240 class Eapi3EbuildAssignment(EbuildAssignment):
241 """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables."""
243 readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=')
245 def check_eapi(self, eapi):
246 return eapi_supports_prefix(eapi)
248 class EbuildNestedDie(LineCheck):
249 """Check ebuild for nested die statements (die statements in subshells"""
251 repoman_check_name = 'ebuild.nesteddie'
252 nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
254 def check(self, num, line):
255 if self.nesteddie_re.match(line):
256 return errors.NESTED_DIE_ERROR
259 class EbuildUselessDodoc(LineCheck):
260 """Check ebuild for useless files in dodoc arguments."""
261 repoman_check_name = 'ebuild.minorsyn'
262 uselessdodoc_re = re.compile(
263 r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENCE|LICENSE)($|\s)')
265 def check(self, num, line):
266 match = self.uselessdodoc_re.match(line)
268 return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
271 class EbuildUselessCdS(LineCheck):
272 """Check for redundant cd ${S} statements"""
273 repoman_check_name = 'ebuild.minorsyn'
274 method_re = re.compile(r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)')
275 cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
278 self.check_next_line = False
280 def check(self, num, line):
281 if self.check_next_line:
282 self.check_next_line = False
283 if self.cds_re.match(line):
284 return errors.REDUNDANT_CD_S_ERROR
285 elif self.method_re.match(line):
286 self.check_next_line = True
288 class EapiDefinition(LineCheck):
290 Check that EAPI assignment conforms to PMS section 7.3.1
291 (first non-comment, non-blank line).
293 repoman_check_name = 'EAPI.definition'
294 ignore_comment = True
295 _eapi_re = portage._pms_eapi_re
298 self._cached_eapi = pkg.metadata['EAPI']
299 self._parsed_eapi = None
300 self._eapi_line_num = None
302 def check(self, num, line):
303 if self._eapi_line_num is None and line.strip():
304 self._eapi_line_num = num + 1
305 m = self._eapi_re.match(line)
307 self._parsed_eapi = m.group(2)
310 if self._parsed_eapi is None:
311 if self._cached_eapi != "0":
312 yield "valid EAPI assignment must occur on or before line: %s" % \
314 elif self._parsed_eapi != self._cached_eapi:
315 yield ("bash returned EAPI '%s' which does not match "
316 "assignment on line: %s") % \
317 (self._cached_eapi, self._eapi_line_num)
319 class EbuildPatches(LineCheck):
320 """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety"""
321 repoman_check_name = 'ebuild.patches'
322 re = re.compile(r'^\s*PATCHES=[^\(]')
323 error = errors.PATCHES_ERROR
325 class EbuildQuotedA(LineCheck):
326 """Ensure ebuilds have no quoting around ${A}"""
328 repoman_check_name = 'ebuild.minorsyn'
329 a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
331 def check(self, num, line):
332 match = self.a_quoted.match(line)
334 return "Quoted \"${A}\" on line: %d"
336 class NoOffsetWithHelpers(LineCheck):
337 """ Check that the image location, the alternate root offset, and the
338 offset prefix (D, ROOT, ED, EROOT and EPREFIX) are not used with
341 repoman_check_name = 'variable.usedwithhelpers'
342 # Ignore matches in quoted strings like this:
343 # elog "installed into ${ROOT}usr/share/php5/apc/."
344 re = re.compile(r'^[^#"\']*\b(docinto|docompress|dodir|dohard|exeinto|fowners|fperms|insinto|into)\s+"?\$\{?(D|ROOT|ED|EROOT|EPREFIX)\b.*')
345 error = errors.NO_OFFSET_WITH_HELPERS
347 class ImplicitRuntimeDeps(LineCheck):
349 Detect the case where DEPEND is set and RDEPEND is unset in the ebuild,
350 since this triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4).
353 repoman_check_name = 'RDEPEND.implicit'
354 _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=')
357 self._rdepend = False
360 def check_eapi(self, eapi):
361 # Beginning with EAPI 4, there is no
362 # implicit RDEPEND=$DEPEND assignment
363 # to be concerned with.
364 return eapi_has_implicit_rdepend(eapi)
366 def check(self, num, line):
367 if not self._rdepend:
368 m = self._assignment_re.match(line)
371 elif m.group(1) == "RDEPEND":
373 elif m.group(1) == "DEPEND":
377 if self._depend and not self._rdepend:
378 yield 'RDEPEND is not explicitly assigned'
380 class InheritDeprecated(LineCheck):
381 """Check if ebuild directly or indirectly inherits a deprecated eclass."""
383 repoman_check_name = 'inherit.deprecated'
385 # deprecated eclass : new eclass (False if no new eclass)
386 deprecated_classes = {
387 "bash-completion": "bash-completion-r1",
388 "gems": "ruby-fakegem",
390 "mozconfig-2": "mozconfig-3",
391 "mozcoreconf": "mozcoreconf-2",
392 "php-ext-pecl-r1": "php-ext-pecl-r2",
393 "php-ext-source-r1": "php-ext-source-r2",
394 "php-pear": "php-pear-r1",
398 "ruby-gnome2": "ruby-ng-gnome2",
399 "x-modular": "xorg-2",
402 _inherit_re = re.compile(r'^\s*inherit\s(.*)$')
406 self._indirect_deprecated = set(eclass for eclass in \
407 self.deprecated_classes if eclass in pkg.inherited)
409 def check(self, num, line):
411 direct_inherits = None
412 m = self._inherit_re.match(line)
414 direct_inherits = m.group(1)
416 direct_inherits = direct_inherits.split()
418 if not direct_inherits:
421 for eclass in direct_inherits:
422 replacement = self.deprecated_classes.get(eclass)
423 if replacement is None:
425 elif replacement is False:
426 self._indirect_deprecated.discard(eclass)
427 self._errors.append("please migrate from " + \
428 "'%s' (no replacement) on line: %d" % (eclass, num + 1))
430 self._indirect_deprecated.discard(eclass)
431 self._errors.append("please migrate from " + \
432 "'%s' to '%s' on line: %d" % \
433 (eclass, replacement, num + 1))
436 for error in self._errors:
440 for eclass in self._indirect_deprecated:
441 replacement = self.deprecated_classes[eclass]
442 if replacement is False:
443 yield "please migrate from indirect " + \
444 "inherit of '%s' (no replacement)" % (eclass,)
446 yield "please migrate from indirect " + \
447 "inherit of '%s' to '%s'" % \
448 (eclass, replacement)
449 del self._indirect_deprecated
451 class InheritEclass(LineCheck):
453 Base class for checking for missing inherits, as well as excess inherits.
456 eclass: Set to the name of your eclass.
457 funcs: A tuple of functions that this eclass provides.
458 comprehensive: Is the list of functions complete?
459 exempt_eclasses: If these eclasses are inherited, disable the missing
463 def __init__(self, eclass, funcs=None, comprehensive=False,
464 exempt_eclasses=None, **kwargs):
465 self._eclass = eclass
466 self._comprehensive = comprehensive
467 self._exempt_eclasses = exempt_eclasses
469 subclasses = _eclass_subclass_info.get(eclass)
470 if subclasses is not None:
471 inherit_re = '(%s)' % '|'.join([eclass] + list(subclasses))
472 self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
473 self._func_re = re.compile(r'\b(' + '|'.join(funcs) + r')\b')
476 self.repoman_check_name = 'inherit.missing'
477 # We can't use pkg.inherited because that tells us all the eclass that
478 # have been inherited and not just the ones we inherit directly.
479 self._inherit = False
480 self._func_call = False
481 if self._exempt_eclasses is not None:
482 self._disabled = any(x in pkg.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:
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
508 'eaclocal', 'eautoconf', 'eautoheader',
509 'eautomake', 'eautoreconf', '_elibtoolize',
512 'comprehensive': True,
515 # git - An EGIT_BOOTSTRAP variable may be used to call one of
516 # the autotools functions.
517 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of
518 # the autotools functions.
519 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
524 'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop',
525 'eumask_push', 'eumask_pop', 'epatch', 'epatch_user',
526 'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex',
529 'comprehensive': False,
531 # These are "eclasses are the whole ebuild" type thing.
532 'exempt_eclasses': ('base', 'cmake-utils', 'toolchain', 'toolchain-binutils', 'vim'),
534 #'inherited_api': ('multilib', 'user',),
539 'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags',
540 'append-((ld|c(pp|xx)?))?flags', 'append-libs',
542 'comprehensive': False
549 'comprehensive': True
557 # These are "eclasses are the whole ebuild" type thing.
558 'exempt_eclasses': ('cmake-utils',),
560 'comprehensive': False
567 'comprehensive': True
574 'comprehensive': False
579 'enewuser', 'enewgroup',
580 'egetent', 'egethome', 'egetshell'
582 'comprehensive': True
586 _eclass_subclass_info = {}
588 for k, v in _eclass_info.items():
589 inherited_api = v.get('inherited_api')
590 if inherited_api is not None:
591 for parent in inherited_api:
592 _eclass_subclass_info.setdefault(parent, set()).add(k)
594 class IUseUndefined(LineCheck):
596 Make sure the ebuild defines IUSE (style guideline
597 says to define IUSE even when empty).
600 repoman_check_name = 'IUSE.undefined'
601 _iuse_def_re = re.compile(r'^IUSE=.*')
604 self._iuse_def = None
606 def check(self, num, line):
607 if self._iuse_def is None:
608 self._iuse_def = self._iuse_def_re.match(line)
611 if self._iuse_def is None:
612 yield 'IUSE is not defined'
614 class EMakeParallelDisabled(PhaseCheck):
615 """Check for emake -j1 calls which disable parallelization."""
616 repoman_check_name = 'upstream.workaround'
617 re = re.compile(r'^\s*emake\s+.*-j\s*1\b')
618 error = errors.EMAKE_PARALLEL_DISABLED
620 def phase_check(self, num, line):
621 if self.in_phase == 'src_compile' or self.in_phase == 'src_install':
622 if self.re.match(line):
625 class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
626 """Check for MAKEOPTS=-j1 that disables parallelization."""
627 repoman_check_name = 'upstream.workaround'
628 re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b')
629 error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
631 class NoAsNeeded(LineCheck):
632 """Check for calls to the no-as-needed function."""
633 repoman_check_name = 'upstream.workaround'
634 re = re.compile(r'.*\$\(no-as-needed\)')
635 error = errors.NO_AS_NEEDED
637 class PreserveOldLib(LineCheck):
638 """Check for calls to the preserve_old_lib function."""
639 repoman_check_name = 'upstream.workaround'
640 re = re.compile(r'.*preserve_old_lib')
641 error = errors.PRESERVE_OLD_LIB
643 class SandboxAddpredict(LineCheck):
644 """Check for calls to the addpredict function."""
645 repoman_check_name = 'upstream.workaround'
646 re = re.compile(r'(^|\s)addpredict\b')
647 error = errors.SANDBOX_ADDPREDICT
649 class DeprecatedBindnowFlags(LineCheck):
650 """Check for calls to the deprecated bindnow-flags function."""
651 repoman_check_name = 'ebuild.minorsyn'
652 re = re.compile(r'.*\$\(bindnow-flags\)')
653 error = errors.DEPRECATED_BINDNOW_FLAGS
655 class WantAutoDefaultValue(LineCheck):
656 """Check setting WANT_AUTO* to latest (default value)."""
657 repoman_check_name = 'ebuild.minorsyn'
658 _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest')
660 def check(self, num, line):
661 m = self._re.match(line)
663 return 'WANT_AUTO' + m.group(1) + \
664 ' redundantly set to default value "latest" on line: %d'
666 class SrcCompileEconf(PhaseCheck):
667 repoman_check_name = 'ebuild.minorsyn'
668 configure_re = re.compile(r'\s(econf|./configure)')
670 def check_eapi(self, eapi):
671 return eapi_has_src_prepare_and_src_configure(eapi)
673 def phase_check(self, num, line):
674 if self.in_phase == 'src_compile':
675 m = self.configure_re.match(line)
677 return ("'%s'" % m.group(1)) + \
678 " call should be moved to src_configure from line: %d"
680 class SrcUnpackPatches(PhaseCheck):
681 repoman_check_name = 'ebuild.minorsyn'
682 src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
684 def check_eapi(self, eapi):
685 return eapi_has_src_prepare_and_src_configure(eapi)
687 def phase_check(self, num, line):
688 if self.in_phase == 'src_unpack':
689 m = self.src_prepare_tools_re.search(line)
691 return ("'%s'" % m.group(1)) + \
692 " call should be moved to src_prepare from line: %d"
694 class BuiltWithUse(LineCheck):
695 repoman_check_name = 'ebuild.minorsyn'
696 re = re.compile(r'(^|.*\b)built_with_use\b')
697 error = errors.BUILT_WITH_USE
699 class DeprecatedUseq(LineCheck):
700 """Checks for use of the deprecated useq function"""
701 repoman_check_name = 'ebuild.minorsyn'
702 re = re.compile(r'(^|.*\b)useq\b')
703 error = errors.USEQ_ERROR
705 class DeprecatedHasq(LineCheck):
706 """Checks for use of the deprecated hasq function"""
707 repoman_check_name = 'ebuild.minorsyn'
708 re = re.compile(r'(^|.*\b)hasq\b')
709 error = errors.HASQ_ERROR
712 class Eapi3DeprecatedFuncs(LineCheck):
713 repoman_check_name = 'EAPI.deprecated'
714 deprecated_commands_re = re.compile(r'^\s*(check_license)\b')
716 def check_eapi(self, eapi):
717 return eapi not in ('0', '1', '2')
719 def check(self, num, line):
720 m = self.deprecated_commands_re.match(line)
722 return ("'%s'" % m.group(1)) + \
723 " has been deprecated in EAPI=3 on line: %d"
726 class Eapi4IncompatibleFuncs(LineCheck):
727 repoman_check_name = 'EAPI.incompatible'
728 banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
730 def check_eapi(self, eapi):
731 return not eapi_has_dosed_dohard(eapi)
733 def check(self, num, line):
734 m = self.banned_commands_re.match(line)
736 return ("'%s'" % m.group(1)) + \
737 " has been banned in EAPI=4 on line: %d"
739 class Eapi4GoneVars(LineCheck):
740 repoman_check_name = 'EAPI.incompatible'
741 undefined_vars_re = re.compile(r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))')
743 def check_eapi(self, eapi):
744 # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later.
745 return not eapi_exports_AA(eapi)
747 def check(self, num, line):
748 m = self.undefined_vars_re.match(line)
750 return ("variable '$%s'" % m.group(1)) + \
751 " is gone in EAPI=4 on line: %d"
753 class PortageInternal(LineCheck):
754 repoman_check_name = 'portage.internal'
755 ignore_comment = True
756 # Match when the command is preceded only by leading whitespace or a shell
757 # operator such as (, {, |, ||, or &&. This prevents false postives in
758 # things like elog messages, as reported in bug #413285.
759 re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b')
761 def check(self, num, line):
762 """Run the check on line and return error if there is one"""
763 m = self.re.match(line)
765 return ("'%s'" % m.group(2)) + " called on line: %d"
767 _constant_checks = tuple(chain((c() for c in (
768 EbuildHeader, EbuildWhitespace, EbuildBlankLine, EbuildQuote,
769 EbuildAssignment, Eapi3EbuildAssignment, EbuildUselessDodoc,
770 EbuildUselessCdS, EbuildNestedDie,
771 EbuildPatches, EbuildQuotedA, EapiDefinition,
772 ImplicitRuntimeDeps, IUseUndefined,
773 EMakeParallelDisabled, EMakeParallelDisabledViaMAKEOPTS, NoAsNeeded,
774 DeprecatedBindnowFlags, SrcUnpackPatches, WantAutoDefaultValue,
775 SrcCompileEconf, Eapi3DeprecatedFuncs, NoOffsetWithHelpers,
776 Eapi4IncompatibleFuncs, Eapi4GoneVars, BuiltWithUse,
777 PreserveOldLib, SandboxAddpredict, PortageInternal,
778 DeprecatedUseq, DeprecatedHasq)),
779 (InheritEclass(k, **kwargs) for k, kwargs in _eclass_info.items())))
781 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
782 _ignore_comment_re = re.compile(r'^\s*#')
784 def run_checks(contents, pkg):
785 unicode_escape_codec = codecs.lookup('unicode_escape')
786 unicode_escape = lambda x: unicode_escape_codec.decode(x)[0]
787 checks = _constant_checks
788 here_doc_delim = None
793 for num, line in enumerate(contents):
795 # Check if we're inside a here-document.
796 if here_doc_delim is not None:
797 if here_doc_delim.match(line):
798 here_doc_delim = None
799 if here_doc_delim is None:
800 here_doc = _here_doc_re.match(line)
801 if here_doc is not None:
802 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1))
803 if here_doc_delim is not None:
806 # Unroll multiline escaped strings so that we can check things:
810 # This will merge these lines like so:
811 # inherit foo bar moo cow
813 # A normal line will end in the two bytes: <\> <\n>. So decoding
814 # that will result in python thinking the <\n> is being escaped
815 # and eat the single <\> which makes it hard for us to detect.
816 # Instead, strip the newline (which we know all lines have), and
817 # append a <0>. Then when python escapes it, if the line ended
818 # in a <\>, we'll end up with a <\0> marker to key off of. This
819 # shouldn't be a problem with any valid ebuild ...
820 line_escaped = unicode_escape(line.rstrip('\n') + '0')
824 # Who knows what kind of crazy crap an ebuild will have
825 # in it -- don't allow it to kill us.
828 # Chop off the \ and \n bytes from the previous line.
829 multiline = multiline[:-2] + line
830 if not line_escaped.endswith('\0'):
837 if line_escaped.endswith('\0'):
842 # Finally we have a full line to parse.
843 is_comment = _ignore_comment_re.match(line) is not None
845 if is_comment and lc.ignore_comment:
847 if lc.check_eapi(pkg.metadata['EAPI']):
848 ignore = lc.ignore_line
849 if not ignore or not ignore.match(line):
850 e = lc.check(num, line)
852 yield lc.repoman_check_name, e % (num + 1)
858 yield lc.repoman_check_name, e