1
2
3
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
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
31
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
43
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):
64
66 """ override this function for your checks """
67 pass
68
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
83
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
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
107
108
124
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
142 if self.line_is_blank:
143 yield 'Useless blank line on last line'
144
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
160 var_names += ["ED", "EPREFIX", "EROOT"]
161
162
163 var_names += ["Ddir", "GAMES_PREFIX_OPT", "GAMES_DATADIR",
164 "GAMES_DATADIR_BASE", "GAMES_SYSCONFDIR", "GAMES_STATEDIR",
165 "GAMES_LOGDIR", "GAMES_BINDIR"]
166
167
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):
223
224
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):
237
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
245
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):
255
256
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):
267
268
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
276 self.check_next_line = False
277
278 - def check(self, num, line):
285
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
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
322
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):
333
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
341
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
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
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
375 if self._depend and not self._rdepend:
376 yield 'RDEPEND is not explicitly assigned'
377
379 """Check if ebuild directly or indirectly inherits a deprecated eclass."""
380
381 repoman_check_name = 'inherit.deprecated'
382
383
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):
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
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
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
475
476
477
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
483
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
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
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
561
562
563
564
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
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
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
640
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
671
677
683
689
695
701
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
714 repoman_check_name = 'ebuild.minorsyn'
715 configure_re = re.compile(r'\s(econf|./configure)')
716
719
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
740
745
751
757
758
771
772
785
799
801 repoman_check_name = 'portage.internal'
802 ignore_comment = True
803
804
805
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
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
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
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
858
859
860
861
862
863 try:
864
865
866
867
868
869
870
871 line_escaped = unicode_escape(line.rstrip('\n') + '0')
872 except SystemExit:
873 raise
874 except:
875
876
877 line_escaped = line
878 if multiline:
879
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
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