Package repoman :: Module checks
[hide private]

Source Code for Module repoman.checks

  1  # repoman: Checks 
  2  # Copyright 2007-2014 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, eapi_has_pkg_pretend 
 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 "mono": "mono-env", 389 "python": "python-r1 / python-single-r1 / python-any-r1", 390 "ruby": "ruby-ng", 391 "x-modular": "xorg-2", 392 } 393 394 _inherit_re = re.compile(r'^\s*inherit\s(.*)$') 395
396 - def new(self, pkg):
397 self._errors = []
398
399 - def check(self, num, line):
400 direct_inherits = None 401 m = self._inherit_re.match(line) 402 if m is not None: 403 direct_inherits = m.group(1) 404 if direct_inherits: 405 direct_inherits = direct_inherits.split() 406 407 if not direct_inherits: 408 return 409 410 for eclass in direct_inherits: 411 replacement = self.deprecated_classes.get(eclass) 412 if replacement is None: 413 pass 414 elif replacement is False: 415 self._errors.append("please migrate from " + \ 416 "'%s' (no replacement) on line: %d" % (eclass, num + 1)) 417 else: 418 self._errors.append("please migrate from " + \ 419 "'%s' to '%s' on line: %d" % \ 420 (eclass, replacement, num + 1))
421
422 - def end(self):
423 for error in self._errors: 424 yield error 425 del self._errors
426
427 -class InheritEclass(LineCheck):
428 """ 429 Base class for checking for missing inherits, as well as excess inherits. 430 431 Args: 432 eclass: Set to the name of your eclass. 433 funcs: A tuple of functions that this eclass provides. 434 comprehensive: Is the list of functions complete? 435 exempt_eclasses: If these eclasses are inherited, disable the missing 436 inherit check. 437 """ 438
439 - def __init__(self, eclass, funcs=None, comprehensive=False, 440 exempt_eclasses=None, ignore_missing=False, **kwargs):
441 self._eclass = eclass 442 self._comprehensive = comprehensive 443 self._exempt_eclasses = exempt_eclasses 444 self._ignore_missing = ignore_missing 445 inherit_re = eclass 446 self._inherit_re = re.compile(r'^(\s*|.*[|&]\s*)\binherit\s(.*\s)?%s(\s|$)' % inherit_re) 447 # Match when the function is preceded only by leading whitespace, a 448 # shell operator such as (, {, |, ||, or &&, or optional variable 449 # setting(s). This prevents false positives in things like elog 450 # messages, as reported in bug #413285. 451 self._func_re = re.compile(r'(^|[|&{(])\s*(\w+=.*)?\b(' + '|'.join(funcs) + r')\b')
452
453 - def new(self, pkg):
454 self.repoman_check_name = 'inherit.missing' 455 # We can't use pkg.inherited because that tells us all the eclasses that 456 # have been inherited and not just the ones we inherit directly. 457 self._inherit = False 458 self._func_call = False 459 if self._exempt_eclasses is not None: 460 inherited = pkg.inherited 461 self._disabled = any(x in inherited for x in self._exempt_eclasses) 462 else: 463 self._disabled = False 464 self._eapi = pkg.eapi
465
466 - def check(self, num, line):
467 if not self._inherit: 468 self._inherit = self._inherit_re.match(line) 469 if not self._inherit: 470 if self._disabled or self._ignore_missing: 471 return 472 s = self._func_re.search(line) 473 if s is not None: 474 func_name = s.group(3) 475 eapi_func = _eclass_eapi_functions.get(func_name) 476 if eapi_func is None or not eapi_func(self._eapi): 477 self._func_call = True 478 return ('%s.eclass is not inherited, ' 479 'but "%s" found at line: %s') % \ 480 (self._eclass, func_name, '%d') 481 elif not self._func_call: 482 self._func_call = self._func_re.search(line)
483
484 - def end(self):
485 if not self._disabled and self._comprehensive and self._inherit and not self._func_call: 486 self.repoman_check_name = 'inherit.unused' 487 yield 'no function called from %s.eclass; please drop' % self._eclass
488 489 _eclass_eapi_functions = { 490 "usex" : lambda eapi: eapi not in ("0", "1", "2", "3", "4", "4-python", "4-slot-abi") 491 } 492 493 # eclasses that export ${ECLASS}_src_(compile|configure|install) 494 _eclass_export_functions = ( 495 'ant-tasks', 'apache-2', 'apache-module', 'aspell-dict', 496 'autotools-utils', 'base', 'bsdmk', 'cannadic', 497 'clutter', 'cmake-utils', 'db', 'distutils', 'elisp', 498 'embassy', 'emboss', 'emul-linux-x86', 'enlightenment', 499 'font-ebdftopcf', 'font', 'fox', 'freebsd', 'freedict', 500 'games', 'games-ggz', 'games-mods', 'gdesklets', 501 'gems', 'gkrellm-plugin', 'gnatbuild', 'gnat', 'gnome2', 502 'gnome-python-common', 'gnustep-base', 'go-mono', 'gpe', 503 'gst-plugins-bad', 'gst-plugins-base', 'gst-plugins-good', 504 'gst-plugins-ugly', 'gtk-sharp-module', 'haskell-cabal', 505 'horde', 'java-ant-2', 'java-pkg-2', 'java-pkg-simple', 506 'java-virtuals-2', 'kde4-base', 'kde4-meta', 'kernel-2', 507 'latex-package', 'linux-mod', 'mozlinguas', 'myspell', 508 'myspell-r2', 'mysql', 'mysql-v2', 'mythtv-plugins', 509 'oasis', 'obs-service', 'office-ext', 'perl-app', 510 'perl-module', 'php-ext-base-r1', 'php-ext-pecl-r2', 511 'php-ext-source-r2', 'php-lib-r1', 'php-pear-lib-r1', 512 'php-pear-r1', 'python-distutils-ng', 'python', 513 'qt4-build', 'qt4-r2', 'rox-0install', 'rox', 'ruby', 514 'ruby-ng', 'scsh', 'selinux-policy-2', 'sgml-catalog', 515 'stardict', 'sword-module', 'tetex-3', 'tetex', 516 'texlive-module', 'toolchain-binutils', 'toolchain', 517 'twisted', 'vdr-plugin-2', 'vdr-plugin', 'vim', 518 'vim-plugin', 'vim-spell', 'virtuoso', 'vmware', 519 'vmware-mod', 'waf-utils', 'webapp', 'xemacs-elisp', 520 'xemacs-packages', 'xfconf', 'x-modular', 'xorg-2', 521 'zproduct' 522 ) 523 524 _eclass_info = { 525 'autotools': { 526 'funcs': ( 527 'eaclocal', 'eautoconf', 'eautoheader', 528 'eautomake', 'eautoreconf', '_elibtoolize', 529 'eautopoint' 530 ), 531 'comprehensive': True, 532 533 # Exempt eclasses: 534 # git - An EGIT_BOOTSTRAP variable may be used to call one of 535 # the autotools functions. 536 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of 537 # the autotools functions. 538 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils') 539 }, 540 541 'eutils': { 542 'funcs': ( 543 'estack_push', 'estack_pop', 'eshopts_push', 'eshopts_pop', 544 'eumask_push', 'eumask_pop', 'epatch', 'epatch_user', 545 'emktemp', 'edos2unix', 'in_iuse', 'use_if_iuse', 'usex' 546 ), 547 'comprehensive': False, 548 549 # These are "eclasses are the whole ebuild" type thing. 550 'exempt_eclasses': _eclass_export_functions, 551 }, 552 553 'flag-o-matic': { 554 'funcs': ( 555 'filter-(ld)?flags', 'strip-flags', 'strip-unsupported-flags', 556 'append-((ld|c(pp|xx)?))?flags', 'append-libs', 557 ), 558 'comprehensive': False 559 }, 560 561 'libtool': { 562 'funcs': ( 563 'elibtoolize', 564 ), 565 'comprehensive': True, 566 'exempt_eclasses': ('autotools',) 567 }, 568 569 'multilib': { 570 'funcs': ( 571 'get_libdir', 572 ), 573 574 # These are "eclasses are the whole ebuild" type thing. 575 'exempt_eclasses': _eclass_export_functions + ('autotools', 'libtool', 576 'multilib-minimal'), 577 578 'comprehensive': False 579 }, 580 581 'multiprocessing': { 582 'funcs': ( 583 'makeopts_jobs', 584 ), 585 'comprehensive': False 586 }, 587 588 'prefix': { 589 'funcs': ( 590 'eprefixify', 591 ), 592 'comprehensive': True 593 }, 594 595 'toolchain-funcs': { 596 'funcs': ( 597 'gen_usr_ldscript', 598 ), 599 'comprehensive': False 600 }, 601 602 'user': { 603 'funcs': ( 604 'enewuser', 'enewgroup', 605 'egetent', 'egethome', 'egetshell', 'esethome' 606 ), 607 'comprehensive': True 608 } 609 } 610
611 -class EMakeParallelDisabled(PhaseCheck):
612 """Check for emake -j1 calls which disable parallelization.""" 613 repoman_check_name = 'upstream.workaround' 614 re = re.compile(r'^\s*emake\s+.*-j\s*1\b') 615 error = errors.EMAKE_PARALLEL_DISABLED 616
617 - def phase_check(self, num, line):
618 if self.in_phase == 'src_compile' or self.in_phase == 'src_install': 619 if self.re.match(line): 620 return self.error
621
622 -class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
623 """Check for MAKEOPTS=-j1 that disables parallelization.""" 624 repoman_check_name = 'upstream.workaround' 625 re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b') 626 error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
627
628 -class NoAsNeeded(LineCheck):
629 """Check for calls to the no-as-needed function.""" 630 repoman_check_name = 'upstream.workaround' 631 re = re.compile(r'.*\$\(no-as-needed\)') 632 error = errors.NO_AS_NEEDED
633
634 -class PreserveOldLib(LineCheck):
635 """Check for calls to the deprecated preserve_old_lib function.""" 636 repoman_check_name = 'ebuild.minorsyn' 637 re = re.compile(r'.*preserve_old_lib') 638 error = errors.PRESERVE_OLD_LIB
639
640 -class SandboxAddpredict(LineCheck):
641 """Check for calls to the addpredict function.""" 642 repoman_check_name = 'upstream.workaround' 643 re = re.compile(r'(^|\s)addpredict\b') 644 error = errors.SANDBOX_ADDPREDICT
645
646 -class DeprecatedBindnowFlags(LineCheck):
647 """Check for calls to the deprecated bindnow-flags function.""" 648 repoman_check_name = 'ebuild.minorsyn' 649 re = re.compile(r'.*\$\(bindnow-flags\)') 650 error = errors.DEPRECATED_BINDNOW_FLAGS
651
652 -class WantAutoDefaultValue(LineCheck):
653 """Check setting WANT_AUTO* to latest (default value).""" 654 repoman_check_name = 'ebuild.minorsyn' 655 _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest') 656
657 - def check(self, num, line):
658 m = self._re.match(line) 659 if m is not None: 660 return 'WANT_AUTO' + m.group(1) + \ 661 ' redundantly set to default value "latest" on line: %d'
662
663 -class SrcCompileEconf(PhaseCheck):
664 repoman_check_name = 'ebuild.minorsyn' 665 configure_re = re.compile(r'\s(econf|./configure)') 666
667 - def check_eapi(self, eapi):
669
670 - def phase_check(self, num, line):
671 if self.in_phase == 'src_compile': 672 m = self.configure_re.match(line) 673 if m is not None: 674 return ("'%s'" % m.group(1)) + \ 675 " call should be moved to src_configure from line: %d"
676
677 -class SrcUnpackPatches(PhaseCheck):
678 repoman_check_name = 'ebuild.minorsyn' 679 src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s') 680
681 - def check_eapi(self, eapi):
683
684 - def phase_check(self, num, line):
685 if self.in_phase == 'src_unpack': 686 m = self.src_prepare_tools_re.search(line) 687 if m is not None: 688 return ("'%s'" % m.group(1)) + \ 689 " call should be moved to src_prepare from line: %d"
690
691 -class BuiltWithUse(LineCheck):
692 repoman_check_name = 'ebuild.minorsyn' 693 re = re.compile(r'(^|.*\b)built_with_use\b') 694 error = errors.BUILT_WITH_USE
695
696 -class DeprecatedUseq(LineCheck):
697 """Checks for use of the deprecated useq function""" 698 repoman_check_name = 'ebuild.minorsyn' 699 re = re.compile(r'(^|.*\b)useq\b') 700 error = errors.USEQ_ERROR
701
702 -class DeprecatedHasq(LineCheck):
703 """Checks for use of the deprecated hasq function""" 704 repoman_check_name = 'ebuild.minorsyn' 705 re = re.compile(r'(^|.*\b)hasq\b') 706 error = errors.HASQ_ERROR
707 708 # EAPI <2 checks
709 -class UndefinedSrcPrepareSrcConfigurePhases(LineCheck):
710 repoman_check_name = 'EAPI.incompatible' 711 src_configprepare_re = re.compile(r'\s*(src_configure|src_prepare)\s*\(\)') 712
713 - def check_eapi(self, eapi):
715
716 - def check(self, num, line):
717 m = self.src_configprepare_re.match(line) 718 if m is not None: 719 return ("'%s'" % m.group(1)) + \ 720 " phase is not defined in EAPI < 2 on line: %d"
721 722 723 # EAPI-3 checks
724 -class Eapi3DeprecatedFuncs(LineCheck):
725 repoman_check_name = 'EAPI.deprecated' 726 deprecated_commands_re = re.compile(r'^\s*(check_license)\b') 727
728 - def check_eapi(self, eapi):
729 return eapi not in ('0', '1', '2')
730
731 - def check(self, num, line):
732 m = self.deprecated_commands_re.match(line) 733 if m is not None: 734 return ("'%s'" % m.group(1)) + \ 735 " has been deprecated in EAPI=3 on line: %d"
736 737 # EAPI <4 checks
738 -class UndefinedPkgPretendPhase(LineCheck):
739 repoman_check_name = 'EAPI.incompatible' 740 pkg_pretend_re = re.compile(r'\s*(pkg_pretend)\s*\(\)') 741
742 - def check_eapi(self, eapi):
743 return not eapi_has_pkg_pretend(eapi)
744
745 - def check(self, num, line):
746 m = self.pkg_pretend_re.match(line) 747 if m is not None: 748 return ("'%s'" % m.group(1)) + \ 749 " phase is not defined in EAPI < 4 on line: %d"
750 751 # EAPI-4 checks
752 -class Eapi4IncompatibleFuncs(LineCheck):
753 repoman_check_name = 'EAPI.incompatible' 754 banned_commands_re = re.compile(r'^\s*(dosed|dohard)') 755
756 - def check_eapi(self, eapi):
757 return not eapi_has_dosed_dohard(eapi)
758
759 - def check(self, num, line):
760 m = self.banned_commands_re.match(line) 761 if m is not None: 762 return ("'%s'" % m.group(1)) + \ 763 " has been banned in EAPI=4 on line: %d"
764
765 -class Eapi4GoneVars(LineCheck):
766 repoman_check_name = 'EAPI.incompatible' 767 undefined_vars_re = re.compile(r'.*\$(\{(AA|KV|EMERGE_FROM)\}|(AA|KV|EMERGE_FROM))') 768
769 - def check_eapi(self, eapi):
770 # AA, KV, and EMERGE_FROM should not be referenced in EAPI 4 or later. 771 return not eapi_exports_AA(eapi)
772
773 - def check(self, num, line):
774 m = self.undefined_vars_re.match(line) 775 if m is not None: 776 return ("variable '$%s'" % m.group(1)) + \ 777 " is gone in EAPI=4 on line: %d"
778
779 -class PortageInternal(LineCheck):
780 repoman_check_name = 'portage.internal' 781 ignore_comment = True 782 # Match when the command is preceded only by leading whitespace or a shell 783 # operator such as (, {, |, ||, or &&. This prevents false positives in 784 # things like elog messages, as reported in bug #413285. 785 re = re.compile(r'^(\s*|.*[|&{(]+\s*)\b(ecompress|ecompressdir|env-update|prepall|prepalldocs|preplib)\b') 786
787 - def check(self, num, line):
788 """Run the check on line and return error if there is one""" 789 m = self.re.match(line) 790 if m is not None: 791 return ("'%s'" % m.group(2)) + " called on line: %d"
792
793 -class PortageInternalVariableAssignment(LineCheck):
794 repoman_check_name = 'portage.internal' 795 internal_assignment = re.compile(r'\s*(export\s+)?(EXTRA_ECONF|EXTRA_EMAKE)\+?=') 796
797 - def check(self, num, line):
798 match = self.internal_assignment.match(line) 799 e = None 800 if match is not None: 801 e = 'Assignment to variable %s' % match.group(2) 802 e += ' on line: %d' 803 return e
804 805 _base_check_classes = (InheritEclass, LineCheck, PhaseCheck) 806 _constant_checks = None 807
808 -def _init(experimental_inherit=False):
809 810 global _constant_checks, _eclass_info 811 812 if not experimental_inherit: 813 # Emulate the old eprefixify.defined and inherit.autotools checks. 814 _eclass_info = { 815 'autotools': { 816 'funcs': ( 817 'eaclocal', 'eautoconf', 'eautoheader', 818 'eautomake', 'eautoreconf', '_elibtoolize', 819 'eautopoint' 820 ), 821 'comprehensive': True, 822 'ignore_missing': True, 823 'exempt_eclasses': ('git', 'git-2', 'subversion', 'autotools-utils') 824 }, 825 826 'prefix': { 827 'funcs': ( 828 'eprefixify', 829 ), 830 'comprehensive': False 831 } 832 } 833 834 _constant_checks = tuple(chain((v() for k, v in globals().items() 835 if isinstance(v, type) and issubclass(v, LineCheck) and 836 v not in _base_check_classes), 837 (InheritEclass(k, **portage._native_kwargs(kwargs)) 838 for k, kwargs in _eclass_info.items())))
839 840 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$') 841 _ignore_comment_re = re.compile(r'^\s*#') 842
843 -def run_checks(contents, pkg):
844 unicode_escape_codec = codecs.lookup('unicode_escape') 845 unicode_escape = lambda x: unicode_escape_codec.decode(x)[0] 846 if _constant_checks is None: 847 _init() 848 checks = _constant_checks 849 here_doc_delim = None 850 multiline = None 851 852 for lc in checks: 853 lc.new(pkg) 854 for num, line in enumerate(contents): 855 856 # Check if we're inside a here-document. 857 if here_doc_delim is not None: 858 if here_doc_delim.match(line): 859 here_doc_delim = None 860 if here_doc_delim is None: 861 here_doc = _here_doc_re.match(line) 862 if here_doc is not None: 863 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1)) 864 if here_doc_delim is not None: 865 continue 866 867 # Unroll multiline escaped strings so that we can check things: 868 # inherit foo bar \ 869 # moo \ 870 # cow 871 # This will merge these lines like so: 872 # inherit foo bar moo cow 873 try: 874 # A normal line will end in the two bytes: <\> <\n>. So decoding 875 # that will result in python thinking the <\n> is being escaped 876 # and eat the single <\> which makes it hard for us to detect. 877 # Instead, strip the newline (which we know all lines have), and 878 # append a <0>. Then when python escapes it, if the line ended 879 # in a <\>, we'll end up with a <\0> marker to key off of. This 880 # shouldn't be a problem with any valid ebuild ... 881 line_escaped = unicode_escape(line.rstrip('\n') + '0') 882 except SystemExit: 883 raise 884 except: 885 # Who knows what kind of crazy crap an ebuild will have 886 # in it -- don't allow it to kill us. 887 line_escaped = line 888 if multiline: 889 # Chop off the \ and \n bytes from the previous line. 890 multiline = multiline[:-2] + line 891 if not line_escaped.endswith('\0'): 892 line = multiline 893 num = multinum 894 multiline = None 895 else: 896 continue 897 else: 898 if line_escaped.endswith('\0'): 899 multinum = num 900 multiline = line 901 continue 902 903 if not line.endswith("#nowarn\n"): 904 # Finally we have a full line to parse. 905 is_comment = _ignore_comment_re.match(line) is not None 906 for lc in checks: 907 if is_comment and lc.ignore_comment: 908 continue 909 if lc.check_eapi(pkg.eapi): 910 ignore = lc.ignore_line 911 if not ignore or not ignore.match(line): 912 e = lc.check(num, line) 913 if e: 914 yield lc.repoman_check_name, e % (num + 1) 915 916 for lc in checks: 917 i = lc.end() 918 if i is not None: 919 for e in i: 920 yield lc.repoman_check_name, e
921