65f024ce4da517525079103a0fec5e1f1bb25c40
[portage.git] / pym / repoman / checks.py
1 # repoman: Checks
2 # Copyright 2007-2012 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4
5 """This module contains functions used in Repoman to ascertain the quality
6 and correctness of an ebuild."""
7
8 import codecs
9 from itertools import chain
10 import re
11 import time
12 import repoman.errors as errors
13 import portage
14 from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \
15         eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \
16         eapi_exports_AA
17 from portage.const import _ENABLE_INHERIT_CHECK
18
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"""
22         ignore_line = False
23         """True if lines containing nothing more than comments with optional
24         leading whitespace should be ignored"""
25         ignore_comment = True
26
27         def new(self, pkg):
28                 pass
29
30         def check_eapi(self, eapi):
31                 """ returns if the check should be run in the given EAPI (default is True) """
32                 return True
33
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):
37                         return self.error
38
39         def end(self):
40                 pass
41
42 class PhaseCheck(LineCheck):
43         """ basic class for function detection """
44
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',
50                 'pkg_config')))
51         in_phase = ''
52
53         def check(self, num, line):
54                 m = self.phases_re.match(line)
55                 if m is not None:
56                         self.in_phase = m.group(1)
57                 if self.in_phase != '' and \
58                                 self.func_end_re.match(line) is not None:
59                         self.in_phase = ''
60
61                 return self.phase_check(num, line)
62
63         def phase_check(self, num, line):
64                 """ override this function for your checks """
65                 pass
66
67 class EbuildHeader(LineCheck):
68         """Ensure ebuilds have proper headers
69                 Copyright header errors
70                 CVS header errors
71                 License header errors
72         
73         Args:
74                 modification_year - Year the ebuild was last modified
75         """
76
77         repoman_check_name = 'ebuild.badheader'
78
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
85
86         def new(self, pkg):
87                 if pkg.mtime is None:
88                         self.modification_year = r'2\d\d\d'
89                 else:
90                         self.modification_year = str(time.gmtime(pkg.mtime)[0])
91                 self.gentoo_copyright_re = re.compile(
92                         self.gentoo_copyright % self.modification_year)
93
94         def check(self, num, line):
95                 if num > 2:
96                         return
97                 elif num == 0:
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
102                 elif num == 2:
103                         if not self.cvs_header.match(line):
104                                 return errors.CVS_HEADER_ERROR
105
106
107 class EbuildWhitespace(LineCheck):
108         """Ensure ebuilds have proper whitespacing"""
109
110         repoman_check_name = 'ebuild.minorsyn'
111
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]$)')  
116
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
122
123 class EbuildBlankLine(LineCheck):
124         repoman_check_name = 'ebuild.minorsyn'
125         ignore_comment = False
126         blank_line = re.compile(r'^$')
127
128         def new(self, pkg):
129                 self.line_is_blank = False
130
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
136                 else:
137                         self.line_is_blank = False
138
139         def end(self):
140                 if self.line_is_blank:
141                         yield 'Useless blank line on last line'
142
143 class EbuildQuote(LineCheck):
144         """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
145
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) + \
150                 r')\s+"[^"]*"\s*$')
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"]
156
157         # EAPI=3/Prefix vars
158         var_names += ["ED", "EPREFIX", "EROOT"]
159
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"]
164
165         var_names = "(%s)" % "|".join(var_names)
166         var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
167                 var_names + '\W)')
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+)')
172         
173         def check(self, num, line):
174                 if self.var_reference.search(line) is None:
175                         return
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
179                 # be ignored.
180                 pos = 0
181                 while pos <= len(line) - 1:
182                         missing_quotes = self.missing_quotes.search(line, pos)
183                         if not missing_quotes:
184                                 break
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
190
191                         # Filter out some false positives that can
192                         # get through the missing_quotes regex.
193                         if self.var_reference.search(group) is None:
194                                 continue
195
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:
203                                 break
204
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
210                         # line).
211                         if self.cond_begin.search(line) is not None:
212                                 continue
213                         if self.cond_end.search(line) is not None:
214                                 continue
215
216                         # Any remaining matches on the same line can be ignored.
217                         return errors.MISSING_QUOTES_ERROR
218
219
220 class EbuildAssignment(LineCheck):
221         """Ensure ebuilds don't assign to readonly variables."""
222
223         repoman_check_name = 'variable.readonly'
224
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
229
230         def __init__(self):
231                 self.previous_line = None
232
233         def check(self, num, line):
234                 match = self.readonly_assignment.match(line)
235                 e = None
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
239                 return e
240
241 class Eapi3EbuildAssignment(EbuildAssignment):
242         """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables."""
243
244         readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=')
245
246         def check_eapi(self, eapi):
247                 return eapi_supports_prefix(eapi)
248
249 class EbuildNestedDie(LineCheck):
250         """Check ebuild for nested die statements (die statements in subshells"""
251         
252         repoman_check_name = 'ebuild.nesteddie'
253         nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
254         
255         def check(self, num, line):
256                 if self.nesteddie_re.match(line):
257                         return errors.NESTED_DIE_ERROR
258
259
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)')
265
266         def check(self, num, line):
267                 match = self.uselessdodoc_re.match(line)
268                 if match:
269                         return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
270
271
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')
277
278         def __init__(self):
279                 self.check_next_line = False
280
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
288
289 class EapiDefinition(LineCheck):
290         """
291         Check that EAPI assignment conforms to PMS section 7.3.1
292         (first non-comment, non-blank line).
293         """
294         repoman_check_name = 'EAPI.definition'
295         ignore_comment = True
296         _eapi_re = portage._pms_eapi_re
297
298         def new(self, pkg):
299                 self._cached_eapi = pkg.metadata['EAPI']
300                 self._parsed_eapi = None
301                 self._eapi_line_num = None
302
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)
307                         if m is not None:
308                                 self._parsed_eapi = m.group(2)
309
310         def end(self):
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" % \
314                                         self._eapi_line_num
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)
319
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
325
326 class EbuildQuotedA(LineCheck):
327         """Ensure ebuilds have no quoting around ${A}"""
328
329         repoman_check_name = 'ebuild.minorsyn'
330         a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
331
332         def check(self, num, line):
333                 match = self.a_quoted.match(line)
334                 if match:
335                         return "Quoted \"${A}\" on line: %d"
336
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
340         helpers """
341
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
347
348 class ImplicitRuntimeDeps(LineCheck):
349         """
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).
352         """
353
354         repoman_check_name = 'RDEPEND.implicit'
355         _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=')
356
357         def new(self, pkg):
358                 self._rdepend = False
359                 self._depend = False
360
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)
366
367         def check(self, num, line):
368                 if not self._rdepend:
369                         m = self._assignment_re.match(line)
370                         if m is None:
371                                 pass
372                         elif m.group(1) == "RDEPEND":
373                                 self._rdepend = True
374                         elif m.group(1) == "DEPEND":
375                                 self._depend = True
376
377         def end(self):
378                 if self._depend and not self._rdepend:
379                         yield 'RDEPEND is not explicitly assigned'
380
381 class InheritDeprecated(LineCheck):
382         """Check if ebuild directly or indirectly inherits a deprecated eclass."""
383
384         repoman_check_name = 'inherit.deprecated'
385
386         # deprecated eclass : new eclass (False if no new eclass)
387         deprecated_classes = {
388                 "bash-completion": "bash-completion-r1",
389                 "gems": "ruby-fakegem",
390                 "git": "git-2",
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                 "qt3": False,
397                 "qt4": "qt4-r2",
398                 "ruby": "ruby-ng",
399                 "ruby-gnome2": "ruby-ng-gnome2",
400                 "x-modular": "xorg-2",
401                 }
402
403         _inherit_re = re.compile(r'^\s*inherit\s(.*)$')
404
405         def new(self, pkg):
406                 self._errors = []
407                 self._indirect_deprecated = set(eclass for eclass in \
408                         self.deprecated_classes if eclass in pkg.inherited)
409
410         def check(self, num, line):
411
412                 direct_inherits = None
413                 m = self._inherit_re.match(line)
414                 if m is not None:
415                         direct_inherits = m.group(1)
416                         if direct_inherits:
417                                 direct_inherits = direct_inherits.split()
418
419                 if not direct_inherits:
420                         return
421
422                 for eclass in direct_inherits:
423                         replacement = self.deprecated_classes.get(eclass)
424                         if replacement is None:
425                                 pass
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))
430                         else:
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))
435
436         def end(self):
437                 for error in self._errors:
438                         yield error
439                 del self._errors
440
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,)
446                         else:
447                                 yield "please migrate from indirect " + \
448                                         "inherit of '%s' to '%s'" % \
449                                         (eclass, replacement)
450                 del self._indirect_deprecated
451
452 class InheritEclass(LineCheck):
453         """
454         Base class for checking for missing inherits, as well as excess inherits.
455
456         Args:
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
461                                   inherit check.
462         """
463
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
470                 inherit_re = eclass
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')
473
474         def new(self, pkg):
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)
483                 else:
484                         self._disabled = False
485
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:
491                                 return
492                         s = self._func_re.search(line)
493                         if s:
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)
499
500         def end(self):
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
504
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',
533         'zproduct'
534 )
535
536 _eclass_info = {
537         'autotools': {
538                 'funcs': (
539                         'eaclocal', 'eautoconf', 'eautoheader',
540                         'eautomake', 'eautoreconf', '_elibtoolize',
541                         'eautopoint'
542                 ),
543                 'comprehensive': True,
544
545                 # Exempt eclasses:
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')
551         },
552
553         'eutils': {
554                 'funcs': (
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',
558                         'makeopts_jobs'
559                 ),
560                 'comprehensive': False,
561
562                 # These are "eclasses are the whole ebuild" type thing.
563                 'exempt_eclasses': _eclass_export_functions,
564         },
565
566         'flag-o-matic': {
567                 'funcs': (
568                         'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags',
569                         'append-((ld|c(pp|xx)?))?flags', 'append-libs',
570                 ),
571                 'comprehensive': False
572         },
573
574         'libtool': {
575                 'funcs': (
576                         'elibtoolize',
577                 ),
578                 'comprehensive': True,
579                 'exempt_eclasses': ('autotools',)
580         },
581
582         'multilib': {
583                 'funcs': (
584                         'get_libdir',
585                 ),
586
587                 # These are "eclasses are the whole ebuild" type thing.
588                 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool'),
589
590                 'comprehensive': False
591         },
592
593         'prefix': {
594                 'funcs': (
595                         'eprefixify',
596                 ),
597                 'comprehensive': True
598         },
599
600         'toolchain-funcs': {
601                 'funcs': (
602                         'gen_usr_ldscript',
603                 ),
604                 'comprehensive': False
605         },
606
607         'user': {
608                 'funcs': (
609                         'enewuser', 'enewgroup',
610                         'egetent', 'egethome', 'egetshell'
611                 ),
612                 'comprehensive': True
613         }
614 }
615
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.
619         _eclass_info = {
620                 'autotools': {
621                         'funcs': (
622                                 'eaclocal', 'eautoconf', 'eautoheader',
623                                 'eautomake', 'eautoreconf', '_elibtoolize',
624                                 'eautopoint'
625                         ),
626                         'comprehensive': True,
627                         'ignore_missing': True,
628                         'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
629                 },
630
631                 'prefix': {
632                         'funcs': (
633                                 'eprefixify',
634                         ),
635                         'comprehensive': False
636                 }
637         }
638
639 class IUseUndefined(LineCheck):
640         """
641         Make sure the ebuild defines IUSE (style guideline
642         says to define IUSE even when empty).
643         """
644
645         repoman_check_name = 'IUSE.undefined'
646         _iuse_def_re = re.compile(r'^IUSE=.*')
647
648         def new(self, pkg):
649                 self._iuse_def = None
650
651         def check(self, num, line):
652                 if self._iuse_def is None:
653                         self._iuse_def = self._iuse_def_re.match(line)
654
655         def end(self):
656                 if self._iuse_def is None:
657                         yield 'IUSE is not defined'
658
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
664
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):
668                                 return self.error
669
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
675
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
681
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
687
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
693
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
699
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')
704
705         def check(self, num, line):
706                 m = self._re.match(line)
707                 if m is not None:
708                         return 'WANT_AUTO' + m.group(1) + \
709                                 ' redundantly set to default value "latest" on line: %d'
710
711 class SrcCompileEconf(PhaseCheck):
712         repoman_check_name = 'ebuild.minorsyn'
713         configure_re = re.compile(r'\s(econf|./configure)')
714
715         def check_eapi(self, eapi):
716                 return eapi_has_src_prepare_and_src_configure(eapi)
717
718         def phase_check(self, num, line):
719                 if self.in_phase == 'src_compile':
720                         m = self.configure_re.match(line)
721                         if m is not None:
722                                 return ("'%s'" % m.group(1)) + \
723                                         " call should be moved to src_configure from line: %d"
724
725 class SrcUnpackPatches(PhaseCheck):
726         repoman_check_name = 'ebuild.minorsyn'
727         src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
728
729         def check_eapi(self, eapi):
730                 return eapi_has_src_prepare_and_src_configure(eapi)
731
732         def phase_check(self, num, line):
733                 if self.in_phase == 'src_unpack':
734                         m = self.src_prepare_tools_re.search(line)
735                         if m is not None:
736                                 return ("'%s'" % m.group(1)) + \
737                                         " call should be moved to src_prepare from line: %d"
738
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
743
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
749
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
755
756 # EAPI-3 checks
757 class Eapi3DeprecatedFuncs(LineCheck):
758         repoman_check_name = 'EAPI.deprecated'
759         deprecated_commands_re = re.compile(r'^\s*(check_license)\b')
760
761         def check_eapi(self, eapi):
762                 return eapi not in ('0', '1', '2')
763
764         def check(self, num, line):
765                 m = self.deprecated_commands_re.match(line)
766                 if m is not None:
767                         return ("'%s'" % m.group(1)) + \
768                                 " has been deprecated in EAPI=3 on line: %d"
769
770 # EAPI-4 checks
771 class Eapi4IncompatibleFuncs(LineCheck):
772         repoman_check_name = 'EAPI.incompatible'
773         banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
774
775         def check_eapi(self, eapi):
776                 return not eapi_has_dosed_dohard(eapi)
777
778         def check(self, num, line):
779                 m = self.banned_commands_re.match(line)
780                 if m is not None:
781                         return ("'%s'" % m.group(1)) + \
782                                 " has been banned in EAPI=4 on line: %d"
783
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))')
787
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)
791
792         def check(self, num, line):
793                 m = self.undefined_vars_re.match(line)
794                 if m is not None:
795                         return ("variable '$%s'" % m.group(1)) + \
796                                 " is gone in EAPI=4 on line: %d"
797
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')
805
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)
809                 if m is not None:
810                         return ("'%s'" % m.group(2)) + " called on line: %d"
811
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())))
825
826 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
827 _ignore_comment_re = re.compile(r'^\s*#')
828
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
834         multiline = None
835
836         for lc in checks:
837                 lc.new(pkg)
838         for num, line in enumerate(contents):
839
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:
849                         continue
850
851                 # Unroll multiline escaped strings so that we can check things:
852                 #               inherit foo bar \
853                 #                       moo \
854                 #                       cow
855                 # This will merge these lines like so:
856                 #               inherit foo bar         moo     cow
857                 try:
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')
866                 except SystemExit:
867                         raise
868                 except:
869                         # Who knows what kind of crazy crap an ebuild will have
870                         # in it -- don't allow it to kill us.
871                         line_escaped = line
872                 if multiline:
873                         # Chop off the \ and \n bytes from the previous line.
874                         multiline = multiline[:-2] + line
875                         if not line_escaped.endswith('\0'):
876                                 line = multiline
877                                 num = multinum
878                                 multiline = None
879                         else:
880                                 continue
881                 else:
882                         if line_escaped.endswith('\0'):
883                                 multinum = num
884                                 multiline = line
885                                 continue
886
887                 # Finally we have a full line to parse.
888                 is_comment = _ignore_comment_re.match(line) is not None
889                 for lc in checks:
890                         if is_comment and lc.ignore_comment:
891                                 continue
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)
896                                         if e:
897                                                 yield lc.repoman_check_name, e % (num + 1)
898
899         for lc in checks:
900                 i = lc.end()
901                 if i is not None:
902                         for e in i:
903                                 yield lc.repoman_check_name, e