repoman: change preserve_old_lib msg, bug #480244
[portage.git] / pym / repoman / checks.py
1 # repoman: Checks
2 # Copyright 2007-2013 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 from __future__ import unicode_literals
9
10 import codecs
11 from itertools import chain
12 import re
13 import time
14 import repoman.errors as errors
15 import portage
16 from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \
17         eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \
18         eapi_exports_AA
19 from portage.const import _ENABLE_INHERIT_CHECK
20
21 class LineCheck(object):
22         """Run a check on a line of an ebuild."""
23         """A regular expression to determine whether to ignore the line"""
24         ignore_line = False
25         """True if lines containing nothing more than comments with optional
26         leading whitespace should be ignored"""
27         ignore_comment = True
28
29         def new(self, pkg):
30                 pass
31
32         def check_eapi(self, eapi):
33                 """ returns if the check should be run in the given EAPI (default is True) """
34                 return True
35
36         def check(self, num, line):
37                 """Run the check on line and return error if there is one"""
38                 if self.re.match(line):
39                         return self.error
40
41         def end(self):
42                 pass
43
44 class PhaseCheck(LineCheck):
45         """ basic class for function detection """
46
47         func_end_re = re.compile(r'^\}$')
48         phases_re = re.compile('(%s)' % '|'.join((
49                 'pkg_pretend', 'pkg_setup', 'src_unpack', 'src_prepare',
50                 'src_configure', 'src_compile', 'src_test', 'src_install',
51                 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm',
52                 'pkg_config')))
53         in_phase = ''
54
55         def check(self, num, line):
56                 m = self.phases_re.match(line)
57                 if m is not None:
58                         self.in_phase = m.group(1)
59                 if self.in_phase != '' and \
60                                 self.func_end_re.match(line) is not None:
61                         self.in_phase = ''
62
63                 return self.phase_check(num, line)
64
65         def phase_check(self, num, line):
66                 """ override this function for your checks """
67                 pass
68
69 class EbuildHeader(LineCheck):
70         """Ensure ebuilds have proper headers
71                 Copyright header errors
72                 CVS header errors
73                 License header errors
74
75         Args:
76                 modification_year - Year the ebuild was last modified
77         """
78
79         repoman_check_name = 'ebuild.badheader'
80
81         gentoo_copyright = r'^# Copyright ((1999|2\d\d\d)-)?%s Gentoo Foundation$'
82         # Why a regex here, use a string match
83         # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
84         gentoo_license = '# Distributed under the terms of the GNU General Public License v2'
85         cvs_header = re.compile(r'^# \$Header: .*\$$')
86         ignore_comment = False
87
88         def new(self, pkg):
89                 if pkg.mtime is None:
90                         self.modification_year = r'2\d\d\d'
91                 else:
92                         self.modification_year = str(time.gmtime(pkg.mtime)[0])
93                 self.gentoo_copyright_re = re.compile(
94                         self.gentoo_copyright % self.modification_year)
95
96         def check(self, num, line):
97                 if num > 2:
98                         return
99                 elif num == 0:
100                         if not self.gentoo_copyright_re.match(line):
101                                 return errors.COPYRIGHT_ERROR
102                 elif num == 1 and line.rstrip('\n') != self.gentoo_license:
103                         return errors.LICENSE_ERROR
104                 elif num == 2:
105                         if not self.cvs_header.match(line):
106                                 return errors.CVS_HEADER_ERROR
107
108
109 class EbuildWhitespace(LineCheck):
110         """Ensure ebuilds have proper whitespacing"""
111
112         repoman_check_name = 'ebuild.minorsyn'
113
114         ignore_line = re.compile(r'(^$)|(^(\t)*#)')
115         ignore_comment = False
116         leading_spaces = re.compile(r'^[\S\t]')
117         trailing_whitespace = re.compile(r'.*([\S]$)')
118
119         def check(self, num, line):
120                 if self.leading_spaces.match(line) is None:
121                         return errors.LEADING_SPACES_ERROR
122                 if self.trailing_whitespace.match(line) is None:
123                         return errors.TRAILING_WHITESPACE_ERROR
124
125 class EbuildBlankLine(LineCheck):
126         repoman_check_name = 'ebuild.minorsyn'
127         ignore_comment = False
128         blank_line = re.compile(r'^$')
129
130         def new(self, pkg):
131                 self.line_is_blank = False
132
133         def check(self, num, line):
134                 if self.line_is_blank and self.blank_line.match(line):
135                         return 'Useless blank line on line: %d'
136                 if self.blank_line.match(line):
137                         self.line_is_blank = True
138                 else:
139                         self.line_is_blank = False
140
141         def end(self):
142                 if self.line_is_blank:
143                         yield 'Useless blank line on last line'
144
145 class EbuildQuote(LineCheck):
146         """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
147
148         repoman_check_name = 'ebuild.minorsyn'
149         _message_commands = ["die", "echo", "eerror",
150                 "einfo", "elog", "eqawarn", "ewarn"]
151         _message_re = re.compile(r'\s(' + "|".join(_message_commands) + \
152                 r')\s+"[^"]*"\s*$')
153         _ignored_commands = ["local", "export"] + _message_commands
154         ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \
155                 r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)')
156         ignore_comment = False
157         var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"]
158
159         # EAPI=3/Prefix vars
160         var_names += ["ED", "EPREFIX", "EROOT"]
161
162         # variables for games.eclass
163         var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
164                 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
165                 "GAMES_LOGDIR", "GAMES_BINDIR"]
166
167         # variables for multibuild.eclass
168         var_names += ["BUILD_DIR"]
169
170         var_names = "(%s)" % "|".join(var_names)
171         var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
172                 var_names + '\W)')
173         missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
174                 r'\}?[^"\'\s]*(\s|$)')
175         cond_begin =  re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
176         cond_end =  re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
177
178         def check(self, num, line):
179                 if self.var_reference.search(line) is None:
180                         return
181                 # There can be multiple matches / violations on a single line. We
182                 # have to make sure none of the matches are violators. Once we've
183                 # found one violator, any remaining matches on the same line can
184                 # be ignored.
185                 pos = 0
186                 while pos <= len(line) - 1:
187                         missing_quotes = self.missing_quotes.search(line, pos)
188                         if not missing_quotes:
189                                 break
190                         # If the last character of the previous match is a whitespace
191                         # character, that character may be needed for the next
192                         # missing_quotes match, so search overlaps by 1 character.
193                         group = missing_quotes.group()
194                         pos = missing_quotes.end() - 1
195
196                         # Filter out some false positives that can
197                         # get through the missing_quotes regex.
198                         if self.var_reference.search(group) is None:
199                                 continue
200
201                         # Filter matches that appear to be an
202                         # argument to a message command.
203                         # For example: false || ewarn "foo $WORKDIR/bar baz"
204                         message_match = self._message_re.search(line)
205                         if message_match is not None and \
206                                 message_match.start() < pos and \
207                                 message_match.end() > pos:
208                                 break
209
210                         # This is an attempt to avoid false positives without getting
211                         # too complex, while possibly allowing some (hopefully
212                         # unlikely) violations to slip through. We just assume
213                         # everything is correct if the there is a ' [[ ' or a ' ]] '
214                         # anywhere in the whole line (possibly continued over one
215                         # line).
216                         if self.cond_begin.search(line) is not None:
217                                 continue
218                         if self.cond_end.search(line) is not None:
219                                 continue
220
221                         # Any remaining matches on the same line can be ignored.
222                         return errors.MISSING_QUOTES_ERROR
223
224
225 class EbuildAssignment(LineCheck):
226         """Ensure ebuilds don't assign to readonly variables."""
227
228         repoman_check_name = 'variable.readonly'
229         readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
230
231         def check(self, num, line):
232                 match = self.readonly_assignment.match(line)
233                 e = None
234                 if match is not None:
235                         e = errors.READONLY_ASSIGNMENT_ERROR
236                 return e
237
238 class Eapi3EbuildAssignment(EbuildAssignment):
239         """Ensure ebuilds don't assign to readonly EAPI 3-introduced variables."""
240
241         readonly_assignment = re.compile(r'\s*(export\s+)?(ED|EPREFIX|EROOT)=')
242
243         def check_eapi(self, eapi):
244                 return eapi_supports_prefix(eapi)
245
246 class EbuildNestedDie(LineCheck):
247         """Check ebuild for nested die statements (die statements in subshells)"""
248
249         repoman_check_name = 'ebuild.nesteddie'
250         nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
251
252         def check(self, num, line):
253                 if self.nesteddie_re.match(line):
254                         return errors.NESTED_DIE_ERROR
255
256
257 class EbuildUselessDodoc(LineCheck):
258         """Check ebuild for useless files in dodoc arguments."""
259         repoman_check_name = 'ebuild.minorsyn'
260         uselessdodoc_re = re.compile(
261                 r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENCE|LICENSE)($|\s)')
262
263         def check(self, num, line):
264                 match = self.uselessdodoc_re.match(line)
265                 if match:
266                         return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
267
268
269 class EbuildUselessCdS(LineCheck):
270         """Check for redundant cd ${S} statements"""
271         repoman_check_name = 'ebuild.minorsyn'
272         method_re = re.compile(r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)')
273         cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
274
275         def __init__(self):
276                 self.check_next_line = False
277
278         def check(self, num, line):
279                 if self.check_next_line:
280                         self.check_next_line = False
281                         if self.cds_re.match(line):
282                                 return errors.REDUNDANT_CD_S_ERROR
283                 elif self.method_re.match(line):
284                         self.check_next_line = True
285
286 class EapiDefinition(LineCheck):
287         """
288         Check that EAPI assignment conforms to PMS section 7.3.1
289         (first non-comment, non-blank line).
290         """
291         repoman_check_name = 'EAPI.definition'
292         ignore_comment = True
293         _eapi_re = portage._pms_eapi_re
294
295         def new(self, pkg):
296                 self._cached_eapi = pkg.eapi
297                 self._parsed_eapi = None
298                 self._eapi_line_num = None
299
300         def check(self, num, line):
301                 if self._eapi_line_num is None and line.strip():
302                         self._eapi_line_num = num + 1
303                         m = self._eapi_re.match(line)
304                         if m is not None:
305                                 self._parsed_eapi = m.group(2)
306
307         def end(self):
308                 if self._parsed_eapi is None:
309                         if self._cached_eapi != "0":
310                                 yield "valid EAPI assignment must occur on or before line: %s" % \
311                                         self._eapi_line_num
312                 elif self._parsed_eapi != self._cached_eapi:
313                         yield ("bash returned EAPI '%s' which does not match "
314                                 "assignment on line: %s") % \
315                                 (self._cached_eapi, self._eapi_line_num)
316
317 class EbuildPatches(LineCheck):
318         """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety"""
319         repoman_check_name = 'ebuild.patches'
320         re = re.compile(r'^\s*PATCHES=[^\(]')
321         error = errors.PATCHES_ERROR
322
323 class EbuildQuotedA(LineCheck):
324         """Ensure ebuilds have no quoting around ${A}"""
325
326         repoman_check_name = 'ebuild.minorsyn'
327         a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
328
329         def check(self, num, line):
330                 match = self.a_quoted.match(line)
331                 if match:
332                         return "Quoted \"${A}\" on line: %d"
333
334 class NoOffsetWithHelpers(LineCheck):
335         """ Check that the image location, the alternate root offset, and the
336         offset prefix (D, ROOT, ED, EROOT and EPREFIX) are not used with
337         helpers """
338
339         repoman_check_name = 'variable.usedwithhelpers'
340         # Ignore matches in quoted strings like this:
341         # elog "installed into ${ROOT}usr/share/php5/apc/."
342         re = re.compile(r'^[^#"\']*\b(docinto|docompress|dodir|dohard|exeinto|fowners|fperms|insinto|into)\s+"?\$\{?(D|ROOT|ED|EROOT|EPREFIX)\b.*')
343         error = errors.NO_OFFSET_WITH_HELPERS
344
345 class ImplicitRuntimeDeps(LineCheck):
346         """
347         Detect the case where DEPEND is set and RDEPEND is unset in the ebuild,
348         since this triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4).
349         """
350
351         repoman_check_name = 'RDEPEND.implicit'
352         _assignment_re = re.compile(r'^\s*(R?DEPEND)\+?=')
353
354         def new(self, pkg):
355                 self._rdepend = False
356                 self._depend = False
357
358         def check_eapi(self, eapi):
359                 # Beginning with EAPI 4, there is no
360                 # implicit RDEPEND=$DEPEND assignment
361                 # to be concerned with.
362                 return eapi_has_implicit_rdepend(eapi)
363
364         def check(self, num, line):
365                 if not self._rdepend:
366                         m = self._assignment_re.match(line)
367                         if m is None:
368                                 pass
369                         elif m.group(1) == "RDEPEND":
370                                 self._rdepend = True
371                         elif m.group(1) == "DEPEND":
372                                 self._depend = True
373
374         def end(self):
375                 if self._depend and not self._rdepend:
376                         yield 'RDEPEND is not explicitly assigned'
377
378 class InheritDeprecated(LineCheck):
379         """Check if ebuild directly or indirectly inherits a deprecated eclass."""
380
381         repoman_check_name = 'inherit.deprecated'
382
383         # deprecated eclass : new eclass (False if no new eclass)
384         deprecated_classes = {
385                 "bash-completion": "bash-completion-r1",
386                 "boost-utils": False,
387                 "distutils": "distutils-r1",
388                 "gems": "ruby-fakegem",
389                 "git": "git-2",
390                 "mono": "mono-env",
391                 "mozconfig-2": "mozconfig-3",
392                 "mozcoreconf": "mozcoreconf-2",
393                 "php-ext-pecl-r1": "php-ext-pecl-r2",
394                 "php-ext-source-r1": "php-ext-source-r2",
395                 "php-pear": "php-pear-r1",
396                 "python": "python-r1 / python-single-r1 / python-any-r1",
397                 "python-distutils-ng": "python-r1 + distutils-r1",
398                 "qt3": False,
399                 "qt4": "qt4-r2",
400                 "ruby": "ruby-ng",
401                 "ruby-gnome2": "ruby-ng-gnome2",
402                 "x-modular": "xorg-2",
403                 }
404
405         _inherit_re = re.compile(r'^\s*inherit\s(.*)$')
406
407         def new(self, pkg):
408                 self._errors = []
409                 self._indirect_deprecated = set(eclass for eclass in \
410                         self.deprecated_classes if eclass in pkg.inherited)
411
412         def check(self, num, line):
413
414                 direct_inherits = None
415                 m = self._inherit_re.match(line)
416                 if m is not None:
417                         direct_inherits = m.group(1)
418                         if direct_inherits:
419                                 direct_inherits = direct_inherits.split()
420
421                 if not direct_inherits:
422                         return
423
424                 for eclass in direct_inherits:
425                         replacement = self.deprecated_classes.get(eclass)
426                         if replacement is None:
427                                 pass
428                         elif replacement is False:
429                                 self._indirect_deprecated.discard(eclass)
430                                 self._errors.append("please migrate from " + \
431                                         "'%s' (no replacement) on line: %d" % (eclass, num + 1))
432                         else:
433                                 self._indirect_deprecated.discard(eclass)
434                                 self._errors.append("please migrate from " + \
435                                         "'%s' to '%s' on line: %d" % \
436                                         (eclass, replacement, num + 1))
437
438         def end(self):
439                 for error in self._errors:
440                         yield error
441                 del self._errors
442
443                 for eclass in self._indirect_deprecated:
444                         replacement = self.deprecated_classes[eclass]
445                         if replacement is False:
446                                 yield "please migrate from indirect " + \
447                                         "inherit of '%s' (no replacement)" % (eclass,)
448                         else:
449                                 yield "please migrate from indirect " + \
450                                         "inherit of '%s' to '%s'" % \
451                                         (eclass, replacement)
452                 del self._indirect_deprecated
453
454 class InheritEclass(LineCheck):
455         """
456         Base class for checking for missing inherits, as well as excess inherits.
457
458         Args:
459                 eclass: Set to the name of your eclass.
460                 funcs: A tuple of functions that this eclass provides.
461                 comprehensive: Is the list of functions complete?
462                 exempt_eclasses: If these eclasses are inherited, disable the missing
463                                   inherit check.
464         """
465
466         def __init__(self, eclass, funcs=None, comprehensive=False,
467                 exempt_eclasses=None, ignore_missing=False, **kwargs):
468                 self._eclass = eclass
469                 self._comprehensive = comprehensive
470                 self._exempt_eclasses = exempt_eclasses
471                 self._ignore_missing = ignore_missing
472                 inherit_re = eclass
473                 self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re)
474                 # Match when the function is preceded only by leading whitespace, a
475                 # shell operator such as (, {, |, ||, or &&, or optional variable
476                 # setting(s). This prevents false positives in things like elog
477                 # messages, as reported in bug #413285.
478                 self._func_re = re.compile(r'(^|[|&{(])\s*(\w+=.*)?\b(' + '|'.join(funcs) + r')\b')
479
480         def new(self, pkg):
481                 self.repoman_check_name = 'inherit.missing'
482                 # We can't use pkg.inherited because that tells us all the eclasses that
483                 # have been inherited and not just the ones we inherit directly.
484                 self._inherit = False
485                 self._func_call = False
486                 if self._exempt_eclasses is not None:
487                         inherited = pkg.inherited
488                         self._disabled = any(x in inherited for x in self._exempt_eclasses)
489                 else:
490                         self._disabled = False
491                 self._eapi = pkg.eapi
492
493         def check(self, num, line):
494                 if not self._inherit:
495                         self._inherit = self._inherit_re.match(line)
496                 if not self._inherit:
497                         if self._disabled or self._ignore_missing:
498                                 return
499                         s = self._func_re.search(line)
500                         if s is not None:
501                                 func_name = s.group(3)
502                                 eapi_func = _eclass_eapi_functions.get(func_name)
503                                 if eapi_func is None or not eapi_func(self._eapi):
504                                         self._func_call = True
505                                         return ('%s.eclass is not inherited, '
506                                                 'but "%s" found at line: %s') % \
507                                                 (self._eclass, func_name, '%d')
508                 elif not self._func_call:
509                         self._func_call = self._func_re.search(line)
510
511         def end(self):
512                 if not self._disabled and self._comprehensive and self._inherit and not self._func_call:
513                         self.repoman_check_name = 'inherit.unused'
514                         yield 'no function called from %s.eclass; please drop' % self._eclass
515
516 _eclass_eapi_functions = {
517         "usex" : lambda eapi: eapi not in ("0", "1", "2", "3", "4", "4-python", "4-slot-abi")
518 }
519
520 # eclasses that export ${ECLASS}_src_(compile|configure|install)
521 _eclass_export_functions = (
522         'ant-tasks', 'apache-2', 'apache-module', 'aspell-dict',
523         'autotools-utils', 'base', 'bsdmk', 'cannadic',
524         'clutter', 'cmake-utils', 'db', 'distutils', 'elisp',
525         'embassy', 'emboss', 'emul-linux-x86', 'enlightenment',
526         'font-ebdftopcf', 'font', 'fox', 'freebsd', 'freedict',
527         'games', 'games-ggz', 'games-mods', 'gdesklets',
528         'gems', 'gkrellm-plugin', 'gnatbuild', 'gnat', 'gnome2',
529         'gnome-python-common', 'gnustep-base', 'go-mono', 'gpe',
530         'gst-plugins-bad', 'gst-plugins-base', 'gst-plugins-good',
531         'gst-plugins-ugly', 'gtk-sharp-module', 'haskell-cabal',
532         'horde', 'java-ant-2', 'java-pkg-2', 'java-pkg-simple',
533         'java-virtuals-2', 'kde4-base', 'kde4-meta', 'kernel-2',
534         'latex-package', 'linux-mod', 'mozlinguas', 'myspell',
535         'myspell-r2', 'mysql', 'mysql-v2', 'mythtv-plugins',
536         'oasis', 'obs-service', 'office-ext', 'perl-app',
537         'perl-module', 'php-ext-base-r1', 'php-ext-pecl-r2',
538         'php-ext-source-r2', 'php-lib-r1', 'php-pear-lib-r1',
539         'php-pear-r1', 'python-distutils-ng', 'python',
540         'qt4-build', 'qt4-r2', 'rox-0install', 'rox', 'ruby',
541         'ruby-ng', 'scsh', 'selinux-policy-2', 'sgml-catalog',
542         'stardict', 'sword-module', 'tetex-3', 'tetex',
543         'texlive-module', 'toolchain-binutils', 'toolchain',
544         'twisted', 'vdr-plugin-2', 'vdr-plugin', 'vim',
545         'vim-plugin', 'vim-spell', 'virtuoso', 'vmware',
546         'vmware-mod', 'waf-utils', 'webapp', 'xemacs-elisp',
547         'xemacs-packages', 'xfconf', 'x-modular', 'xorg-2',
548         'zproduct'
549 )
550
551 _eclass_info = {
552         'autotools': {
553                 'funcs': (
554                         'eaclocal', 'eautoconf', 'eautoheader',
555                         'eautomake', 'eautoreconf', '_elibtoolize',
556                         'eautopoint'
557                 ),
558                 'comprehensive': True,
559
560                 # Exempt eclasses:
561                 # git - An EGIT_BOOTSTRAP variable may be used to call one of
562                 #       the autotools functions.
563                 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of
564                 #       the autotools functions.
565                 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
566         },
567
568         'eutils': {
569                 'funcs': (
570                         'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop',
571                         'eumask_push', 'eumask_pop', 'epatch', 'epatch_user',
572                         'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex'
573                 ),
574                 'comprehensive': False,
575
576                 # These are "eclasses are the whole ebuild" type thing.
577                 'exempt_eclasses': _eclass_export_functions,
578         },
579
580         'flag-o-matic': {
581                 'funcs': (
582                         'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags',
583                         'append-((ld|c(pp|xx)?))?flags', 'append-libs',
584                 ),
585                 'comprehensive': False
586         },
587
588         'libtool': {
589                 'funcs': (
590                         'elibtoolize',
591                 ),
592                 'comprehensive': True,
593                 'exempt_eclasses': ('autotools',)
594         },
595
596         'multilib': {
597                 'funcs': (
598                         'get_libdir',
599                 ),
600
601                 # These are "eclasses are the whole ebuild" type thing.
602                 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool',
603                         'multilib-minimal'),
604
605                 'comprehensive': False
606         },
607
608         'multiprocessing': {
609                 'funcs': (
610                         'makeopts_jobs',
611                 ),
612                 'comprehensive': False
613         },
614
615         'prefix': {
616                 'funcs': (
617                         'eprefixify',
618                 ),
619                 'comprehensive': True
620         },
621
622         'toolchain-funcs': {
623                 'funcs': (
624                         'gen_usr_ldscript',
625                 ),
626                 'comprehensive': False
627         },
628
629         'user': {
630                 'funcs': (
631                         'enewuser', 'enewgroup',
632                         'egetent', 'egethome', 'egetshell', 'esethome'
633                 ),
634                 'comprehensive': True
635         }
636 }
637
638 if not _ENABLE_INHERIT_CHECK:
639         # Since the InheritEclass check is experimental, in the stable branch
640         # we emulate the old eprefixify.defined and inherit.autotools checks.
641         _eclass_info = {
642                 'autotools': {
643                         'funcs': (
644                                 'eaclocal', 'eautoconf', 'eautoheader',
645                                 'eautomake', 'eautoreconf', '_elibtoolize',
646                                 'eautopoint'
647                         ),
648                         'comprehensive': True,
649                         'ignore_missing': True,
650                         'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils')
651                 },
652
653                 'prefix': {
654                         'funcs': (
655                                 'eprefixify',
656                         ),
657                         'comprehensive': False
658                 }
659         }
660
661 class EMakeParallelDisabled(PhaseCheck):
662         """Check for emake -j1 calls which disable parallelization."""
663         repoman_check_name = 'upstream.workaround'
664         re = re.compile(r'^\s*emake\s+.*-j\s*1\b')
665         error = errors.EMAKE_PARALLEL_DISABLED
666
667         def phase_check(self, num, line):
668                 if self.in_phase == 'src_compile' or self.in_phase == 'src_install':
669                         if self.re.match(line):
670                                 return self.error
671
672 class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
673         """Check for MAKEOPTS=-j1 that disables parallelization."""
674         repoman_check_name = 'upstream.workaround'
675         re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b')
676         error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
677
678 class NoAsNeeded(LineCheck):
679         """Check for calls to the no-as-needed function."""
680         repoman_check_name = 'upstream.workaround'
681         re = re.compile(r'.*\$\(no-as-needed\)')
682         error = errors.NO_AS_NEEDED
683
684 class PreserveOldLib(LineCheck):
685         """Check for calls to the deprecated preserve_old_lib function."""
686         repoman_check_name = 'ebuild.minorsyn'
687         re = re.compile(r'.*preserve_old_lib')
688         error = errors.PRESERVE_OLD_LIB
689
690 class SandboxAddpredict(LineCheck):
691         """Check for calls to the addpredict function."""
692         repoman_check_name = 'upstream.workaround'
693         re = re.compile(r'(^|\s)addpredict\b')
694         error = errors.SANDBOX_ADDPREDICT
695
696 class DeprecatedBindnowFlags(LineCheck):
697         """Check for calls to the deprecated bindnow-flags function."""
698         repoman_check_name = 'ebuild.minorsyn'
699         re = re.compile(r'.*\$\(bindnow-flags\)')
700         error = errors.DEPRECATED_BINDNOW_FLAGS
701
702 class WantAutoDefaultValue(LineCheck):
703         """Check setting WANT_AUTO* to latest (default value)."""
704         repoman_check_name = 'ebuild.minorsyn'
705         _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest')
706
707         def check(self, num, line):
708                 m = self._re.match(line)
709                 if m is not None:
710                         return 'WANT_AUTO' + m.group(1) + \
711                                 ' redundantly set to default value "latest" on line: %d'
712
713 class SrcCompileEconf(PhaseCheck):
714         repoman_check_name = 'ebuild.minorsyn'
715         configure_re = re.compile(r'\s(econf|./configure)')
716
717         def check_eapi(self, eapi):
718                 return eapi_has_src_prepare_and_src_configure(eapi)
719
720         def phase_check(self, num, line):
721                 if self.in_phase == 'src_compile':
722                         m = self.configure_re.match(line)
723                         if m is not None:
724                                 return ("'%s'" % m.group(1)) + \
725                                         " call should be moved to src_configure from line: %d"
726
727 class SrcUnpackPatches(PhaseCheck):
728         repoman_check_name = 'ebuild.minorsyn'
729         src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
730
731         def check_eapi(self, eapi):
732                 return eapi_has_src_prepare_and_src_configure(eapi)
733
734         def phase_check(self, num, line):
735                 if self.in_phase == 'src_unpack':
736                         m = self.src_prepare_tools_re.search(line)
737                         if m is not None:
738                                 return ("'%s'" % m.group(1)) + \
739                                         " call should be moved to src_prepare from line: %d"
740
741 class BuiltWithUse(LineCheck):
742         repoman_check_name = 'ebuild.minorsyn'
743         re = re.compile(r'(^|.*\b)built_with_use\b')
744         error = errors.BUILT_WITH_USE
745
746 class DeprecatedUseq(LineCheck):
747         """Checks for use of the deprecated useq function"""
748         repoman_check_name = 'ebuild.minorsyn'
749         re = re.compile(r'(^|.*\b)useq\b')
750         error = errors.USEQ_ERROR
751
752 class DeprecatedHasq(LineCheck):
753         """Checks for use of the deprecated hasq function"""
754         repoman_check_name = 'ebuild.minorsyn'
755         re = re.compile(r'(^|.*\b)hasq\b')
756         error = errors.HASQ_ERROR
757
758 # EAPI-3 checks
759 class Eapi3DeprecatedFuncs(LineCheck):
760         repoman_check_name = 'EAPI.deprecated'
761         deprecated_commands_re = re.compile(r'^\s*(check_license)\b')
762
763         def check_eapi(self, eapi):
764                 return eapi not in ('0', '1', '2')
765
766         def check(self, num, line):
767                 m = self.deprecated_commands_re.match(line)
768                 if m is not None:
769                         return ("'%s'" % m.group(1)) + \
770                                 " has been deprecated in EAPI=3 on line: %d"
771
772 # EAPI-4 checks
773 class Eapi4IncompatibleFuncs(LineCheck):
774         repoman_check_name = 'EAPI.incompatible'
775         banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
776
777         def check_eapi(self, eapi):
778                 return not eapi_has_dosed_dohard(eapi)
779
780         def check(self, num, line):
781                 m = self.banned_commands_re.match(line)
782                 if m is not None:
783                         return ("'%s'" % m.group(1)) + \
784                                 " has been banned in EAPI=4 on line: %d"
785
786 class Eapi4GoneVars(LineCheck):
787         repoman_check_name = 'EAPI.incompatible'
788         undefined_vars_re = re.compile(r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))')
789
790         def check_eapi(self, eapi):
791                 # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later.
792                 return not eapi_exports_AA(eapi)
793
794         def check(self, num, line):
795                 m = self.undefined_vars_re.match(line)
796                 if m is not None:
797                         return ("variable '$%s'" % m.group(1)) + \
798                                 " is gone in EAPI=4 on line: %d"
799
800 class PortageInternal(LineCheck):
801         repoman_check_name = 'portage.internal'
802         ignore_comment = True
803         # Match when the command is preceded only by leading whitespace or a shell
804         # operator such as (, {, |, ||, or &&. This prevents false positives in
805         # things like elog messages, as reported in bug #413285.
806         re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b')
807
808         def check(self, num, line):
809                 """Run the check on line and return error if there is one"""
810                 m = self.re.match(line)
811                 if m is not None:
812                         return ("'%s'" % m.group(2)) + " called on line: %d"
813
814 class PortageInternalVariableAssignment(LineCheck):
815         repoman_check_name = 'portage.internal'
816         internal_assignment = re.compile(r'\s*(export\s+)?(EXTRA_ECONF|EXTRA_EMAKE)\+?=')
817
818         def check(self, num, line):
819                 match = self.internal_assignment.match(line)
820                 e = None
821                 if match is not None:
822                         e = 'Assignment to variable %s' % match.group(2)
823                         e += ' on line: %d'
824                 return e
825
826 _base_check_classes = (InheritEclass, LineCheck, PhaseCheck)
827 _constant_checks = tuple(chain((v() for k, v in globals().items()
828         if isinstance(v, type) and issubclass(v, LineCheck) and v not in _base_check_classes),
829         (InheritEclass(k, **portage._native_kwargs(kwargs))
830         for k, kwargs in _eclass_info.items())))
831
832 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
833 _ignore_comment_re = re.compile(r'^\s*#')
834
835 def run_checks(contents, pkg):
836         unicode_escape_codec = codecs.lookup('unicode_escape')
837         unicode_escape = lambda x: unicode_escape_codec.decode(x)[0]
838         checks = _constant_checks
839         here_doc_delim = None
840         multiline = None
841
842         for lc in checks:
843                 lc.new(pkg)
844         for num, line in enumerate(contents):
845
846                 # Check if we're inside a here-document.
847                 if here_doc_delim is not None:
848                         if here_doc_delim.match(line):
849                                 here_doc_delim = None
850                 if here_doc_delim is None:
851                         here_doc = _here_doc_re.match(line)
852                         if here_doc is not None:
853                                 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1))
854                 if here_doc_delim is not None:
855                         continue
856
857                 # Unroll multiline escaped strings so that we can check things:
858                 #               inherit foo bar \
859                 #                       moo \
860                 #                       cow
861                 # This will merge these lines like so:
862                 #               inherit foo bar         moo     cow
863                 try:
864                         # A normal line will end in the two bytes: <\> <\n>.  So decoding
865                         # that will result in python thinking the <\n> is being escaped
866                         # and eat the single <\> which makes it hard for us to detect.
867                         # Instead, strip the newline (which we know all lines have), and
868                         # append a <0>.  Then when python escapes it, if the line ended
869                         # in a <\>, we'll end up with a <\0> marker to key off of.  This
870                         # shouldn't be a problem with any valid ebuild ...
871                         line_escaped = unicode_escape(line.rstrip('\n') + '0')
872                 except SystemExit:
873                         raise
874                 except:
875                         # Who knows what kind of crazy crap an ebuild will have
876                         # in it -- don't allow it to kill us.
877                         line_escaped = line
878                 if multiline:
879                         # Chop off the \ and \n bytes from the previous line.
880                         multiline = multiline[:-2] + line
881                         if not line_escaped.endswith('\0'):
882                                 line = multiline
883                                 num = multinum
884                                 multiline = None
885                         else:
886                                 continue
887                 else:
888                         if line_escaped.endswith('\0'):
889                                 multinum = num
890                                 multiline = line
891                                 continue
892
893                 if not line.endswith("#nowarn\n"):
894                         # Finally we have a full line to parse.
895                         is_comment = _ignore_comment_re.match(line) is not None
896                         for lc in checks:
897                                 if is_comment and lc.ignore_comment:
898                                         continue
899                                 if lc.check_eapi(pkg.eapi):
900                                         ignore = lc.ignore_line
901                                         if not ignore or not ignore.match(line):
902                                                 e = lc.check(num, line)
903                                                 if e:
904                                                         yield lc.repoman_check_name, e % (num + 1)
905
906         for lc in checks:
907                 i = lc.end()
908                 if i is not None:
909                         for e in i:
910                                 yield lc.repoman_check_name, e