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