2 # Copyright 1999-2011 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 # Next to do: dep syntax checking in mask files
6 # Then, check to make sure deps are satisfiable (to avoid "can't find match for" problems)
7 # that last one is tricky because multiple profiles need to be checked.
9 from __future__ import print_function
27 from urllib.request import urlopen as urllib_request_urlopen
29 from urllib import urlopen as urllib_request_urlopen
31 from itertools import chain
32 from stat import S_ISDIR
37 from os import path as osp
38 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
40 portage._disable_legacy_globals()
41 portage.dep._internal_warnings = True
44 import xml.etree.ElementTree
45 from xml.parsers.expat import ExpatError
47 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
48 from portage.output import EOutput
54 from portage import os
55 from portage import subprocess_getstatusoutput
56 from portage import _encodings
57 from portage import _unicode_encode
58 from portage import StringIO
59 from repoman.checks import run_checks
60 from repoman import utilities
61 from repoman.herdbase import make_herd_base
62 from _emerge.Package import Package
63 from _emerge.RootConfig import RootConfig
64 from _emerge.userquery import userquery
65 import portage.checksum
67 from portage import cvstree, normalize_path
68 from portage import util
69 from portage.exception import (FileNotFound, MissingParameter,
70 ParseError, PermissionDenied)
71 from portage.manifest import Manifest
72 from portage.process import find_binary, spawn
73 from portage.output import bold, create_color_func, \
75 from portage.output import ConsoleStyleFile, StyleWriter
76 from portage.util import cmp_sort_key, writemsg_level
77 from portage.package.ebuild.digestgen import digestgen
78 from portage.eapi import eapi_has_slot_deps, \
79 eapi_has_use_deps, eapi_has_strong_blocks, eapi_has_iuse_defaults, \
80 eapi_has_required_use, eapi_has_use_dep_defaults
82 if sys.hexversion >= 0x3000000:
85 util.initialize_logger()
87 # 14 is the length of DESCRIPTION=""
89 allowed_filename_chars="a-zA-Z0-9._-+:"
90 disallowed_filename_chars_re = re.compile(r'[^a-zA-Z0-9._\-+:]')
91 pv_toolong_re = re.compile(r'[0-9]{19,}')
92 bad = create_color_func("BAD")
94 # A sane umask is needed for files that portage creates.
96 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
97 # behave incrementally.
98 repoman_incrementals = tuple(x for x in \
99 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
100 repoman_settings = portage.config(local_config=False)
101 repoman_settings.lock()
103 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
104 repoman_settings.get('TERM') == 'dumb' or \
105 not sys.stdout.isatty():
109 print("repoman: " + txt)
115 def exithandler(signum=None, frame=None):
116 logging.fatal("Interrupted; exiting...")
120 sys.exit(128 + signum)
122 signal.signal(signal.SIGINT,exithandler)
124 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
125 """Repoman needs it's own HelpFormatter for now, because the default ones
126 murder the help text."""
128 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
129 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
131 def format_description(self, description):
134 class RepomanOptionParser(optparse.OptionParser):
135 """Add the on_tail function, ruby has it, optionParser should too
138 def __init__(self, *args, **kwargs):
139 optparse.OptionParser.__init__(self, *args, **kwargs)
142 def on_tail(self, description):
143 self.tail += description
145 def format_help(self, formatter=None):
146 result = optparse.OptionParser.format_help(self, formatter)
151 def ParseArgs(argv, qahelp):
152 """This function uses a customized optionParser to parse command line arguments for repoman
154 argv - a sequence of command line arguments
155 qahelp - a dict of qa warning to help message
157 (opts, args), just like a call to parser.parse_args()
160 if argv and sys.hexversion < 0x3000000 and not isinstance(argv[0], unicode):
161 argv = [portage._unicode_decode(x) for x in argv]
164 'commit' : 'Run a scan then commit changes',
165 'ci' : 'Run a scan then commit changes',
166 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
167 'full' : 'Scan directory tree and print all issues (not a summary)',
168 'help' : 'Show this screen',
169 'manifest' : 'Generate a Manifest (fetches files if necessary)',
170 'manifest-check' : 'Check Manifests for missing or incorrect digests',
171 'scan' : 'Scan directory tree for QA issues'
174 mode_keys = list(modes)
177 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
178 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
179 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
180 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
181 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
183 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
184 help='Request a confirmation before commiting')
186 parser.add_option('-m', '--commitmsg', dest='commitmsg',
187 help='specify a commit message on the command line')
189 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
190 help='specify a path to a file that contains a commit message')
192 parser.add_option('-p', '--pretend', dest='pretend', default=False,
193 action='store_true', help='don\'t commit or fix anything; just show what would be done')
195 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
196 help='do not print unnecessary messages')
198 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
199 help='Commit with QA violations')
201 parser.add_option('--vcs', dest='vcs',
202 help='Force using specific VCS instead of autodetection')
204 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
205 help='be very verbose in output', default=0)
207 parser.add_option('-V', '--version', dest='version', action='store_true',
208 help='show version info')
210 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
211 default=False, help='forces the metadata.xml parse check to be carried out')
213 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
214 default=False, help='ignore arch-specific failures (where arch != host)')
216 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
217 default=False, help='ignore masked packages (not allowed with commit mode)')
219 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
220 default=False, help='include dev profiles in dependency checks')
222 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
223 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
225 parser.add_option('--without-mask', dest='without_mask', action='store_true',
226 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
228 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
229 help='specify which mode repoman will run in (default=full)')
231 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
234 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
236 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
238 sorted_qa = list(qahelp)
241 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
243 opts, args = parser.parse_args(argv[1:])
245 if opts.mode == 'help':
246 parser.print_help(short=False)
254 parser.error("invalid mode: %s" % arg)
259 if opts.mode == 'ci':
260 opts.mode = 'commit' # backwards compat shortcut
262 if opts.mode == 'commit' and not (opts.force or opts.pretend):
263 if opts.ignore_masked:
264 parser.error('Commit mode and --ignore-masked are not compatible')
265 if opts.without_mask:
266 parser.error('Commit mode and --without-mask are not compatible')
268 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
269 for val in range(opts.verbosity):
270 logger = logging.getLogger()
271 logger.setLevel(logger.getEffectiveLevel() - 10)
273 for val in range(opts.quiet):
274 logger = logging.getLogger()
275 logger.setLevel(logger.getEffectiveLevel() + 10)
280 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
281 "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
282 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
283 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
284 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
285 "changelog.missing":"Missing ChangeLog files",
286 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
287 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
288 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
289 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
290 "file.size":"Files in the files directory must be under 20 KiB",
291 "file.size.fatal":"Files in the files directory must be under 60 KiB",
292 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
293 "file.UTF8":"File is not UTF8 compliant",
294 "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf",
295 "inherit.deprecated":"Ebuild inherits a deprecated eclass",
296 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
297 "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
298 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
299 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
300 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
301 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
302 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
303 "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
304 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
305 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
306 "EAPI.definition":"EAPI is defined after an inherit call (must be defined before)",
307 "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
308 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
309 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
310 "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
311 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
312 "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
313 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
314 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
315 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
316 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
317 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
318 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
319 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
320 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
321 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
322 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
323 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
324 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
325 "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
326 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
327 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
328 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
329 "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
330 "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
331 "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
332 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
333 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
334 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
335 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
336 "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
337 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
338 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
339 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
340 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
341 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
342 "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
343 "variable.readonly":"Assigning a readonly variable",
344 "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
345 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
346 "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
347 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
348 "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
349 "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)",
350 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
351 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
352 "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
353 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
354 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
355 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
356 "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
357 "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
358 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
359 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
360 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
361 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
362 "ebuild.badheader":"This ebuild has a malformed header",
363 "eprefixify.defined":"The ebuild uses eprefixify, but does not inherit the prefix eclass",
364 "manifest.bad":"Manifest has missing or incorrect digests",
365 "metadata.missing":"Missing metadata.xml files",
366 "metadata.bad":"Bad metadata.xml files",
367 "metadata.warning":"Warnings in metadata.xml files",
368 "portage.internal":"The ebuild uses an internal Portage function",
369 "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
370 "usage.obsolete":"The ebuild makes use of an obsolete construct",
371 "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
374 qacats = list(qahelp)
379 "changelog.notadded",
387 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
388 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
389 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
390 "DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde",
391 "DESCRIPTION.toolong",
409 "inherit.deprecated",
410 "java.eclassesnotused",
411 "wxwidgets.eclassnotused",
415 "upstream.workaround",
421 non_ascii_re = re.compile(r'[^\x00-\x7f]')
423 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
424 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
425 allvars.update(Package.metadata_keys)
426 allvars = sorted(allvars)
428 for x in missingvars:
431 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
435 valid_restrict = frozenset(["binchecks", "bindist",
436 "fetch", "installsources", "mirror",
437 "primaryuri", "strip", "test", "userpriv"])
439 live_eclasses = frozenset([
450 suspect_rdepend = frozenset([
451 "app-arch/cabextract",
452 "app-arch/rpm2targz",
457 "dev-perl/extutils-pkgconfig",
463 "dev-util/gtk-doc-am",
466 "dev-util/pkgconfig",
470 "media-gfx/ebdftopcf",
472 "sys-devel/autoconf",
473 "sys-devel/automake",
480 "virtual/linux-sources",
485 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
486 # force refetch if the local copy creation time is older than this
487 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
490 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
492 options, arguments = ParseArgs(sys.argv, qahelp)
495 print("Portage", portage.VERSION)
498 # Set this to False when an extraordinary issue (generally
499 # something other than a QA issue) makes it impossible to
500 # commit (like if Manifest generation fails).
503 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
507 myreporoot = os.path.basename(portdir_overlay)
508 myreporoot += mydir[len(portdir_overlay):]
511 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
516 vcses = utilities.FindVCS()
518 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
519 print(red('*** Please either clean up your workdir or specify --vcs option.'))
526 # Note: We don't use ChangeLogs in distributed SCMs.
527 # It will be generated on server side from scm log,
528 # before package moves to the rsync server.
529 # This is needed because we try to avoid merge collisions.
530 check_changelog = vcs in ('cvs', 'svn')
532 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
533 vcs_preserves_mtime = vcs not in ('git',)
535 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
536 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
537 if vcs_global_opts is None:
538 if vcs in ('cvs', 'svn'):
539 vcs_global_opts = "-q"
542 vcs_global_opts = vcs_global_opts.split()
544 if vcs == "cvs" and \
545 "commit" == options.mode and \
546 "RMD160" not in portage.checksum.hashorigin_map:
547 from portage.util import grablines
548 repo_lines = grablines("./CVS/Repository")
550 "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]:
551 msg = "Please install " \
552 "pycrypto or enable python's ssl USE flag in order " \
553 "to enable RMD160 hash support. See bug #198398 for " \
556 from textwrap import wrap
557 for line in wrap(msg, 70):
562 if options.mode == 'commit' and not options.pretend and not vcs:
563 logging.info("Not in a version controlled repository; enabling pretend mode.")
564 options.pretend = True
566 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
567 repoman_settings = portage.config(local_config=False)
568 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
569 (repoman_settings.get('PORTDIR_OVERLAY', ''), portdir_overlay)
570 # We have to call the config constructor again so
571 # that config.repositories is initialized correctly.
572 repoman_settings = portage.config(local_config=False, env=dict(os.environ,
573 PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
577 root : {'porttree' : portage.portagetree(root, settings=repoman_settings)}
579 portdb = trees[root]['porttree'].dbapi
581 # Constrain dependency resolution to the master(s)
582 # that are specified in layout.conf.
583 portdir_overlay = os.path.realpath(portdir_overlay)
584 repo_info = portdb._repo_info[portdir_overlay]
585 portdb.porttrees = list(repo_info.eclass_db.porttrees)
586 portdir = portdb.porttrees[0]
588 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
589 # profile-specific config constructor calls.
590 env = os.environ.copy()
591 env['PORTDIR'] = portdir
592 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
594 logging.info('Setting paths:')
595 logging.info('PORTDIR = "' + portdir + '"')
596 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
598 # It's confusing if these warnings are displayed without the user
599 # being told which profile they come from, so disable them.
600 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
603 for path in set([portdir, portdir_overlay]):
604 categories.extend(portage.util.grabfile(
605 os.path.join(path, 'profiles', 'categories')))
606 repoman_settings.categories = tuple(sorted(
607 portage.util.stack_lists([categories], incremental=1)))
610 portdb.settings = repoman_settings
611 root_config = RootConfig(repoman_settings, trees[root], None)
612 # We really only need to cache the metadata that's necessary for visibility
613 # filtering. Anything else can be discarded to reduce memory consumption.
614 portdb._aux_cache_keys.clear()
615 portdb._aux_cache_keys.update(["EAPI", "KEYWORDS", "SLOT"])
617 reposplit = myreporoot.split(os.path.sep)
618 repolevel = len(reposplit)
620 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
621 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
622 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
623 if options.mode == 'commit' and repolevel not in [1,2,3]:
624 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
625 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
626 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
628 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
630 startdir = normalize_path(mydir)
632 for x in range(0, repolevel - 1):
633 repodir = os.path.dirname(repodir)
634 repodir = os.path.realpath(repodir)
637 err(mycat+" is not an official category. Skipping QA checks in this directory.\nPlease ensure that you add "+catdir+" to "+repodir+"/profiles/categories\nif it is a new category.")
639 class ProfileDesc(object):
640 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
641 def __init__(self, arch, status, sub_path, tree_path):
645 sub_path = normalize_path(sub_path.lstrip(os.sep))
646 self.sub_path = sub_path
647 self.tree_path = tree_path
649 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
651 self.abs_path = tree_path
656 return 'empty profile'
659 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
661 # get lists of valid keywords, licenses, and use
665 global_pmasklines = []
667 for path in portdb.porttrees:
669 liclist.update(os.listdir(os.path.join(path, "licenses")))
672 kwlist.update(portage.grabfile(os.path.join(path,
673 "profiles", "arch.list")))
675 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
681 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
683 expand_list = os.listdir(expand_desc_dir)
687 for fn in expand_list:
688 if not fn[-5:] == '.desc':
690 use_prefix = fn[:-5].lower() + '_'
691 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
694 uselist.add(use_prefix + x[0])
696 global_pmasklines.append(portage.util.grabfile_package(
697 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
699 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
701 desc_file = codecs.open(_unicode_encode(desc_path,
702 encoding=_encodings['fs'], errors='strict'),
703 mode='r', encoding=_encodings['repo.content'], errors='replace')
704 except EnvironmentError:
707 for i, x in enumerate(desc_file):
714 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
715 desc_path + " line %d" % (i+1, ))
716 elif arch[0] not in kwlist:
717 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
718 desc_path + " line %d" % (i+1, ))
719 elif arch[2] not in valid_profile_types:
720 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
721 desc_path + " line %d" % (i+1, ))
722 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
723 if not os.path.isdir(profile_desc.abs_path):
725 "Invalid %s profile (%s) for arch %s in %s line %d",
726 arch[2], arch[1], arch[0], desc_path, i+1)
729 os.path.join(profile_desc.abs_path, 'deprecated')):
731 profile_list.append(profile_desc)
734 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
735 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
737 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
738 global_pmaskdict = {}
739 for x in global_pmasklines:
740 global_pmaskdict.setdefault(x.cp, []).append(x)
741 del global_pmasklines
743 def has_global_mask(pkg):
744 mask_atoms = global_pmaskdict.get(pkg.cp)
748 if portage.dep.match_from_list(x, pkg_list):
752 # Ensure that profile sub_path attributes are unique. Process in reverse order
753 # so that profiles with duplicate sub_path from overlays will override
754 # profiles with the same sub_path from parent repos.
756 profile_list.reverse()
757 profile_sub_paths = set()
758 for prof in profile_list:
759 if prof.sub_path in profile_sub_paths:
761 profile_sub_paths.add(prof.sub_path)
762 profiles.setdefault(prof.arch, []).append(prof)
764 # Use an empty profile for checking dependencies of
765 # packages that have empty KEYWORDS.
766 prof = ProfileDesc('**', 'stable', '', '')
767 profiles.setdefault(prof.arch, []).append(prof)
769 for x in repoman_settings.archlist():
772 if x not in profiles:
773 print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
774 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
775 print(red("up with the "+x+" team."))
779 logging.fatal("Couldn't find licenses?")
783 logging.fatal("Couldn't read KEYWORDS from arch.list")
787 logging.fatal("Couldn't find use.desc?")
792 #we are inside a category directory
794 if catdir not in repoman_settings.categories:
796 mydirlist=os.listdir(startdir)
798 if x == "CVS" or x.startswith("."):
800 if os.path.isdir(startdir+"/"+x):
801 scanlist.append(catdir+"/"+x)
802 repo_subdir = catdir + os.sep
804 for x in repoman_settings.categories:
805 if not os.path.isdir(startdir+"/"+x):
807 for y in os.listdir(startdir+"/"+x):
808 if y == "CVS" or y.startswith("."):
810 if os.path.isdir(startdir+"/"+x+"/"+y):
811 scanlist.append(x+"/"+y)
814 catdir = reposplit[-2]
815 if catdir not in repoman_settings.categories:
817 scanlist.append(catdir+"/"+reposplit[-1])
818 repo_subdir = scanlist[-1] + os.sep
820 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
821 ' from the current working directory'
822 logging.critical(msg)
825 repo_subdir_len = len(repo_subdir)
828 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
830 def dev_keywords(profiles):
832 Create a set of KEYWORDS values that exist in 'dev'
833 profiles. These are used
834 to trigger a message notifying the user when they might
835 want to add the --include-dev option.
838 for arch, arch_profiles in profiles.items():
839 for prof in arch_profiles:
840 arch_set = type_arch_map.get(prof.status)
843 type_arch_map[prof.status] = arch_set
846 dev_keywords = type_arch_map.get('dev', set())
847 dev_keywords.update(['~' + arch for arch in dev_keywords])
848 return frozenset(dev_keywords)
850 dev_keywords = dev_keywords(profiles)
855 # provided by the desktop-file-utils package
856 desktop_file_validate = find_binary("desktop-file-validate")
857 desktop_pattern = re.compile(r'.*\.desktop$')
863 xmllint_capable = False
864 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
867 """Parse a RFC 822 date and time string.
868 This is required for python3 compatibility, since the
869 rfc822.parsedate() function is not available."""
872 for x in s.upper().split():
873 for y in x.split(','):
877 if len(s_split) != 6:
880 # %a, %d %b %Y %H:%M:%S %Z
881 a, d, b, Y, H_M_S, Z = s_split
883 # Convert month to integer, since strptime %w is locale-dependent.
884 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
885 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
889 m = str(m).rjust(2, '0')
891 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
893 def fetch_metadata_dtd():
895 Fetch metadata.dtd if it doesn't exist or the ctime is older than
896 metadata_dtd_ctime_interval.
898 @returns: True if successful, otherwise False
902 metadata_dtd_st = None
903 current_time = int(time.time())
905 metadata_dtd_st = os.stat(metadata_dtd)
906 except EnvironmentError as e:
907 if e.errno not in (errno.ENOENT, errno.ESTALE):
911 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
912 if abs(current_time - metadata_dtd_st.st_ctime) \
913 < metadata_dtd_ctime_interval:
918 print(green("***") + " the local copy of metadata.dtd " + \
919 "needs to be refetched, doing that now")
922 url_f = urllib_request_urlopen(metadata_dtd_uri)
923 msg_info = url_f.info()
924 last_modified = msg_info.get('last-modified')
925 if last_modified is not None:
926 last_modified = parsedate(last_modified)
927 if last_modified is not None:
928 last_modified = calendar.timegm(last_modified)
930 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
932 local_f = open(metadata_dtd_tmp, mode='wb')
933 local_f.write(url_f.read())
935 if last_modified is not None:
937 os.utime(metadata_dtd_tmp,
938 (int(last_modified), int(last_modified)))
940 # This fails on some odd non-unix-like filesystems.
941 # We don't really need the mtime to be preserved
942 # anyway here (currently we use ctime to trigger
943 # fetch), so just ignore it.
945 os.rename(metadata_dtd_tmp, metadata_dtd)
948 os.unlink(metadata_dtd_tmp)
954 except EnvironmentError as e:
956 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
957 print(red("!!!")+" exception '%s' though." % (e,))
958 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
963 if options.mode == "manifest":
965 elif not find_binary('xmllint'):
966 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
967 if options.xml_parse or repolevel==3:
968 print(red("!!!")+" sorry, xmllint is needed. failing\n")
971 if not fetch_metadata_dtd():
973 #this can be problematic if xmllint changes their output
976 if options.mode == 'commit' and vcs:
977 utilities.detect_vcs_conflicts(options, vcs)
979 if options.mode == "manifest":
981 elif options.pretend:
982 print(green("\nRepoMan does a once-over of the neighborhood..."))
984 print(green("\nRepoMan scours the neighborhood..."))
987 modified_ebuilds = set()
988 modified_changelogs = set()
994 mycvstree = cvstree.getentries("./", recursive=1)
995 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
996 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
998 svnstatus = os.popen("svn status").readlines()
999 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1000 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1002 mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines()
1003 mychanged = ["./" + elem[:-1] for elem in mychanged]
1005 mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines()
1006 mynew = ["./" + elem[:-1] for elem in mynew]
1008 bzrstatus = os.popen("bzr status -S .").readlines()
1009 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
1010 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
1012 mychanged = os.popen("hg status --no-status --modified .").readlines()
1013 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1014 mynew = os.popen("hg status --no-status --added .").readlines()
1015 mynew = ["./" + elem.rstrip() for elem in mynew]
1018 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1019 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1020 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1021 if os.path.basename(x) == "ChangeLog")
1023 have_pmasked = False
1024 have_dev_keywords = False
1027 arch_xmatch_caches = {}
1028 shared_xmatch_caches = {"cp-list":{}}
1030 # Disable the "ebuild.notadded" check when not in commit mode and
1031 # running `svn status` in every package dir will be too expensive.
1033 check_ebuild_notadded = not \
1034 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1036 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1037 thirdpartymirrors = []
1038 for v in repoman_settings.thirdpartymirrors().values():
1039 thirdpartymirrors.extend(v)
1041 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1043 Implements doctype() as required to avoid deprecation warnings with
1046 def doctype(self, name, pubid, system):
1050 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1051 except (EnvironmentError, ParseError, PermissionDenied) as e:
1053 except FileNotFound:
1054 # TODO: Download as we do for metadata.dtd, but add a way to
1055 # disable for non-gentoo repoman users who may not have herds.
1059 #ebuilds and digests added to cvs respectively.
1060 logging.info("checking package %s" % x)
1062 catdir,pkgdir=x.split("/")
1063 checkdir=repodir+"/"+x
1064 checkdir_relative = ""
1066 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1068 checkdir_relative = os.path.join(catdir, checkdir_relative)
1069 checkdir_relative = os.path.join(".", checkdir_relative)
1070 generated_manifest = False
1072 if options.mode == "manifest" or \
1073 (options.mode != 'manifest-check' and \
1074 'digest' in repoman_settings.features) or \
1075 options.mode in ('commit', 'fix') and not options.pretend:
1076 auto_assumed = set()
1077 fetchlist_dict = portage.FetchlistDict(checkdir,
1078 repoman_settings, portdb)
1079 if options.mode == 'manifest' and options.force:
1080 portage._doebuild_manifest_exempt_depend += 1
1082 distdir = repoman_settings['DISTDIR']
1083 mf = portage.manifest.Manifest(checkdir, distdir,
1084 fetchlist_dict=fetchlist_dict)
1085 mf.create(requiredDistfiles=None,
1086 assumeDistHashesAlways=True)
1087 for distfiles in fetchlist_dict.values():
1088 for distfile in distfiles:
1089 if os.path.isfile(os.path.join(distdir, distfile)):
1090 mf.fhashdict['DIST'].pop(distfile, None)
1092 auto_assumed.add(distfile)
1095 portage._doebuild_manifest_exempt_depend -= 1
1097 repoman_settings["O"] = checkdir
1098 generated_manifest = digestgen(
1099 mysettings=repoman_settings, myportdb=portdb)
1101 if not generated_manifest:
1102 print("Unable to generate manifest.")
1105 if options.mode == "manifest":
1106 if not dofail and options.force and auto_assumed and \
1107 'assume-digests' in repoman_settings.features:
1108 # Show which digests were assumed despite the --force option
1109 # being given. This output will already have been shown by
1110 # digestgen() if assume-digests is not enabled, so only show
1111 # it here if assume-digests is enabled.
1112 pkgs = list(fetchlist_dict)
1114 portage.writemsg_stdout(" digest.assumed" + \
1115 portage.output.colorize("WARN",
1116 str(len(auto_assumed)).rjust(18)) + "\n")
1118 fetchmap = fetchlist_dict[cpv]
1119 pf = portage.catsplit(cpv)[1]
1120 for distfile in sorted(fetchmap):
1121 if distfile in auto_assumed:
1122 portage.writemsg_stdout(
1123 " %s::%s\n" % (pf, distfile))
1128 if not generated_manifest:
1129 repoman_settings['O'] = checkdir
1130 repoman_settings['PORTAGE_QUIET'] = '1'
1131 if not portage.digestcheck([], repoman_settings, strict=1):
1132 stats["manifest.bad"] += 1
1133 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1134 repoman_settings.pop('PORTAGE_QUIET', None)
1136 if options.mode == 'manifest-check':
1139 checkdirlist=os.listdir(checkdir)
1143 for y in checkdirlist:
1144 if (y in no_exec or y.endswith(".ebuild")) and \
1145 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1146 stats["file.executable"] += 1
1147 fails["file.executable"].append(os.path.join(checkdir, y))
1148 if y.endswith(".ebuild"):
1150 ebuildlist.append(pf)
1151 cpv = "%s/%s" % (catdir, pf)
1153 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1156 stats["ebuild.syntax"] += 1
1157 fails["ebuild.syntax"].append(os.path.join(x, y))
1161 stats["ebuild.output"] += 1
1162 fails["ebuild.output"].append(os.path.join(x, y))
1164 if not portage.eapi_is_supported(myaux["EAPI"]):
1166 stats["EAPI.unsupported"] += 1
1167 fails["EAPI.unsupported"].append(os.path.join(x, y))
1169 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1170 root_config=root_config, type_name="ebuild")
1172 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1174 for i in range(len(ebuildlist)):
1175 ebuild_split = portage.pkgsplit(ebuildlist[i])
1176 pkgsplits[ebuild_split] = ebuildlist[i]
1177 ebuildlist[i] = ebuild_split
1178 ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp))
1179 for i in range(len(ebuildlist)):
1180 ebuildlist[i] = pkgsplits[ebuildlist[i]]
1185 if len(pkgs) != len(ebuildlist):
1186 # If we can't access all the metadata then it's totally unsafe to
1187 # commit since there's no way to generate a correct Manifest.
1188 # Do not try to do any more QA checks on this package since missing
1189 # metadata leads to false positives for several checks, and false
1190 # positives confuse users.
1194 for y in checkdirlist:
1195 m = disallowed_filename_chars_re.search(y.strip(os.sep))
1197 stats["file.name"] += 1
1198 fails["file.name"].append("%s/%s: char '%s'" % \
1199 (checkdir, y, m.group(0)))
1201 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1205 for l in codecs.open(_unicode_encode(os.path.join(checkdir, y),
1206 encoding=_encodings['fs'], errors='strict'),
1207 mode='r', encoding=_encodings['repo.content']):
1209 except UnicodeDecodeError as ue:
1210 stats["file.UTF8"] += 1
1211 s = ue.object[:ue.start]
1215 s = s[s.rfind("\n") + 1:]
1216 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1218 if vcs in ("git", "hg") and check_ebuild_notadded:
1220 myf = os.popen("git ls-files --others %s" % \
1221 (portage._shell_quote(checkdir_relative),))
1223 myf = os.popen("hg status --no-status --unknown %s" % \
1224 (portage._shell_quote(checkdir_relative),))
1226 if l[:-1][-7:] == ".ebuild":
1227 stats["ebuild.notadded"] += 1
1228 fails["ebuild.notadded"].append(
1229 os.path.join(x, os.path.basename(l[:-1])))
1232 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1235 myf=open(checkdir+"/CVS/Entries","r")
1237 myf = os.popen("svn status --depth=files --verbose " + checkdir)
1239 myf = os.popen("bzr ls -v --kind=file " + checkdir)
1240 myl = myf.readlines()
1246 splitl=l[1:].split("/")
1249 if splitl[0][-7:]==".ebuild":
1250 eadded.append(splitl[0][:-7])
1255 # tree conflict, new in subversion 1.6
1258 if l[-7:] == ".ebuild":
1259 eadded.append(os.path.basename(l[:-7]))
1264 if l[-7:] == ".ebuild":
1265 eadded.append(os.path.basename(l[:-7]))
1267 myf = os.popen("svn status " + checkdir)
1272 l = l.rstrip().split(' ')[-1]
1273 if l[-7:] == ".ebuild":
1274 eadded.append(os.path.basename(l[:-7]))
1277 stats["CVS/Entries.IO_error"] += 1
1278 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
1283 mf = Manifest(checkdir, repoman_settings["DISTDIR"])
1284 mydigests=mf.getTypeDigests("DIST")
1286 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1288 src_uri_error = False
1289 for mykey in fetchlist_dict:
1291 myfiles_all.extend(fetchlist_dict[mykey])
1292 except portage.exception.InvalidDependString as e:
1293 src_uri_error = True
1295 portdb.aux_get(mykey, ["SRC_URI"])
1297 # This will be reported as an "ebuild.syntax" error.
1300 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
1301 fails["SRC_URI.syntax"].append(
1302 "%s.ebuild SRC_URI: %s" % (mykey, e))
1304 if not src_uri_error:
1305 # This test can produce false positives if SRC_URI could not
1306 # be parsed for one or more ebuilds. There's no point in
1307 # producing a false error here since the root cause will
1308 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1309 # or "ebuild.sytax".
1310 myfiles_all = set(myfiles_all)
1311 for entry in mydigests:
1312 if entry not in myfiles_all:
1313 stats["digest.unused"] += 1
1314 fails["digest.unused"].append(checkdir+"::"+entry)
1315 for entry in myfiles_all:
1316 if entry not in mydigests:
1317 stats["digest.missing"] += 1
1318 fails["digest.missing"].append(checkdir+"::"+entry)
1321 if os.path.exists(checkdir+"/files"):
1322 filesdirlist=os.listdir(checkdir+"/files")
1324 # recurse through files directory
1325 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1327 y = filesdirlist.pop(0)
1328 relative_path = os.path.join(x, "files", y)
1329 full_path = os.path.join(repodir, relative_path)
1331 mystat = os.stat(full_path)
1332 except OSError as oe:
1334 # don't worry about it. it likely was removed via fix above.
1338 if S_ISDIR(mystat.st_mode):
1339 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1340 if y == "CVS" or y == ".svn":
1342 for z in os.listdir(checkdir+"/files/"+y):
1343 if z == "CVS" or z == ".svn":
1345 filesdirlist.append(y+"/"+z)
1346 # Current policy is no files over 20 KiB, these are the checks. File size between
1347 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1348 elif mystat.st_size > 61440:
1349 stats["file.size.fatal"] += 1
1350 fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1351 elif mystat.st_size > 20480:
1352 stats["file.size"] += 1
1353 fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1355 m = disallowed_filename_chars_re.search(
1356 os.path.basename(y.rstrip(os.sep)))
1358 stats["file.name"] += 1
1359 fails["file.name"].append("%s/files/%s: char '%s'" % \
1360 (checkdir, y, m.group(0)))
1362 if desktop_file_validate and desktop_pattern.match(y):
1363 status, cmd_output = subprocess_getstatusoutput(
1364 "'%s' '%s'" % (desktop_file_validate, full_path))
1365 if os.WIFEXITED(status) and os.WEXITSTATUS(status) != os.EX_OK:
1366 # Note: in the future we may want to grab the
1367 # warnings in addition to the errors. We're
1368 # just doing errors now since we don't want
1369 # to generate too much noise at first.
1370 error_re = re.compile(r'.*\s*error:\s*(.*)')
1371 for line in cmd_output.splitlines():
1372 error_match = error_re.match(line)
1373 if error_match is None:
1375 stats["desktop.invalid"] += 1
1376 fails["desktop.invalid"].append(
1377 relative_path + ': %s' % error_match.group(1))
1381 if check_changelog and "ChangeLog" not in checkdirlist:
1382 stats["changelog.missing"]+=1
1383 fails["changelog.missing"].append(x+"/ChangeLog")
1386 #metadata.xml file check
1387 if "metadata.xml" not in checkdirlist:
1388 stats["metadata.missing"]+=1
1389 fails["metadata.missing"].append(x+"/metadata.xml")
1390 #metadata.xml parse check
1392 metadata_bad = False
1394 # read metadata.xml into memory
1396 _metadata_xml = xml.etree.ElementTree.parse(
1397 os.path.join(checkdir, "metadata.xml"),
1398 parser=xml.etree.ElementTree.XMLParser(
1399 target=_MetadataTreeBuilder()))
1400 except (xml.etree.ElementTree.ParseError, ExpatError, EnvironmentError) as e:
1402 stats["metadata.bad"] += 1
1403 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1406 # load USE flags from metadata.xml
1408 musedict = utilities.parse_metadata_use(_metadata_xml)
1409 except portage.exception.ParseError as e:
1411 stats["metadata.bad"] += 1
1412 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1414 # Run other metadata.xml checkers
1416 utilities.check_metadata(_metadata_xml, herd_base)
1417 except (utilities.UnknownHerdsError, ) as e:
1419 stats["metadata.bad"] += 1
1420 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1423 #Only carry out if in package directory or check forced
1424 if xmllint_capable and not metadata_bad:
1425 # xmlint can produce garbage output even on success, so only dump
1426 # the ouput when it fails.
1427 st, out = subprocess_getstatusoutput(
1428 "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \
1429 (metadata_dtd, os.path.join(checkdir, "metadata.xml")))
1431 print(red("!!!") + " metadata.xml is invalid:")
1432 for z in out.splitlines():
1433 print(red("!!! ")+z)
1434 stats["metadata.bad"]+=1
1435 fails["metadata.bad"].append(x+"/metadata.xml")
1438 muselist = frozenset(musedict)
1440 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1441 changelog_modified = changelog_path in modified_changelogs
1444 # detect unused local USE-descriptions
1445 used_useflags = set()
1447 for y in ebuildlist:
1448 relative_path = os.path.join(x, y + ".ebuild")
1449 full_path = os.path.join(repodir, relative_path)
1450 ebuild_path = y + ".ebuild"
1452 ebuild_path = os.path.join(pkgdir, ebuild_path)
1454 ebuild_path = os.path.join(catdir, ebuild_path)
1455 ebuild_path = os.path.join(".", ebuild_path)
1456 if check_changelog and not changelog_modified \
1457 and ebuild_path in new_ebuilds:
1458 stats['changelog.ebuildadded'] += 1
1459 fails['changelog.ebuildadded'].append(relative_path)
1461 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1462 #ebuild not added to vcs
1463 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1464 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1465 myesplit=portage.pkgsplit(y)
1466 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1467 or pv_toolong_re.search(myesplit[1]) \
1468 or pv_toolong_re.search(myesplit[2]):
1469 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1470 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1472 elif myesplit[0]!=pkgdir:
1473 print(pkgdir,myesplit[0])
1474 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1475 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1482 for k, msgs in pkg.invalid.items():
1484 stats[k] = stats[k] + 1
1485 fails[k].append("%s %s" % (relative_path, msg))
1488 myaux = pkg.metadata
1489 eapi = myaux["EAPI"]
1490 inherited = pkg.inherited
1491 live_ebuild = live_eclasses.intersection(inherited)
1493 for k, v in myaux.items():
1494 if not isinstance(v, basestring):
1496 m = non_ascii_re.search(v)
1498 stats["variable.invalidchar"] += 1
1499 fails["variable.invalidchar"].append(
1500 ("%s: %s variable contains non-ASCII " + \
1501 "character at position %s") % \
1502 (relative_path, k, m.start() + 1))
1504 if not src_uri_error:
1505 # Check that URIs don't reference a server from thirdpartymirrors.
1506 for uri in portage.dep.use_reduce( \
1507 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1508 contains_mirror = False
1509 for mirror in thirdpartymirrors:
1510 if uri.startswith(mirror):
1511 contains_mirror = True
1513 if not contains_mirror:
1516 stats["SRC_URI.mirror"] += 1
1517 fails["SRC_URI.mirror"].append(
1518 "%s: '%s' found in thirdpartymirrors" % \
1519 (relative_path, mirror))
1521 if myaux.get("PROVIDE"):
1522 stats["virtual.oldstyle"]+=1
1523 fails["virtual.oldstyle"].append(relative_path)
1525 for pos, missing_var in enumerate(missingvars):
1526 if not myaux.get(missing_var):
1527 if catdir == "virtual" and \
1528 missing_var in ("HOMEPAGE", "LICENSE"):
1530 if live_ebuild and missing_var == "KEYWORDS":
1532 myqakey=missingvars[pos]+".missing"
1533 stats[myqakey]=stats[myqakey]+1
1534 fails[myqakey].append(x+"/"+y+".ebuild")
1536 if catdir == "virtual":
1537 for var in ("HOMEPAGE", "LICENSE"):
1539 myqakey = var + ".virtual"
1540 stats[myqakey] = stats[myqakey] + 1
1541 fails[myqakey].append(relative_path)
1543 # 14 is the length of DESCRIPTION=""
1544 if len(myaux['DESCRIPTION']) > max_desc_len:
1545 stats['DESCRIPTION.toolong'] += 1
1546 fails['DESCRIPTION.toolong'].append(
1547 "%s: DESCRIPTION is %d characters (max %d)" % \
1548 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1550 keywords = myaux["KEYWORDS"].split()
1551 stable_keywords = []
1552 for keyword in keywords:
1553 if not keyword.startswith("~") and \
1554 not keyword.startswith("-"):
1555 stable_keywords.append(keyword)
1557 if ebuild_path in new_ebuilds:
1558 stable_keywords.sort()
1559 stats["KEYWORDS.stable"] += 1
1560 fails["KEYWORDS.stable"].append(
1561 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1562 " ".join(stable_keywords))
1564 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1565 if not kw.startswith("-"))
1567 previous_keywords = slot_keywords.get(myaux["SLOT"])
1568 if previous_keywords is None:
1569 slot_keywords[myaux["SLOT"]] = set()
1570 elif ebuild_archs and not live_ebuild:
1571 dropped_keywords = previous_keywords.difference(ebuild_archs)
1572 if dropped_keywords:
1573 stats["KEYWORDS.dropped"] += 1
1574 fails["KEYWORDS.dropped"].append(
1575 relative_path + ": %s" % \
1576 " ".join(sorted(dropped_keywords)))
1578 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1580 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1581 if "-*" in keywords:
1589 stats["KEYWORDS.stupid"] += 1
1590 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1593 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1594 not be allowed to be marked stable
1597 bad_stable_keywords = []
1598 for keyword in keywords:
1599 if not keyword.startswith("~") and \
1600 not keyword.startswith("-"):
1601 bad_stable_keywords.append(keyword)
1603 if bad_stable_keywords:
1604 stats["LIVEVCS.stable"] += 1
1605 fails["LIVEVCS.stable"].append(
1606 x + "/" + y + ".ebuild with stable keywords:%s " % \
1607 bad_stable_keywords)
1608 del bad_stable_keywords
1610 if keywords and not has_global_mask(pkg):
1611 stats["LIVEVCS.unmasked"] += 1
1612 fails["LIVEVCS.unmasked"].append(relative_path)
1614 if options.ignore_arches:
1615 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1616 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1619 for keyword in myaux["KEYWORDS"].split():
1620 if (keyword[0]=="-"):
1622 elif (keyword[0]=="~"):
1623 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1625 arches.append([keyword, keyword, [keyword]])
1628 # Use an empty profile for checking dependencies of
1629 # packages that have empty KEYWORDS.
1630 arches.append(['**', '**', ['**']])
1632 baddepsyntax = False
1633 badlicsyntax = False
1634 badprovsyntax = False
1635 catpkg = catdir+"/"+y
1637 inherited_java_eclass = "java-pkg-2" in inherited or \
1638 "java-pkg-opt-2" in inherited
1639 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1640 operator_tokens = set(["||", "(", ")"])
1641 type_list, badsyntax = [], []
1642 for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
1643 "LICENSE", "PROPERTIES", "PROVIDE"):
1644 mydepstr = myaux[mytype]
1647 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1648 token_class=portage.dep.Atom
1651 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1652 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1653 except portage.exception.InvalidDependString as e:
1655 badsyntax.append(str(e))
1657 if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1658 if mytype in ("RDEPEND", "PDEPEND") and \
1659 "test?" in mydepstr.split():
1660 stats[mytype + '.suspect'] += 1
1661 fails[mytype + '.suspect'].append(relative_path + \
1662 ": 'test?' USE conditional in %s" % mytype)
1668 is_blocker = atom.blocker
1670 if mytype == "DEPEND" and \
1671 not is_blocker and \
1672 not inherited_java_eclass and \
1673 atom.cp == "virtual/jdk":
1674 stats['java.eclassesnotused'] += 1
1675 fails['java.eclassesnotused'].append(relative_path)
1676 elif mytype == "DEPEND" and \
1677 not is_blocker and \
1678 not inherited_wxwidgets_eclass and \
1679 atom.cp == "x11-libs/wxGTK":
1680 stats['wxwidgets.eclassnotused'] += 1
1681 fails['wxwidgets.eclassnotused'].append(
1682 relative_path + ": DEPENDs on x11-libs/wxGTK"
1683 " without inheriting wxwidgets.eclass")
1684 elif mytype in ("PDEPEND", "RDEPEND"):
1685 if not is_blocker and \
1686 atom.cp in suspect_rdepend:
1687 stats[mytype + '.suspect'] += 1
1688 fails[mytype + '.suspect'].append(
1689 relative_path + ": '%s'" % atom)
1691 if atom.operator == "~" and \
1692 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
1693 stats[mytype + '.badtilde'] += 1
1694 fails[mytype + '.badtilde'].append(
1695 (relative_path + ": %s uses the ~ operator"
1696 " with a non-zero revision:" + \
1697 " '%s'") % (mytype, atom))
1699 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1701 for m,b in zip(type_list, badsyntax):
1702 stats[m+".syntax"] += 1
1703 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1705 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
1706 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
1707 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1708 badlicsyntax = badlicsyntax > 0
1709 badprovsyntax = badprovsyntax > 0
1711 # uselist checks - global
1714 for myflag in myaux["IUSE"].split():
1715 flag_name = myflag.lstrip("+-")
1716 used_useflags.add(flag_name)
1717 if myflag != flag_name:
1718 default_use.append(myflag)
1719 if flag_name not in uselist:
1720 myuse.append(flag_name)
1722 # uselist checks - metadata
1723 for mypos in range(len(myuse)-1,-1,-1):
1724 if myuse[mypos] and (myuse[mypos] in muselist):
1727 if default_use and not eapi_has_iuse_defaults(eapi):
1728 for myflag in default_use:
1729 stats['EAPI.incompatible'] += 1
1730 fails['EAPI.incompatible'].append(
1731 (relative_path + ": IUSE defaults" + \
1732 " not supported with EAPI='%s':" + \
1733 " '%s'") % (eapi, myflag))
1735 for mypos in range(len(myuse)):
1736 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1737 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1740 if not badlicsyntax:
1741 # Parse the LICENSE variable, remove USE conditions and
1743 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
1744 # Check each entry to ensure that it exists in PORTDIR's
1745 # license directory.
1746 for lic in licenses:
1747 # Need to check for "||" manually as no portage
1748 # function will remove it without removing values.
1749 if lic not in liclist and lic != "||":
1750 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1751 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
1754 myuse = myaux["KEYWORDS"].split()
1762 if myskey not in kwlist:
1763 stats["KEYWORDS.invalid"] += 1
1764 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1765 elif myskey not in profiles:
1766 stats["KEYWORDS.invalid"] += 1
1767 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1772 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
1773 except portage.exception.InvalidDependString as e:
1774 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
1775 fails["RESTRICT.syntax"].append(
1776 "%s: RESTRICT: %s" % (relative_path, e))
1779 myrestrict = set(myrestrict)
1780 mybadrestrict = myrestrict.difference(valid_restrict)
1782 stats["RESTRICT.invalid"] += len(mybadrestrict)
1783 for mybad in mybadrestrict:
1784 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
1786 required_use = myaux["REQUIRED_USE"]
1788 if not eapi_has_required_use(eapi):
1789 stats['EAPI.incompatible'] += 1
1790 fails['EAPI.incompatible'].append(
1791 relative_path + ": REQUIRED_USE" + \
1792 " not supported with EAPI='%s'" % (eapi,))
1794 portage.dep.check_required_use(required_use, (),
1795 pkg.iuse.is_valid_flag)
1796 except portage.exception.InvalidDependString as e:
1797 stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
1798 fails["REQUIRED_USE.syntax"].append(
1799 "%s: REQUIRED_USE: %s" % (relative_path, e))
1803 relative_path = os.path.join(x, y + ".ebuild")
1804 full_path = os.path.join(repodir, relative_path)
1805 if not vcs_preserves_mtime:
1806 if ebuild_path not in new_ebuilds and \
1807 ebuild_path not in modified_ebuilds:
1810 # All ebuilds should have utf_8 encoding.
1811 f = codecs.open(_unicode_encode(full_path,
1812 encoding=_encodings['fs'], errors='strict'),
1813 mode='r', encoding=_encodings['repo.content'])
1815 for check_name, e in run_checks(f, pkg):
1816 stats[check_name] += 1
1817 fails[check_name].append(relative_path + ': %s' % e)
1820 except UnicodeDecodeError:
1821 # A file.UTF8 failure will have already been recorded above.
1825 # The dep_check() calls are the most expensive QA test. If --force
1826 # is enabled, there's no point in wasting time on these since the
1827 # user is intent on forcing the commit anyway.
1830 for keyword,arch,groups in arches:
1832 if arch not in profiles:
1833 # A missing profile will create an error further down
1834 # during the KEYWORDS verification.
1837 for prof in profiles[arch]:
1839 if prof.status not in ("stable", "dev") or \
1840 prof.status == "dev" and not options.include_dev:
1843 dep_settings = arch_caches.get(prof.sub_path)
1844 if dep_settings is None:
1845 dep_settings = portage.config(
1846 config_profile_path=prof.abs_path,
1847 config_incrementals=repoman_incrementals,
1849 _unmatched_removal=options.unmatched_removal,
1851 if options.without_mask:
1852 dep_settings._mask_manager = \
1853 copy.deepcopy(dep_settings._mask_manager)
1854 dep_settings._mask_manager._pmaskdict.clear()
1855 arch_caches[prof.sub_path] = dep_settings
1857 xmatch_cache_key = (prof.sub_path, tuple(groups))
1858 xcache = arch_xmatch_caches.get(xmatch_cache_key)
1862 xcache = portdb.xcache
1863 xcache.update(shared_xmatch_caches)
1864 arch_xmatch_caches[xmatch_cache_key] = xcache
1866 trees["/"]["porttree"].settings = dep_settings
1867 portdb.settings = dep_settings
1868 portdb.xcache = xcache
1869 # for package.use.mask support inside dep_check
1870 dep_settings.setcpv(pkg)
1871 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
1872 # just in case, prevent config.reset() from nuking these.
1873 dep_settings.backup_changes("ACCEPT_KEYWORDS")
1875 if not baddepsyntax:
1876 ismasked = not ebuild_archs or \
1877 pkg.cpv not in portdb.xmatch("list-visible", pkg.cp)
1879 if not have_pmasked:
1880 have_pmasked = bool(dep_settings._getMaskAtom(
1881 pkg.cpv, pkg.metadata))
1882 if options.ignore_masked:
1884 #we are testing deps for a masked package; give it some lee-way
1886 matchmode = "minimum-all"
1889 matchmode = "minimum-visible"
1891 if not have_dev_keywords:
1892 have_dev_keywords = \
1893 bool(dev_keywords.intersection(keywords))
1895 if prof.status == "dev":
1896 suffix=suffix+"indev"
1898 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1900 mykey=mytype+".bad"+suffix
1901 myvalue = myaux[mytype]
1905 success, atoms = portage.dep_check(myvalue, portdb,
1906 dep_settings, use="all", mode=matchmode,
1912 if not prof.sub_path:
1913 # old-style virtuals currently aren't
1914 # resolvable with empty profile, since
1915 # 'virtuals' mappings are unavailable
1916 # (it would be expensive to search
1917 # for PROVIDE in all ebuilds)
1918 atoms = [atom for atom in atoms if not \
1919 (atom.cp.startswith('virtual/') and \
1920 not portdb.cp_list(atom.cp))]
1922 #we have some unsolvable deps
1923 #remove ! deps, which always show up as unsatisfiable
1924 atoms = [str(atom.unevaluated_atom) \
1925 for atom in atoms if not atom.blocker]
1927 #if we emptied out our list, continue:
1930 stats[mykey]=stats[mykey]+1
1931 fails[mykey].append("%s: %s(%s) %s" % \
1932 (relative_path, keyword,
1935 stats[mykey]=stats[mykey]+1
1936 fails[mykey].append("%s: %s(%s) %s" % \
1937 (relative_path, keyword,
1940 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
1941 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
1942 #if not portage.portdb.xmatch("bestmatch-visible",x):
1943 # stats["ebuild.nostable"]+=1
1944 # fails["ebuild.nostable"].append(x)
1945 if allmasked and repolevel == 3:
1946 stats["ebuild.allmasked"]+=1
1947 fails["ebuild.allmasked"].append(x)
1949 # check if there are unused local USE-descriptions in metadata.xml
1950 # (unless there are any invalids, to avoid noise)
1952 for myflag in muselist.difference(used_useflags):
1953 stats["metadata.warning"] += 1
1954 fails["metadata.warning"].append(
1955 "%s/metadata.xml: unused local USE-description: '%s'" % \
1958 if options.mode == "manifest":
1961 #dofail will be set to 1 if we have failed in at least one non-warning category
1963 #dowarn will be set to 1 if we tripped any warnings
1965 #dofull will be set if we should print a "repoman full" informational message
1966 dofull = options.mode != 'full'
1972 if x not in qawarnings:
1976 (dowarn and not (options.quiet or options.mode == "scan")):
1979 # Save QA output so that it can be conveniently displayed
1980 # in $EDITOR while the user creates a commit message.
1981 # Otherwise, the user would not be able to see this output
1982 # once the editor has taken over the screen.
1983 qa_output = StringIO()
1984 style_file = ConsoleStyleFile(sys.stdout)
1985 if options.mode == 'commit' and \
1986 (not commitmessage or not commitmessage.strip()):
1987 style_file.write_listener = qa_output
1988 console_writer = StyleWriter(file=style_file, maxcol=9999)
1989 console_writer.style_listener = style_file.new_styles
1991 f = formatter.AbstractFormatter(console_writer)
1993 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
1996 del console_writer, f, style_file
1997 qa_output = qa_output.getvalue()
1998 qa_output = qa_output.splitlines(True)
2000 def grouplist(mylist,seperator="/"):
2001 """(list,seperator="/") -- Takes a list of elements; groups them into
2002 same initial element categories. Returns a dict of {base:[sublist]}
2003 From: ["blah/foo","spork/spatula","blah/weee/splat"]
2004 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
2007 xs=x.split(seperator)
2010 if xs[0] not in mygroups:
2011 mygroups[xs[0]]=[seperator.join(xs[1:])]
2013 mygroups[xs[0]]+=[seperator.join(xs[1:])]
2016 suggest_ignore_masked = False
2017 suggest_include_dev = False
2019 if have_pmasked and not (options.without_mask or options.ignore_masked):
2020 suggest_ignore_masked = True
2021 if have_dev_keywords and not options.include_dev:
2022 suggest_include_dev = True
2024 if suggest_ignore_masked or suggest_include_dev:
2026 if suggest_ignore_masked:
2027 print(bold("Note: use --without-mask to check " + \
2028 "KEYWORDS on dependencies of masked packages"))
2030 if suggest_include_dev:
2031 print(bold("Note: use --include-dev (-d) to check " + \
2032 "dependencies for 'dev' profiles"))
2035 if options.mode != 'commit':
2037 print(bold("Note: type \"repoman full\" for a complete listing."))
2038 if dowarn and not dofail:
2039 print(green("RepoMan sez:"),"\"You're only giving me a partial QA payment?\n I'll take it this time, but I'm not happy.\"")
2041 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2043 print(bad("Please fix these important QA issues first."))
2044 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2047 if dofail and can_force and options.force and not options.pretend:
2048 print(green("RepoMan sez:") + \
2049 " \"You want to commit even with these QA issues?\n" + \
2050 " I'll take it this time, but I'm not happy.\"\n")
2052 if options.force and not can_force:
2053 print(bad("The --force option has been disabled due to extraordinary issues."))
2054 print(bad("Please fix these important QA issues first."))
2055 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2059 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2064 myvcstree=portage.cvstree.getentries("./",recursive=1)
2065 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
2066 except SystemExit as e:
2067 raise # TODO propagate this
2069 err("Error retrieving CVS tree; exiting.")
2072 svnstatus=os.popen("svn status --no-ignore").readlines()
2073 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
2074 except SystemExit as e:
2075 raise # TODO propagate this
2077 err("Error retrieving SVN info; exiting.")
2079 # get list of files not under version control or missing
2080 myf = os.popen("git ls-files --others")
2081 myunadded = [ "./" + elem[:-1] for elem in myf ]
2085 bzrstatus=os.popen("bzr status -S .").readlines()
2086 myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
2087 except SystemExit as e:
2088 raise # TODO propagate this
2090 err("Error retrieving bzr info; exiting.")
2092 myunadded = os.popen("hg status --no-status --unknown .").readlines()
2093 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2095 # Mercurial doesn't handle manually deleted files as removed from
2096 # the repository, so the user need to remove them before commit,
2097 # using "hg remove [FILES]"
2098 mydeleted = os.popen("hg status --no-status --deleted .").readlines()
2099 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2104 for x in range(len(myunadded)-1,-1,-1):
2105 xs=myunadded[x].split("/")
2107 print("!!! files dir is not added! Please correct this.")
2109 elif xs[-1]=="Manifest":
2110 # It's a manifest... auto add
2111 myautoadd+=[myunadded[x]]
2115 print(">>> Auto-Adding missing Manifest(s)...")
2118 print("(cvs add "+" ".join(myautoadd)+")")
2120 print("(svn add "+" ".join(myautoadd)+")")
2122 print("(git add "+" ".join(myautoadd)+")")
2124 print("(bzr add "+" ".join(myautoadd)+")")
2126 print("(hg add "+" ".join(myautoadd)+")")
2130 retval=os.system("cvs add "+" ".join(myautoadd))
2132 retval=os.system("svn add "+" ".join(myautoadd))
2134 retval=os.system("git add "+" ".join(myautoadd))
2136 retval=os.system("bzr add "+" ".join(myautoadd))
2138 retval=os.system("hg add "+" ".join(myautoadd))
2140 writemsg_level("!!! Exiting on %s (shell) error code: %s\n" % \
2141 (vcs, retval), level=logging.ERROR, noiselevel=-1)
2145 print(red("!!! The following files are in your local tree but are not added to the master"))
2146 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2153 if vcs == "hg" and mydeleted:
2154 print(red("!!! The following files are removed manually from your local tree but are not"))
2155 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2163 mycvstree = cvstree.getentries("./", recursive=1)
2164 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2165 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2166 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
2167 bin_blob_pattern = re.compile("^-kb$")
2168 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2169 recursive=1, basedir="./"))
2173 svnstatus = os.popen("svn status").readlines()
2174 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2175 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2176 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2178 # Subversion expands keywords specified in svn:keywords properties.
2179 props = os.popen("svn propget -R svn:keywords").readlines()
2180 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2181 for prop in props if " - " in prop)
2184 mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines()
2185 mychanged = ["./" + elem[:-1] for elem in mychanged]
2187 mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines()
2188 mynew = ["./" + elem[:-1] for elem in mynew]
2190 myremoved = os.popen("git diff-index --name-only --relative --diff-filter=D HEAD").readlines()
2191 myremoved = ["./" + elem[:-1] for elem in myremoved]
2194 bzrstatus = os.popen("bzr status -S .").readlines()
2195 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
2196 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] in "NK" or elem[0:1] == "R" ) ]
2197 myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
2198 myremoved = [ "./" + elem.split()[-3:-2][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "K" or elem[0:1] == "R" ) ]
2199 # Bazaar expands nothing.
2202 mychanged = os.popen("hg status --no-status --modified .").readlines()
2203 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2204 mynew = os.popen("hg status --no-status --added .").readlines()
2205 mynew = ["./" + elem.rstrip() for elem in mynew]
2206 myremoved = os.popen("hg status --no-status --removed .").readlines()
2207 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2210 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2211 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2213 print("(Didn't find any changed files...)")
2217 # Manifests need to be regenerated after all other commits, so don't commit
2218 # them now even if they have changed.
2221 for f in mychanged + mynew:
2222 if "Manifest" == os.path.basename(f):
2226 if vcs in ('git', 'hg'):
2227 myupdates.difference_update(myremoved)
2228 myupdates = list(myupdates)
2229 mymanifests = list(mymanifests)
2233 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2234 if vcs not in ('cvs', 'svn'):
2235 # With git, bzr and hg, there's never any keyword expansion, so
2236 # there's no need to regenerate manifests and all files will be
2237 # committed in one big commit at the end.
2241 headerstring = "'\$(Header|Id).*\$'"
2243 svn_keywords = dict((k.lower(), k) for k in [
2246 "LastChangedRevision",
2257 for myfile in myupdates:
2259 # for CVS, no_expansion contains files that are excluded from expansion
2261 if myfile in no_expansion:
2264 # for SVN, expansion contains files that are included in expansion
2266 if myfile not in expansion:
2269 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2270 enabled_keywords = []
2271 for k in expansion[myfile]:
2272 keyword = svn_keywords.get(k.lower())
2273 if keyword is not None:
2274 enabled_keywords.append(keyword)
2276 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2278 myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile)
2280 myheaders.append(myfile)
2282 print("%s have headers that will change." % green(str(len(myheaders))))
2283 print("* Files with headers will cause the manifests to be changed and committed separately.")
2285 logging.info("myupdates: %s", myupdates)
2286 logging.info("myheaders: %s", myheaders)
2288 commitmessage = options.commitmsg
2289 if options.commitmsgfile:
2291 f = codecs.open(_unicode_encode(options.commitmsgfile,
2292 encoding=_encodings['fs'], errors='strict'),
2293 mode='r', encoding=_encodings['content'], errors='replace')
2294 commitmessage = f.read()
2297 except (IOError, OSError) as e:
2298 if e.errno == errno.ENOENT:
2299 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2302 # We've read the content so the file is no longer needed.
2303 commitmessagefile = None
2304 if not commitmessage or not commitmessage.strip():
2306 editor = os.environ.get("EDITOR")
2307 if editor and utilities.editor_is_executable(editor):
2308 commitmessage = utilities.get_commit_message_with_editor(
2309 editor, message=qa_output)
2311 commitmessage = utilities.get_commit_message_with_stdin()
2312 except KeyboardInterrupt:
2314 if not commitmessage or not commitmessage.strip():
2315 print("* no commit message? aborting commit.")
2317 commitmessage = commitmessage.rstrip()
2318 portage_version = getattr(portage, "VERSION", None)
2319 if portage_version is None:
2320 sys.stderr.write("Failed to insert portage version in message!\n")
2322 portage_version = "Unknown"
2323 unameout = platform.system() + " "
2324 if platform.system() in ["Darwin", "SunOS"]:
2325 unameout += platform.processor()
2327 unameout += platform.machine()
2328 commitmessage += "\n\n(Portage version: %s/%s/%s" % \
2329 (portage_version, vcs, unameout)
2331 commitmessage += ", RepoMan options: --force"
2332 commitmessage += ")"
2334 if options.ask and userquery('Commit changes?', True) != 'Yes':
2335 print("* aborting commit.")
2338 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2339 myfiles = myupdates + myremoved
2340 if not myheaders and "sign" not in repoman_settings.features:
2341 myfiles += mymanifests
2342 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2343 mymsg = os.fdopen(fd, "wb")
2344 mymsg.write(_unicode_encode(commitmessage))
2348 print(green("Using commit message:"))
2349 print(green("------------------------------------------------------------------------------"))
2350 print(commitmessage)
2351 print(green("------------------------------------------------------------------------------"))
2354 # Having a leading ./ prefix on file paths can trigger a bug in
2355 # the cvs server when committing files to multiple directories,
2356 # so strip the prefix.
2357 myfiles = [f.lstrip("./") for f in myfiles]
2360 commit_cmd.extend(vcs_global_opts)
2361 commit_cmd.append("commit")
2362 commit_cmd.extend(vcs_local_opts)
2363 commit_cmd.extend(["-F", commitmessagefile])
2364 commit_cmd.extend(myfiles)
2368 print("(%s)" % (" ".join(commit_cmd),))
2370 retval = spawn(commit_cmd, env=os.environ)
2371 if retval != os.EX_OK:
2372 writemsg_level(("!!! Exiting on %s (shell) " + \
2373 "error code: %s\n") % (vcs, retval),
2374 level=logging.ERROR, noiselevel=-1)
2378 os.unlink(commitmessagefile)
2382 # Setup the GPG commands
2383 def gpgsign(filename):
2384 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2386 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2387 " Is make.globals missing?")
2388 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2389 "PORTAGE_GPG_KEY" not in repoman_settings:
2390 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2391 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2392 if "PORTAGE_GPG_DIR" not in repoman_settings:
2393 repoman_settings["PORTAGE_GPG_DIR"] = \
2394 os.path.expanduser("~/.gnupg")
2395 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2396 % repoman_settings["PORTAGE_GPG_DIR"])
2398 repoman_settings["PORTAGE_GPG_DIR"] = \
2399 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2400 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2401 raise portage.exception.InvalidLocation(
2402 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2403 repoman_settings["PORTAGE_GPG_DIR"])
2404 gpgvars = {"FILE": filename}
2405 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2406 v = repoman_settings.get(k)
2409 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2411 print("("+gpgcmd+")")
2413 rValue = os.system(gpgcmd)
2414 if rValue == os.EX_OK:
2415 os.rename(filename+".asc", filename)
2417 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2419 # When files are removed and re-added, the cvs server will put /Attic/
2420 # inside the $Header path. This code detects the problem and corrects it
2421 # so that the Manifest will generate correctly. See bug #169500.
2422 # Use binary mode in order to avoid potential character encoding issues.
2423 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2424 attic_str = b'/Attic/'
2425 attic_replace = b'/'
2427 f = open(_unicode_encode(x,
2428 encoding=_encodings['fs'], errors='strict'),
2430 mylines = f.readlines()
2433 for i, line in enumerate(mylines):
2434 if cvs_header_re.match(line) is not None and \
2436 mylines[i] = line.replace(attic_str, attic_replace)
2439 portage.util.write_atomic(x, b''.join(mylines),
2442 manifest_commit_required = True
2443 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2444 myfiles = myupdates + myremoved
2445 for x in range(len(myfiles)-1, -1, -1):
2446 if myfiles[x].count("/") < 4-repolevel:
2449 if repolevel==3: # In a package dir
2450 repoman_settings["O"] = startdir
2451 digestgen(mysettings=repoman_settings, myportdb=portdb)
2452 elif repolevel==2: # In a category dir
2455 if len(xs) < 4-repolevel:
2461 mydone.append(xs[0])
2462 repoman_settings["O"] = os.path.join(startdir, xs[0])
2463 if not os.path.isdir(repoman_settings["O"]):
2465 digestgen(mysettings=repoman_settings, myportdb=portdb)
2466 elif repolevel==1: # repo-cvsroot
2467 print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n")
2470 if len(xs) < 4-repolevel:
2474 if "/".join(xs[:2]) in mydone:
2476 mydone.append("/".join(xs[:2]))
2477 repoman_settings["O"] = os.path.join(startdir, xs[0], xs[1])
2478 if not os.path.isdir(repoman_settings["O"]):
2480 digestgen(mysettings=repoman_settings, myportdb=portdb)
2482 print(red("I'm confused... I don't know where I am!"))
2485 # Force an unsigned commit when more than one Manifest needs to be signed.
2486 if repolevel < 3 and "sign" in repoman_settings.features:
2488 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2489 mymsg = os.fdopen(fd, "wb")
2490 mymsg.write(_unicode_encode(commitmessage))
2491 mymsg.write(b"\n (Unsigned Manifest commit)")
2495 commit_cmd.extend(vcs_global_opts)
2496 commit_cmd.append("commit")
2497 commit_cmd.extend(vcs_local_opts)
2498 commit_cmd.extend(["-F", commitmessagefile])
2499 commit_cmd.extend(f.lstrip("./") for f in mymanifests)
2503 print("(%s)" % (" ".join(commit_cmd),))
2505 retval = spawn(commit_cmd, env=os.environ)
2507 writemsg_level(("!!! Exiting on %s (shell) " + \
2508 "error code: %s\n") % (vcs, retval),
2509 level=logging.ERROR, noiselevel=-1)
2513 os.unlink(commitmessagefile)
2516 manifest_commit_required = False
2519 if "sign" in repoman_settings.features:
2521 myfiles = myupdates + myremoved + mymanifests
2523 if repolevel==3: # In a package dir
2524 repoman_settings["O"] = "."
2525 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2526 elif repolevel==2: # In a category dir
2530 if len(xs) < 4-repolevel:
2536 mydone.append(xs[0])
2537 repoman_settings["O"] = os.path.join(".", xs[0])
2538 if not os.path.isdir(repoman_settings["O"]):
2540 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2541 elif repolevel==1: # repo-cvsroot
2542 print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n")
2546 if len(xs) < 4-repolevel:
2550 if "/".join(xs[:2]) in mydone:
2552 mydone.append("/".join(xs[:2]))
2553 repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
2554 if not os.path.isdir(repoman_settings["O"]):
2556 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2557 except portage.exception.PortageException as e:
2558 portage.writemsg("!!! %s\n" % str(e))
2559 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2563 # It's not safe to use the git commit -a option since there might
2564 # be some modified files elsewhere in the working tree that the
2565 # user doesn't want to commit. Therefore, call git update-index
2566 # in order to ensure that the index is updated with the latest
2567 # versions of all new and modified files in the relevant portion
2568 # of the working tree.
2569 myfiles = mymanifests + myupdates
2571 update_index_cmd = ["git", "update-index"]
2572 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2574 print("(%s)" % (" ".join(update_index_cmd),))
2576 retval = spawn(update_index_cmd, env=os.environ)
2577 if retval != os.EX_OK:
2578 writemsg_level(("!!! Exiting on %s (shell) " + \
2579 "error code: %s\n") % (vcs, retval),
2580 level=logging.ERROR, noiselevel=-1)
2583 if vcs in ['git', 'bzr', 'hg'] or manifest_commit_required or signed:
2585 myfiles = mymanifests[:]
2586 if vcs in ['git', 'bzr', 'hg']:
2587 myfiles += myupdates
2588 myfiles += myremoved
2591 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2592 mymsg = os.fdopen(fd, "wb")
2593 # strip the closing parenthesis
2594 mymsg.write(_unicode_encode(commitmessage[:-1]))
2596 mymsg.write(_unicode_encode(
2597 ", signed Manifest commit with key %s)" % \
2598 repoman_settings["PORTAGE_GPG_KEY"]))
2600 mymsg.write(b", unsigned Manifest commit)")
2604 if options.pretend and vcs is None:
2605 # substitute a bogus value for pretend output
2606 commit_cmd.append("cvs")
2608 commit_cmd.append(vcs)
2609 commit_cmd.extend(vcs_global_opts)
2610 commit_cmd.append("commit")
2611 commit_cmd.extend(vcs_local_opts)
2613 commit_cmd.extend(["--logfile", commitmessagefile])
2614 commit_cmd.extend(myfiles)
2616 commit_cmd.extend(["-F", commitmessagefile])
2617 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2621 print("(%s)" % (" ".join(commit_cmd),))
2623 retval = spawn(commit_cmd, env=os.environ)
2624 if retval != os.EX_OK:
2625 writemsg_level(("!!! Exiting on %s (shell) " + \
2626 "error code: %s\n") % (vcs, retval),
2627 level=logging.ERROR, noiselevel=-1)
2631 os.unlink(commitmessagefile)
2637 print("Commit complete.")
2639 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
2640 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")