Package repoman :: Module checks
[hide private]

Source Code for Module repoman.checks

  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):
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 preserve_old_lib function.""" 686 repoman_check_name = 'upstream.workaround' 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):
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):
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
911