2 # Copyright 2007 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 """This module contains functions used in Repoman to ascertain the quality
7 and correctness of an ebuild."""
11 import repoman.errors as errors
13 class LineCheck(object):
14 """Run a check on a line of an ebuild."""
15 """A regular expression to determine whether to ignore the line"""
21 def check_eapi(self, eapi):
22 """ returns if the check should be run in the given EAPI (default is True) """
25 def check(self, num, line):
26 """Run the check on line and return error if there is one"""
27 if self.re.match(line):
33 class EbuildHeader(LineCheck):
34 """Ensure ebuilds have proper headers
35 Copyright header errors
40 modification_year - Year the ebuild was last modified
43 repoman_check_name = 'ebuild.badheader'
45 gentoo_copyright = r'^# Copyright ((1999|200\d)-)?%s Gentoo Foundation$'
46 # Why a regex here, use a string match
47 # gentoo_license = re.compile(r'^# Distributed under the terms of the GNU General Public License v2$')
48 gentoo_license = r'# Distributed under the terms of the GNU General Public License v2'
49 cvs_header = re.compile(r'^#\s*\$Header.*\$$')
52 self.modification_year = str(time.gmtime(pkg.mtime)[0])
53 self.gentoo_copyright_re = re.compile(
54 self.gentoo_copyright % self.modification_year)
56 def check(self, num, line):
60 if not self.gentoo_copyright_re.match(line):
61 return errors.COPYRIGHT_ERROR
62 elif num == 1 and line.strip() != self.gentoo_license:
63 return errors.LICENSE_ERROR
65 if not self.cvs_header.match(line):
66 return errors.CVS_HEADER_ERROR
69 class EbuildWhitespace(LineCheck):
70 """Ensure ebuilds have proper whitespacing"""
72 repoman_check_name = 'ebuild.minorsyn'
74 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
75 leading_spaces = re.compile(r'^[\S\t]')
76 trailing_whitespace = re.compile(r'.*([\S]$)')
78 def check(self, num, line):
79 if self.leading_spaces.match(line) is None:
80 return errors.LEADING_SPACES_ERROR
81 if self.trailing_whitespace.match(line) is None:
82 return errors.TRAILING_WHITESPACE_ERROR
84 class EbuildBlankLine(LineCheck):
85 repoman_check_name = 'ebuild.minorsyn'
86 blank_line = re.compile(r'^$')
89 self.line_is_blank = False
91 def check(self, num, line):
92 if self.line_is_blank and self.blank_line.match(line):
93 return 'Useless blank line on line: %d'
94 if self.blank_line.match(line):
95 self.line_is_blank = True
97 self.line_is_blank = False
100 if self.line_is_blank:
101 yield 'Useless blank line on last line'
103 class EbuildQuote(LineCheck):
104 """Ensure ebuilds have valid quoting around things like D,FILESDIR, etc..."""
106 repoman_check_name = 'ebuild.minorsyn'
107 _message_commands = ["die", "echo", "eerror",
108 "einfo", "elog", "eqawarn", "ewarn"]
109 _message_re = re.compile(r'\s(' + "|".join(_message_commands) + \
111 _ignored_commands = ["local", "export"] + _message_commands
112 ignore_line = re.compile(r'(^$)|(^\s*#.*)|(^\s*\w+=.*)' + \
113 r'|(^\s*(' + "|".join(_ignored_commands) + r')\s+)')
114 var_names = ["D", "DISTDIR", "FILESDIR", "S", "T", "ROOT", "WORKDIR"]
116 # variables for games.eclass
117 var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
118 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
119 "GAMES_LOGDIR", "GAMES_BINDIR"]
121 var_names = "(%s)" % "|".join(var_names)
122 var_reference = re.compile(r'\$(\{'+var_names+'\}|' + \
124 missing_quotes = re.compile(r'(\s|^)[^"\'\s]*\$\{?' + var_names + \
125 r'\}?[^"\'\s]*(\s|$)')
126 cond_begin = re.compile(r'(^|\s+)\[\[($|\\$|\s+)')
127 cond_end = re.compile(r'(^|\s+)\]\]($|\\$|\s+)')
129 def check(self, num, line):
130 if self.var_reference.search(line) is None:
132 # There can be multiple matches / violations on a single line. We
133 # have to make sure none of the matches are violators. Once we've
134 # found one violator, any remaining matches on the same line can
137 while pos <= len(line) - 1:
138 missing_quotes = self.missing_quotes.search(line, pos)
139 if not missing_quotes:
141 # If the last character of the previous match is a whitespace
142 # character, that character may be needed for the next
143 # missing_quotes match, so search overlaps by 1 character.
144 group = missing_quotes.group()
145 pos = missing_quotes.end() - 1
147 # Filter out some false positives that can
148 # get through the missing_quotes regex.
149 if self.var_reference.search(group) is None:
152 # Filter matches that appear to be an
153 # argument to a message command.
154 # For example: false || ewarn "foo $WORKDIR/bar baz"
155 message_match = self._message_re.search(line)
156 if message_match is not None and \
157 message_match.start() < pos and \
158 message_match.end() > pos:
161 # This is an attempt to avoid false positives without getting
162 # too complex, while possibly allowing some (hopefully
163 # unlikely) violations to slip through. We just assume
164 # everything is correct if the there is a ' [[ ' or a ' ]] '
165 # anywhere in the whole line (possibly continued over one
167 if self.cond_begin.search(line) is not None:
169 if self.cond_end.search(line) is not None:
172 # Any remaining matches on the same line can be ignored.
173 return errors.MISSING_QUOTES_ERROR
176 class EbuildAssignment(LineCheck):
177 """Ensure ebuilds don't assign to readonly variables."""
179 repoman_check_name = 'variable.readonly'
181 readonly_assignment = re.compile(r'^\s*(export\s+)?(A|CATEGORY|P|PV|PN|PR|PVR|PF|D|WORKDIR|FILESDIR|FEATURES|USE)=')
182 line_continuation = re.compile(r'([^#]*\S)(\s+|\t)\\$')
183 ignore_line = re.compile(r'(^$)|(^(\t)*#)')
186 self.previous_line = None
188 def check(self, num, line):
189 match = self.readonly_assignment.match(line)
191 if match and (not self.previous_line or not self.line_continuation.match(self.previous_line)):
192 e = errors.READONLY_ASSIGNMENT_ERROR
193 self.previous_line = line
197 class EbuildNestedDie(LineCheck):
198 """Check ebuild for nested die statements (die statements in subshells"""
200 repoman_check_name = 'ebuild.nesteddie'
201 nesteddie_re = re.compile(r'^[^#]*\s\(\s[^)]*\bdie\b')
203 def check(self, num, line):
204 if self.nesteddie_re.match(line):
205 return errors.NESTED_DIE_ERROR
208 class EbuildUselessDodoc(LineCheck):
209 """Check ebuild for useless files in dodoc arguments."""
210 repoman_check_name = 'ebuild.minorsyn'
211 uselessdodoc_re = re.compile(
212 r'^\s*dodoc(\s+|\s+.*\s+)(ABOUT-NLS|COPYING|LICENSE)($|\s)')
214 def check(self, num, line):
215 match = self.uselessdodoc_re.match(line)
217 return "Useless dodoc '%s'" % (match.group(2), ) + " on line: %d"
220 class EbuildUselessCdS(LineCheck):
221 """Check for redundant cd ${S} statements"""
222 repoman_check_name = 'ebuild.minorsyn'
223 method_re = re.compile(r'^\s*src_(prepare|configure|compile|install|test)\s*\(\)')
224 cds_re = re.compile(r'^\s*cd\s+("\$(\{S\}|S)"|\$(\{S\}|S))\s')
227 self.check_next_line = False
229 def check(self, num, line):
230 if self.check_next_line:
231 self.check_next_line = False
232 if self.cds_re.match(line):
233 return errors.REDUNDANT_CD_S_ERROR
234 elif self.method_re.match(line):
235 self.check_next_line = True
237 class EapiDefinition(LineCheck):
238 """ Check that EAPI is defined before inherits"""
239 repoman_check_name = 'EAPI.definition'
241 eapi_re = re.compile(r'^EAPI=')
242 inherit_re = re.compile(r'^\s*inherit\s')
245 self.inherit_line = None
247 def check(self, num, line):
248 if self.eapi_re.match(line) is not None:
249 if self.inherit_line is not None:
250 return errors.EAPI_DEFINED_AFTER_INHERIT
251 elif self.inherit_re.match(line) is not None:
252 self.inherit_line = line
254 class EbuildPatches(LineCheck):
255 """Ensure ebuilds use bash arrays for PATCHES to ensure white space safety"""
256 repoman_check_name = 'ebuild.patches'
257 re = re.compile(r'^\s*PATCHES=[^\(]')
258 error = errors.PATCHES_ERROR
260 class EbuildQuotedA(LineCheck):
261 """Ensure ebuilds have no quoting around ${A}"""
263 repoman_check_name = 'ebuild.minorsyn'
264 a_quoted = re.compile(r'.*\"\$(\{A\}|A)\"')
266 def check(self, num, line):
267 match = self.a_quoted.match(line)
269 return "Quoted \"${A}\" on line: %d"
271 class ImplicitRuntimeDeps(LineCheck):
273 Detect the case where DEPEND is set and RDEPEND is unset in the ebuild,
274 since this triggers implicit RDEPEND=$DEPEND assignment.
277 _assignment_re = re.compile(r'^\s*(R?DEPEND)=')
280 # RDEPEND=DEPEND is no longer available in EAPI=3
281 if pkg.metadata['EAPI'] in ('0', '1', '2'):
282 self.repoman_check_name = 'RDEPEND.implicit'
284 self.repoman_check_name = 'EAPI.incompatible'
285 self._rdepend = False
288 def check(self, num, line):
289 if not self._rdepend:
290 m = self._assignment_re.match(line)
293 elif m.group(1) == "RDEPEND":
295 elif m.group(1) == "DEPEND":
299 if self._depend and not self._rdepend:
300 yield 'RDEPEND is not explicitly assigned'
302 class InheritAutotools(LineCheck):
304 Make sure appropriate functions are called in
305 ebuilds that inherit autotools.eclass.
308 repoman_check_name = 'inherit.autotools'
309 ignore_line = re.compile(r'(^|\s*)#')
310 _inherit_autotools_re = re.compile(r'^\s*inherit\s(.*\s)?autotools(\s|$)')
312 "eaclocal", "eautoconf", "eautoheader",
313 "eautomake", "eautoreconf", "_elibtoolize")
314 _autotools_func_re = re.compile(r'\b(' + \
315 "|".join(_autotools_funcs) + r')\b')
317 # git - An EGIT_BOOTSTRAP variable may be used to call one of
318 # the autotools functions.
319 # subversion - An ESVN_BOOTSTRAP variable may be used to call one of
320 # the autotools functions.
321 _exempt_eclasses = frozenset(["git", "subversion"])
324 self._inherit_autotools = None
325 self._autotools_func_call = None
326 self._disabled = self._exempt_eclasses.intersection(pkg.inherited)
328 def check(self, num, line):
331 if self._inherit_autotools is None:
332 self._inherit_autotools = self._inherit_autotools_re.match(line)
333 if self._inherit_autotools is not None and \
334 self._autotools_func_call is None:
335 self._autotools_func_call = self._autotools_func_re.search(line)
338 if self._inherit_autotools and self._autotools_func_call is None:
339 yield 'no eauto* function called'
341 class IUseUndefined(LineCheck):
343 Make sure the ebuild defines IUSE (style guideline
344 says to define IUSE even when empty).
347 repoman_check_name = 'IUSE.undefined'
348 _iuse_def_re = re.compile(r'^IUSE=.*')
351 self._iuse_def = None
353 def check(self, num, line):
354 if self._iuse_def is None:
355 self._iuse_def = self._iuse_def_re.match(line)
358 if self._iuse_def is None:
359 yield 'IUSE is not defined'
361 class EMakeParallelDisabled(LineCheck):
362 """Check for emake -j1 calls which disable parallelization."""
363 repoman_check_name = 'upstream.workaround'
364 re = re.compile(r'^\s*emake\s+.*-j\s*1\b')
365 error = errors.EMAKE_PARALLEL_DISABLED
367 class EMakeParallelDisabledViaMAKEOPTS(LineCheck):
368 """Check for MAKEOPTS=-j1 that disables parallelization."""
369 repoman_check_name = 'upstream.workaround'
370 re = re.compile(r'^\s*MAKEOPTS=(\'|")?.*-j\s*1\b')
371 error = errors.EMAKE_PARALLEL_DISABLED_VIA_MAKEOPTS
373 class DeprecatedBindnowFlags(LineCheck):
374 """Check for calls to the deprecated bindnow-flags function."""
375 repoman_check_name = 'ebuild.minorsyn'
376 re = re.compile(r'.*\$\(bindnow-flags\)')
377 error = errors.DEPRECATED_BINDNOW_FLAGS
379 class WantAutoDefaultValue(LineCheck):
380 """Check setting WANT_AUTO* to latest (default value)."""
381 repoman_check_name = 'ebuild.minorsyn'
382 _re = re.compile(r'^WANT_AUTO(CONF|MAKE)=(\'|")?latest')
384 def check(self, num, line):
385 m = self._re.match(line)
387 return 'WANT_AUTO' + m.group(1) + \
388 ' redundantly set to default value "latest" on line: %d'
390 class PhaseCheck(LineCheck):
391 """ basic class for function detection """
393 ignore_line = re.compile(r'(^\s*#)')
394 func_end_re = re.compile(r'^\}$')
398 self.phases = ('pkg_setup', 'pkg_preinst', 'pkg_postinst', 'pkg_prerm', 'pkg_postrm', 'pkg_pretend',
399 'src_unpack', 'src_prepare', 'src_compile', 'src_test', 'src_install')
401 for phase in self.phases:
402 phase_re += phase + '|'
403 phase_re = phase_re[:-1] + ')'
404 self.phases_re = re.compile(phase_re)
406 def check(self, num, line):
407 m = self.phases_re.match(line)
409 self.in_phase = m.group(1)
410 if self.in_phase != '' and \
411 self.func_end_re.match(line) is not None:
414 return self.phase_check(num, line)
416 def phase_check(self, num, line):
417 """ override this function for your checks """
420 class SrcCompileEconf(PhaseCheck):
421 repoman_check_name = 'ebuild.minorsyn'
422 configure_re = re.compile(r'\s(econf|./configure)')
424 def check_eapi(self, eapi):
425 return eapi not in ('0', '1')
427 def phase_check(self, num, line):
428 if self.in_phase == 'src_compile':
429 m = self.configure_re.match(line)
431 return ("'%s'" % m.group(1)) + \
432 " call should be moved to src_configure from line: %d"
434 class SrcUnpackPatches(PhaseCheck):
435 repoman_check_name = 'ebuild.minorsyn'
436 src_prepare_tools_re = re.compile(r'\s(e?patch|sed)\s')
439 if pkg.metadata['EAPI'] not in ('0', '1'):
440 self.eapi = pkg.metadata['EAPI']
443 self.in_src_unpack = None
445 def check_eapi(self, eapi):
446 return eapi not in ('0', '1')
448 def phase_check(self, num, line):
449 if self.in_phase == 'src_unpack':
450 m = self.src_prepare_tools_re.search(line)
452 return ("'%s'" % m.group(1)) + \
453 " call should be moved to src_prepare from line: %d"
456 class Eapi3IncompatibleFuncs(LineCheck):
457 repoman_check_name = 'EAPI.incompatible'
458 ignore_line = re.compile(r'(^\s*#)')
459 banned_commands_re = re.compile(r'^\s*(dosed|dohard)')
462 self.eapi = pkg.metadata['EAPI']
464 def check_eapi(self, eapi):
465 return self.eapi not in ('0', '1', '2')
467 def check(self, num, line):
468 m = self.banned_commands_re.match(line)
470 return ("'%s'" % m.group(1)) + \
471 " has been banned in EAPI=3 on line: %d"
473 class Eapi3GoneVars(LineCheck):
474 repoman_check_name = 'EAPI.incompatible'
475 ignore_line = re.compile(r'(^\s*#)')
476 undefined_vars_re = re.compile(r'.*\$(\{(AA|KV)\}|(AA|KV))')
479 self.eapi = pkg.metadata['EAPI']
481 def check_eapi(self, eapi):
482 return self.eapi not in ('0', '1', '2')
484 def check(self, num, line):
485 m = self.undefined_vars_re.match(line)
487 return ("variable '$%s'" % m.group(1)) + \
488 " is gone in EAPI=3 on line: %d"
491 _constant_checks = tuple((c() for c in (
492 EbuildHeader, EbuildWhitespace, EbuildBlankLine, EbuildQuote,
493 EbuildAssignment, EbuildUselessDodoc,
494 EbuildUselessCdS, EbuildNestedDie,
495 EbuildPatches, EbuildQuotedA, EapiDefinition,
496 IUseUndefined, ImplicitRuntimeDeps, InheritAutotools,
497 EMakeParallelDisabled, EMakeParallelDisabledViaMAKEOPTS,
498 DeprecatedBindnowFlags, SrcUnpackPatches, WantAutoDefaultValue,
499 SrcCompileEconf, Eapi3IncompatibleFuncs, Eapi3GoneVars)))
501 _here_doc_re = re.compile(r'.*\s<<[-]?(\w+)$')
503 def run_checks(contents, pkg):
504 checks = _constant_checks
505 here_doc_delim = None
509 for num, line in enumerate(contents):
511 # Check if we're inside a here-document.
512 if here_doc_delim is not None:
513 if here_doc_delim.match(line):
514 here_doc_delim = None
515 if here_doc_delim is None:
516 here_doc = _here_doc_re.match(line)
517 if here_doc is not None:
518 here_doc_delim = re.compile(r'^\s*%s$' % here_doc.group(1))
520 if here_doc_delim is None:
521 # We're not in a here-document.
523 if lc.check_eapi(pkg.metadata['EAPI']):
524 ignore = lc.ignore_line
525 if not ignore or not ignore.match(line):
526 e = lc.check(num, line)
528 yield lc.repoman_check_name, e % (num + 1)
534 yield lc.repoman_check_name, e