2 # Copyright 1999-2013 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, unicode_literals
30 from urllib.request import urlopen as urllib_request_urlopen
32 from urllib import urlopen as urllib_request_urlopen
34 from itertools import chain
35 from stat import S_ISDIR
37 from os import path as osp
38 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
39 sys.path.insert(0, pym_path)
41 portage._internal_caller = True
42 portage._disable_legacy_globals()
45 import xml.etree.ElementTree
46 from xml.parsers.expat import ExpatError
47 except (SystemExit, KeyboardInterrupt):
49 except (ImportError, SystemError, RuntimeError, Exception):
50 # broken or missing xml support
51 # http://bugs.python.org/issue14988
52 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
53 from portage.output import EOutput
59 from portage import os
60 from portage import _encodings
61 from portage import _unicode_encode
62 from repoman.checks import run_checks
63 from repoman import utilities
64 from repoman.herdbase import make_herd_base
65 from _emerge.Package import Package
66 from _emerge.RootConfig import RootConfig
67 from _emerge.userquery import userquery
68 import portage.checksum
70 from portage import cvstree, normalize_path
71 from portage import util
72 from portage.exception import (FileNotFound, InvalidAtom, MissingParameter,
73 ParseError, PermissionDenied)
74 from portage.dep import Atom
75 from portage.process import find_binary, spawn
76 from portage.output import bold, create_color_func, \
78 from portage.output import ConsoleStyleFile, StyleWriter
79 from portage.util import writemsg_level
80 from portage.package.ebuild.digestgen import digestgen
81 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
83 if sys.hexversion >= 0x3000000:
86 util.initialize_logger()
88 # 14 is the length of DESCRIPTION=""
90 allowed_filename_chars="a-zA-Z0-9._-+:"
91 pv_toolong_re = re.compile(r'[0-9]{19,}')
92 GPG_KEY_ID_REGEX = r'(0x)?([0-9a-fA-F]{8}|[0-9a-fA-F]{16}|[0-9a-fA-F]{24}|[0-9a-fA-F]{32}|[0-9a-fA-F]{40})!?'
93 bad = create_color_func("BAD")
95 # A sane umask is needed for files that portage creates.
97 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
98 # behave incrementally.
99 repoman_incrementals = tuple(x for x in \
100 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
101 config_root = os.environ.get("PORTAGE_CONFIGROOT")
102 repoman_settings = portage.config(config_root=config_root, local_config=False)
104 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
105 repoman_settings.get('TERM') == 'dumb' or \
106 not sys.stdout.isatty():
110 print("repoman: " + txt)
116 def exithandler(signum=None, frame=None):
117 logging.fatal("Interrupted; exiting...")
121 sys.exit(128 + signum)
123 signal.signal(signal.SIGINT,exithandler)
125 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
126 """Repoman needs it's own HelpFormatter for now, because the default ones
127 murder the help text."""
129 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
130 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
132 def format_description(self, description):
135 class RepomanOptionParser(optparse.OptionParser):
136 """Add the on_tail function, ruby has it, optionParser should too
139 def __init__(self, *args, **kwargs):
140 optparse.OptionParser.__init__(self, *args, **kwargs)
143 def on_tail(self, description):
144 self.tail += description
146 def format_help(self, formatter=None):
147 result = optparse.OptionParser.format_help(self, formatter)
152 def ParseArgs(argv, qahelp):
153 """This function uses a customized optionParser to parse command line arguments for repoman
155 argv - a sequence of command line arguments
156 qahelp - a dict of qa warning to help message
158 (opts, args), just like a call to parser.parse_args()
161 if argv and isinstance(argv[0], bytes):
162 argv = [portage._unicode_decode(x) for x in argv]
165 'commit' : 'Run a scan then commit changes',
166 'ci' : 'Run a scan then commit changes',
167 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
168 'full' : 'Scan directory tree and print all issues (not a summary)',
169 'help' : 'Show this screen',
170 'manifest' : 'Generate a Manifest (fetches files if necessary)',
171 'manifest-check' : 'Check Manifests for missing or incorrect digests',
172 'scan' : 'Scan directory tree for QA issues'
175 mode_keys = list(modes)
178 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
179 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
180 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
181 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
182 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
184 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
185 help='Request a confirmation before commiting')
187 parser.add_option('-m', '--commitmsg', dest='commitmsg',
188 help='specify a commit message on the command line')
190 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
191 help='specify a path to a file that contains a commit message')
193 parser.add_option('--digest',
194 type='choice', choices=('y', 'n'), metavar='<y|n>',
195 help='Automatically update Manifest digests for modified files')
197 parser.add_option('-p', '--pretend', dest='pretend', default=False,
198 action='store_true', help='don\'t commit or fix anything; just show what would be done')
200 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
201 help='do not print unnecessary messages')
204 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
205 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
206 'regardless of modification if \'force\' is specified)')
208 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
209 help='Commit with QA violations')
211 parser.add_option('--vcs', dest='vcs',
212 help='Force using specific VCS instead of autodetection')
214 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
215 help='be very verbose in output', default=0)
217 parser.add_option('-V', '--version', dest='version', action='store_true',
218 help='show version info')
220 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
221 default=False, help='forces the metadata.xml parse check to be carried out')
224 '--if-modified', type='choice', choices=('y', 'n'), default='n',
226 help='only check packages that have uncommitted modifications')
228 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
229 default=False, help='ignore arch-specific failures (where arch != host)')
231 parser.add_option("--ignore-default-opts",
233 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
235 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
236 default=False, help='ignore masked packages (not allowed with commit mode)')
238 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
239 default=False, help='include dev profiles in dependency checks')
241 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
242 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
244 parser.add_option('--without-mask', dest='without_mask', action='store_true',
245 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
247 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
248 help='specify which mode repoman will run in (default=full)')
250 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
253 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
255 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
257 sorted_qa = list(qahelp)
260 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
262 opts, args = parser.parse_args(argv[1:])
264 if not opts.ignore_default_opts:
265 default_opts = repoman_settings.get("REPOMAN_DEFAULT_OPTS", "").split()
267 opts, args = parser.parse_args(default_opts + sys.argv[1:])
269 if opts.mode == 'help':
270 parser.print_help(short=False)
278 parser.error("invalid mode: %s" % arg)
283 if opts.mode == 'ci':
284 opts.mode = 'commit' # backwards compat shortcut
286 if opts.mode == 'commit' and not (opts.force or opts.pretend):
287 if opts.ignore_masked:
288 parser.error('Commit mode and --ignore-masked are not compatible')
289 if opts.without_mask:
290 parser.error('Commit mode and --without-mask are not compatible')
292 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
293 for val in range(opts.verbosity):
294 logger = logging.getLogger()
295 logger.setLevel(logger.getEffectiveLevel() - 10)
297 for val in range(opts.quiet):
298 logger = logging.getLogger()
299 logger.setLevel(logger.getEffectiveLevel() + 10)
304 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
305 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
306 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
307 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
308 "changelog.missing":"Missing ChangeLog files",
309 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
310 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
311 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
312 "dependency.bad": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds)",
313 "dependency.badmasked": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds)",
314 "dependency.badindev": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds) in developing arch",
315 "dependency.badmaskedindev": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds) in developing arch",
316 "dependency.badtilde": "Uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
317 "dependency.syntax": "Syntax error in dependency string (usually an extra/missing space/parenthesis)",
318 "dependency.unknown" : "Ebuild has a dependency that refers to an unknown package (which may be valid if it is a blocker for a renamed/removed package, or is an alternative choice provided by an overlay)",
319 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
320 "file.size":"Files in the files directory must be under 20 KiB",
321 "file.size.fatal":"Files in the files directory must be under 60 KiB",
322 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
323 "file.UTF8":"File is not UTF8 compliant",
324 "inherit.deprecated":"Ebuild inherits a deprecated eclass",
325 "inherit.missing":"Ebuild uses functions from an eclass but does not inherit it",
326 "inherit.unused":"Ebuild inherits an eclass but does not use it",
327 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
328 "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
329 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
330 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
331 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
332 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
333 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
334 "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
335 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
336 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
337 "EAPI.definition":"EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
338 "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
339 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
340 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
341 "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
342 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
343 "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
344 "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
345 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
346 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
347 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
348 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
349 "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
350 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
351 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
352 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
353 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
354 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
355 "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
356 "variable.readonly":"Assigning a readonly variable",
357 "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
358 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
359 "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
360 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
361 "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
362 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
363 "LICENSE.deprecated":"This ebuild is listing a deprecated license.",
364 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
365 "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
366 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
367 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
368 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
369 "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
370 "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
371 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
372 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
373 "ebuild.badheader":"This ebuild has a malformed header",
374 "manifest.bad":"Manifest has missing or incorrect digests",
375 "metadata.missing":"Missing metadata.xml files",
376 "metadata.bad":"Bad metadata.xml files",
377 "metadata.warning":"Warnings in metadata.xml files",
378 "portage.internal":"The ebuild uses an internal Portage function or variable",
379 "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
380 "virtual.suspect":"Ebuild contains a package that usually should be pulled via virtual/, not directly.",
381 "usage.obsolete":"The ebuild makes use of an obsolete construct",
382 "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
385 qacats = list(qahelp)
390 "changelog.notadded",
391 "dependency.unknown",
396 "dependency.badmasked",
397 "dependency.badindev",
398 "dependency.badmaskedindev",
399 "dependency.badtilde",
400 "DESCRIPTION.toolong",
403 "LICENSE.deprecated",
418 "inherit.deprecated",
419 "java.eclassesnotused",
420 "wxwidgets.eclassnotused",
424 "upstream.workaround",
429 if portage.const._ENABLE_INHERIT_CHECK:
430 # This is experimental, so it's non-fatal.
431 qawarnings.add("inherit.missing")
433 non_ascii_re = re.compile(r'[^\x00-\x7f]')
435 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
436 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
437 allvars.update(Package.metadata_keys)
438 allvars = sorted(allvars)
440 for x in missingvars:
443 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
447 valid_restrict = frozenset(["binchecks", "bindist",
448 "fetch", "installsources", "mirror",
449 "primaryuri", "strip", "test", "userpriv"])
451 live_eclasses = frozenset([
462 suspect_rdepend = frozenset([
463 "app-arch/cabextract",
464 "app-arch/rpm2targz",
469 "dev-perl/extutils-pkgconfig",
475 "dev-util/gtk-doc-am",
478 "dev-util/pkg-config-lite",
480 "dev-util/pkgconfig",
481 "dev-util/pkgconfig-openbsd",
485 "media-gfx/ebdftopcf",
487 "sys-devel/autoconf",
488 "sys-devel/automake",
495 "virtual/linux-sources",
502 "dev-util/pkg-config-lite":"virtual/pkgconfig",
503 "dev-util/pkgconf":"virtual/pkgconfig",
504 "dev-util/pkgconfig":"virtual/pkgconfig",
505 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
506 "dev-libs/libusb":"virtual/libusb",
507 "dev-libs/libusbx":"virtual/libusb",
508 "dev-libs/libusb-compat":"virtual/libusb",
511 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
512 # force refetch if the local copy creation time is older than this
513 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
516 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
518 options, arguments = ParseArgs(sys.argv, qahelp)
521 print("Portage", portage.VERSION)
524 # Set this to False when an extraordinary issue (generally
525 # something other than a QA issue) makes it impossible to
526 # commit (like if Manifest generation fails).
529 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
533 myreporoot = os.path.basename(portdir_overlay)
534 myreporoot += mydir[len(portdir_overlay):]
537 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
542 vcses = utilities.FindVCS()
544 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
545 print(red('*** Please either clean up your workdir or specify --vcs option.'))
552 if options.if_modified == "y" and vcs is None:
553 logging.info("Not in a version controlled repository; "
554 "disabling --if-modified.")
555 options.if_modified = "n"
557 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
558 vcs_preserves_mtime = vcs in ('cvs',)
560 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
561 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
562 if vcs_global_opts is None:
563 if vcs in ('cvs', 'svn'):
564 vcs_global_opts = "-q"
567 vcs_global_opts = vcs_global_opts.split()
569 if options.mode == 'commit' and not options.pretend and not vcs:
570 logging.info("Not in a version controlled repository; enabling pretend mode.")
571 options.pretend = True
573 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
574 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
575 (repoman_settings.get('PORTDIR_OVERLAY', ''),
576 portage._shell_quote(portdir_overlay))
577 # We have to call the config constructor again so
578 # that config.repositories is initialized correctly.
579 repoman_settings = portage.config(config_root=config_root, local_config=False,
580 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
582 root = repoman_settings['EROOT']
584 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
586 portdb = trees[root]['porttree'].dbapi
588 # Constrain dependency resolution to the master(s)
589 # that are specified in layout.conf.
590 repodir = os.path.realpath(portdir_overlay)
591 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
592 portdb.porttrees = list(repo_config.eclass_db.porttrees)
593 portdir = portdb.porttrees[0]
595 if repo_config.allow_provide_virtual:
596 qawarnings.add("virtual.oldstyle")
598 if repo_config.sign_commit:
600 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
601 # the commit arguments. If key_id is unspecified, then it must be
602 # configured by `git config user.signingkey key_id`.
603 vcs_local_opts.append("--gpg-sign")
605 # In order to disable manifest signatures, repos may set
606 # "sign-manifests = false" in metadata/layout.conf. This
607 # can be used to prevent merge conflicts like those that
608 # thin-manifests is designed to prevent.
609 sign_manifests = "sign" in repoman_settings.features and \
610 repo_config.sign_manifest
612 if sign_manifests and options.mode in ("commit",) and \
613 repoman_settings.get("PORTAGE_GPG_KEY") and \
614 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
615 repoman_settings["PORTAGE_GPG_KEY"]) is None:
616 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
617 repoman_settings["PORTAGE_GPG_KEY"])
620 manifest_hashes = repo_config.manifest_hashes
621 if manifest_hashes is None:
622 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
624 if options.mode in ("commit", "fix", "manifest"):
625 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
626 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
627 "metadata/layout.conf does not contain the '%s' hash which "
628 "is required by this portage version. You will have to "
629 "upgrade portage if you want to generate valid manifests for "
630 "this repository.") % \
631 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
632 for line in textwrap.wrap(msg, 70):
636 unsupported_hashes = manifest_hashes.difference(
637 portage.const.MANIFEST2_HASH_FUNCTIONS)
638 if unsupported_hashes:
639 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
640 "metadata/layout.conf contains one or more hash types '%s' "
641 "which are not supported by this portage version. You will "
642 "have to upgrade portage if you want to generate valid "
643 "manifests for this repository.") % \
644 (repo_config.name, " ".join(sorted(unsupported_hashes)))
645 for line in textwrap.wrap(msg, 70):
649 if options.echangelog is None and repo_config.update_changelog:
650 options.echangelog = 'y'
653 options.echangelog = 'n'
655 # The --echangelog option causes automatic ChangeLog generation,
656 # which invalidates changelog.ebuildadded and changelog.missing
658 # Note: Some don't use ChangeLogs in distributed SCMs.
659 # It will be generated on server side from scm log,
660 # before package moves to the rsync server.
661 # This is needed because they try to avoid merge collisions.
662 # Gentoo's Council decided to always use the ChangeLog file.
663 # TODO: shouldn't this just be switched on the repo, iso the VCS?
664 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
666 if 'digest' in repoman_settings.features and options.digest != 'n':
669 logging.debug("vcs: %s" % (vcs,))
670 logging.debug("repo config: %s" % (repo_config,))
671 logging.debug("options: %s" % (options,))
673 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
674 # profile-specific config constructor calls.
675 env = os.environ.copy()
676 env['PORTDIR'] = portdir
677 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
679 logging.info('Setting paths:')
680 logging.info('PORTDIR = "' + portdir + '"')
681 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
683 # It's confusing if these warnings are displayed without the user
684 # being told which profile they come from, so disable them.
685 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
688 for path in repo_config.eclass_db.porttrees:
689 categories.extend(portage.util.grabfile(
690 os.path.join(path, 'profiles', 'categories')))
691 repoman_settings.categories = frozenset(
692 portage.util.stack_lists([categories], incremental=1))
693 categories = repoman_settings.categories
695 portdb.settings = repoman_settings
696 root_config = RootConfig(repoman_settings, trees[root], None)
697 # We really only need to cache the metadata that's necessary for visibility
698 # filtering. Anything else can be discarded to reduce memory consumption.
699 portdb._aux_cache_keys.clear()
700 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
702 reposplit = myreporoot.split(os.path.sep)
703 repolevel = len(reposplit)
705 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
706 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
707 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
708 if options.mode == 'commit' and repolevel not in [1,2,3]:
709 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
710 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
711 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
713 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
715 # Make startdir relative to the canonical repodir, so that we can pass
716 # it to digestgen and it won't have to be canonicalized again.
720 startdir = normalize_path(mydir)
721 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2-repolevel+3:])
724 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.")
726 def repoman_getstatusoutput(cmd):
728 Implements an interface similar to getstatusoutput(), but with
729 customized unicode handling (see bug #310789) and without the shell.
731 args = portage.util.shlex_split(cmd)
732 encoding = _encodings['fs']
733 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
734 # Python 3.1 does not support bytes in Popen args.
735 args = [_unicode_encode(x,
736 encoding=encoding, errors='strict') for x in args]
737 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
738 stderr=subprocess.STDOUT)
739 output = portage._unicode_decode(proc.communicate()[0],
740 encoding=encoding, errors='strict')
741 if output and output[-1] == "\n":
742 # getstatusoutput strips one newline
744 return (proc.wait(), output)
746 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
748 Implements an interface similar to os.popen(), but with customized
749 unicode handling (see bug #310789) and without the shell.
752 __slots__ = ('_proc', '_stdout')
754 def __init__(self, cmd):
755 args = portage.util.shlex_split(cmd)
756 encoding = _encodings['fs']
757 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
758 # Python 3.1 does not support bytes in Popen args.
759 args = [_unicode_encode(x,
760 encoding=encoding, errors='strict') for x in args]
761 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
762 object.__setattr__(self, '_proc', proc)
763 object.__setattr__(self, '_stdout',
764 codecs.getreader(encoding)(proc.stdout, 'strict'))
766 def _get_target(self):
767 return object.__getattribute__(self, '_stdout')
769 __enter__ = _get_target
771 def __exit__(self, exc_type, exc_value, traceback):
772 proc = object.__getattribute__(self, '_proc')
776 class ProfileDesc(object):
777 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
778 def __init__(self, arch, status, sub_path, tree_path):
782 sub_path = normalize_path(sub_path.lstrip(os.sep))
783 self.sub_path = sub_path
784 self.tree_path = tree_path
786 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
788 self.abs_path = tree_path
793 return 'empty profile'
796 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
798 # get lists of valid keywords, licenses, and use
802 global_pmasklines = []
804 for path in portdb.porttrees:
806 liclist.update(os.listdir(os.path.join(path, "licenses")))
809 kwlist.update(portage.grabfile(os.path.join(path,
810 "profiles", "arch.list")))
812 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
818 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
820 expand_list = os.listdir(expand_desc_dir)
824 for fn in expand_list:
825 if not fn[-5:] == '.desc':
827 use_prefix = fn[:-5].lower() + '_'
828 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
831 uselist.add(use_prefix + x[0])
833 global_pmasklines.append(portage.util.grabfile_package(
834 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
836 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
838 desc_file = io.open(_unicode_encode(desc_path,
839 encoding=_encodings['fs'], errors='strict'),
840 mode='r', encoding=_encodings['repo.content'], errors='replace')
841 except EnvironmentError:
844 for i, x in enumerate(desc_file):
851 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
852 desc_path + " line %d" % (i+1, ))
853 elif arch[0] not in kwlist:
854 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
855 desc_path + " line %d" % (i+1, ))
856 elif arch[2] not in valid_profile_types:
857 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
858 desc_path + " line %d" % (i+1, ))
859 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
860 if not os.path.isdir(profile_desc.abs_path):
862 "Invalid %s profile (%s) for arch %s in %s line %d",
863 arch[2], arch[1], arch[0], desc_path, i+1)
866 os.path.join(profile_desc.abs_path, 'deprecated')):
868 profile_list.append(profile_desc)
871 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
872 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
874 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
875 global_pmaskdict = {}
876 for x in global_pmasklines:
877 global_pmaskdict.setdefault(x.cp, []).append(x)
878 del global_pmasklines
880 def has_global_mask(pkg):
881 mask_atoms = global_pmaskdict.get(pkg.cp)
885 if portage.dep.match_from_list(x, pkg_list):
889 # Ensure that profile sub_path attributes are unique. Process in reverse order
890 # so that profiles with duplicate sub_path from overlays will override
891 # profiles with the same sub_path from parent repos.
893 profile_list.reverse()
894 profile_sub_paths = set()
895 for prof in profile_list:
896 if prof.sub_path in profile_sub_paths:
898 profile_sub_paths.add(prof.sub_path)
899 profiles.setdefault(prof.arch, []).append(prof)
901 # Use an empty profile for checking dependencies of
902 # packages that have empty KEYWORDS.
903 prof = ProfileDesc('**', 'stable', '', '')
904 profiles.setdefault(prof.arch, []).append(prof)
906 for x in repoman_settings.archlist():
909 if x not in profiles:
910 print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
911 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
912 print(red("up with the "+x+" team."))
915 liclist_deprecated = set()
916 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
917 liclist_deprecated.update(
918 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
921 logging.fatal("Couldn't find licenses?")
925 logging.fatal("Couldn't read KEYWORDS from arch.list")
929 logging.fatal("Couldn't find use.desc?")
934 #we are inside a category directory
936 if catdir not in categories:
938 mydirlist=os.listdir(startdir)
940 if x == "CVS" or x.startswith("."):
942 if os.path.isdir(startdir+"/"+x):
943 scanlist.append(catdir+"/"+x)
944 repo_subdir = catdir + os.sep
947 if not os.path.isdir(startdir+"/"+x):
949 for y in os.listdir(startdir+"/"+x):
950 if y == "CVS" or y.startswith("."):
952 if os.path.isdir(startdir+"/"+x+"/"+y):
953 scanlist.append(x+"/"+y)
956 catdir = reposplit[-2]
957 if catdir not in categories:
959 scanlist.append(catdir+"/"+reposplit[-1])
960 repo_subdir = scanlist[-1] + os.sep
962 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
963 ' from the current working directory'
964 logging.critical(msg)
967 repo_subdir_len = len(repo_subdir)
970 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
972 def vcs_files_to_cps(vcs_file_iter):
974 Iterate over the given modified file paths returned from the vcs,
975 and return a frozenset containing category/pn strings for each
982 if reposplit[-2] in categories and \
983 next(vcs_file_iter, None) is not None:
984 modified_cps.append("/".join(reposplit[-2:]))
987 category = reposplit[-1]
988 if category in categories:
989 for filename in vcs_file_iter:
990 f_split = filename.split(os.sep)
993 modified_cps.append(category + "/" + f_split[1])
997 for filename in vcs_file_iter:
998 f_split = filename.split(os.sep)
999 # ['.', category, pn,...]
1000 if len(f_split) > 3 and f_split[1] in categories:
1001 modified_cps.append("/".join(f_split[1:3]))
1003 return frozenset(modified_cps)
1005 def git_supports_gpg_sign():
1006 status, cmd_output = \
1007 repoman_getstatusoutput("git --version")
1008 cmd_output = cmd_output.split()
1010 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1011 if version is not None:
1012 version = [int(x) for x in version.groups()]
1013 if version[0] > 1 or \
1014 (version[0] == 1 and version[1] > 7) or \
1015 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1019 def dev_keywords(profiles):
1021 Create a set of KEYWORDS values that exist in 'dev'
1022 profiles. These are used
1023 to trigger a message notifying the user when they might
1024 want to add the --include-dev option.
1027 for arch, arch_profiles in profiles.items():
1028 for prof in arch_profiles:
1029 arch_set = type_arch_map.get(prof.status)
1030 if arch_set is None:
1032 type_arch_map[prof.status] = arch_set
1035 dev_keywords = type_arch_map.get('dev', set())
1036 dev_keywords.update(['~' + arch for arch in dev_keywords])
1037 return frozenset(dev_keywords)
1039 dev_keywords = dev_keywords(profiles)
1048 xmllint_capable = False
1049 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1052 """Parse a RFC 822 date and time string.
1053 This is required for python3 compatibility, since the
1054 rfc822.parsedate() function is not available."""
1057 for x in s.upper().split():
1058 for y in x.split(','):
1062 if len(s_split) != 6:
1065 # %a, %d %b %Y %H:%M:%S %Z
1066 a, d, b, Y, H_M_S, Z = s_split
1068 # Convert month to integer, since strptime %w is locale-dependent.
1069 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
1070 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
1071 m = month_map.get(b)
1076 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
1078 def fetch_metadata_dtd():
1080 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1081 metadata_dtd_ctime_interval.
1083 @return: True if successful, otherwise False
1087 metadata_dtd_st = None
1088 current_time = int(time.time())
1090 metadata_dtd_st = os.stat(metadata_dtd)
1091 except EnvironmentError as e:
1092 if e.errno not in (errno.ENOENT, errno.ESTALE):
1096 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1097 if abs(current_time - metadata_dtd_st.st_ctime) \
1098 < metadata_dtd_ctime_interval:
1103 print(green("***") + " the local copy of metadata.dtd " + \
1104 "needs to be refetched, doing that now")
1107 url_f = urllib_request_urlopen(metadata_dtd_uri)
1108 msg_info = url_f.info()
1109 last_modified = msg_info.get('last-modified')
1110 if last_modified is not None:
1111 last_modified = parsedate(last_modified)
1112 if last_modified is not None:
1113 last_modified = calendar.timegm(last_modified)
1115 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
1117 local_f = open(metadata_dtd_tmp, mode='wb')
1118 local_f.write(url_f.read())
1120 if last_modified is not None:
1122 os.utime(metadata_dtd_tmp,
1123 (int(last_modified), int(last_modified)))
1125 # This fails on some odd non-unix-like filesystems.
1126 # We don't really need the mtime to be preserved
1127 # anyway here (currently we use ctime to trigger
1128 # fetch), so just ignore it.
1130 os.rename(metadata_dtd_tmp, metadata_dtd)
1133 os.unlink(metadata_dtd_tmp)
1139 except EnvironmentError as e:
1141 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
1142 print(red("!!!")+" exception '%s' though." % (e,))
1143 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
1148 if options.mode == "manifest":
1150 elif not find_binary('xmllint'):
1151 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1152 if options.xml_parse or repolevel==3:
1153 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1156 if not fetch_metadata_dtd():
1158 #this can be problematic if xmllint changes their output
1159 xmllint_capable=True
1161 if options.mode == 'commit' and vcs:
1162 utilities.detect_vcs_conflicts(options, vcs)
1164 if options.mode == "manifest":
1166 elif options.pretend:
1167 print(green("\nRepoMan does a once-over of the neighborhood..."))
1169 print(green("\nRepoMan scours the neighborhood..."))
1172 modified_ebuilds = set()
1173 modified_changelogs = set()
1179 mycvstree = cvstree.getentries("./", recursive=1)
1180 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1181 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1182 if options.if_modified == "y":
1183 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1186 with repoman_popen("svn status") as f:
1187 svnstatus = f.readlines()
1188 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1189 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1190 if options.if_modified == "y":
1191 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1194 with repoman_popen("git diff-index --name-only "
1195 "--relative --diff-filter=M HEAD") as f:
1196 mychanged = f.readlines()
1197 mychanged = ["./" + elem[:-1] for elem in mychanged]
1199 with repoman_popen("git diff-index --name-only "
1200 "--relative --diff-filter=A HEAD") as f:
1201 mynew = f.readlines()
1202 mynew = ["./" + elem[:-1] for elem in mynew]
1203 if options.if_modified == "y":
1204 with repoman_popen("git diff-index --name-only "
1205 "--relative --diff-filter=D HEAD") as f:
1206 myremoved = f.readlines()
1207 myremoved = ["./" + elem[:-1] for elem in myremoved]
1210 with repoman_popen("bzr status -S .") as f:
1211 bzrstatus = f.readlines()
1212 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
1213 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
1214 if options.if_modified == "y":
1215 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" ) ]
1218 with repoman_popen("hg status --no-status --modified .") as f:
1219 mychanged = f.readlines()
1220 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1221 with repoman_popen("hg status --no-status --added .") as f:
1222 mynew = f.readlines()
1223 mynew = ["./" + elem.rstrip() for elem in mynew]
1224 if options.if_modified == "y":
1225 with repoman_popen("hg status --no-status --removed .") as f:
1226 myremoved = f.readlines()
1227 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1230 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1231 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1232 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1233 if os.path.basename(x) == "ChangeLog")
1235 def vcs_new_changed(relative_path):
1236 for x in chain(mychanged, mynew):
1237 if x == relative_path:
1241 have_pmasked = False
1242 have_dev_keywords = False
1245 # NOTE: match-all caches are not shared due to potential
1246 # differences between profiles in _get_implicit_iuse.
1248 arch_xmatch_caches = {}
1249 shared_xmatch_caches = {"cp-list":{}}
1251 # Disable the "ebuild.notadded" check when not in commit mode and
1252 # running `svn status` in every package dir will be too expensive.
1254 check_ebuild_notadded = not \
1255 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1257 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1258 thirdpartymirrors = {}
1259 for k, v in repoman_settings.thirdpartymirrors().items():
1261 if not v.endswith("/"):
1263 thirdpartymirrors[v] = k
1265 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1267 Implements doctype() as required to avoid deprecation warnings with
1270 def doctype(self, name, pubid, system):
1274 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1275 except (EnvironmentError, ParseError, PermissionDenied) as e:
1277 except FileNotFound:
1278 # TODO: Download as we do for metadata.dtd, but add a way to
1279 # disable for non-gentoo repoman users who may not have herds.
1282 effective_scanlist = scanlist
1283 if options.if_modified == "y":
1284 effective_scanlist = sorted(vcs_files_to_cps(
1285 chain(mychanged, mynew, myremoved)))
1287 for x in effective_scanlist:
1288 #ebuilds and digests added to cvs respectively.
1289 logging.info("checking package %s" % x)
1290 # save memory by discarding xmatch caches from previous package(s)
1291 arch_xmatch_caches.clear()
1293 catdir,pkgdir=x.split("/")
1294 checkdir=repodir+"/"+x
1295 checkdir_relative = ""
1297 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1299 checkdir_relative = os.path.join(catdir, checkdir_relative)
1300 checkdir_relative = os.path.join(".", checkdir_relative)
1301 generated_manifest = False
1303 if options.mode == "manifest" or \
1304 (options.mode != 'manifest-check' and options.digest == 'y') or \
1305 options.mode in ('commit', 'fix') and not options.pretend:
1306 auto_assumed = set()
1307 fetchlist_dict = portage.FetchlistDict(checkdir,
1308 repoman_settings, portdb)
1309 if options.mode == 'manifest' and options.force:
1310 portage._doebuild_manifest_exempt_depend += 1
1312 distdir = repoman_settings['DISTDIR']
1313 mf = repoman_settings.repositories.get_repo_for_location(
1314 os.path.dirname(os.path.dirname(checkdir)))
1315 mf = mf.load_manifest(checkdir, distdir,
1316 fetchlist_dict=fetchlist_dict)
1317 mf.create(requiredDistfiles=None,
1318 assumeDistHashesAlways=True)
1319 for distfiles in fetchlist_dict.values():
1320 for distfile in distfiles:
1321 if os.path.isfile(os.path.join(distdir, distfile)):
1322 mf.fhashdict['DIST'].pop(distfile, None)
1324 auto_assumed.add(distfile)
1327 portage._doebuild_manifest_exempt_depend -= 1
1329 repoman_settings["O"] = checkdir
1331 generated_manifest = digestgen(
1332 mysettings=repoman_settings, myportdb=portdb)
1333 except portage.exception.PermissionDenied as e:
1334 generated_manifest = False
1335 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1336 level=logging.ERROR, noiselevel=-1)
1338 if not generated_manifest:
1339 print("Unable to generate manifest.")
1342 if options.mode == "manifest":
1343 if not dofail and options.force and auto_assumed and \
1344 'assume-digests' in repoman_settings.features:
1345 # Show which digests were assumed despite the --force option
1346 # being given. This output will already have been shown by
1347 # digestgen() if assume-digests is not enabled, so only show
1348 # it here if assume-digests is enabled.
1349 pkgs = list(fetchlist_dict)
1351 portage.writemsg_stdout(" digest.assumed" + \
1352 portage.output.colorize("WARN",
1353 str(len(auto_assumed)).rjust(18)) + "\n")
1355 fetchmap = fetchlist_dict[cpv]
1356 pf = portage.catsplit(cpv)[1]
1357 for distfile in sorted(fetchmap):
1358 if distfile in auto_assumed:
1359 portage.writemsg_stdout(
1360 " %s::%s\n" % (pf, distfile))
1365 if not generated_manifest:
1366 repoman_settings['O'] = checkdir
1367 repoman_settings['PORTAGE_QUIET'] = '1'
1368 if not portage.digestcheck([], repoman_settings, strict=1):
1369 stats["manifest.bad"] += 1
1370 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1371 repoman_settings.pop('PORTAGE_QUIET', None)
1373 if options.mode == 'manifest-check':
1376 checkdirlist=os.listdir(checkdir)
1380 for y in checkdirlist:
1381 if (y in no_exec or y.endswith(".ebuild")) and \
1382 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1383 stats["file.executable"] += 1
1384 fails["file.executable"].append(os.path.join(checkdir, y))
1385 if y.endswith(".ebuild"):
1387 ebuildlist.append(pf)
1388 cpv = "%s/%s" % (catdir, pf)
1390 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1393 stats["ebuild.syntax"] += 1
1394 fails["ebuild.syntax"].append(os.path.join(x, y))
1398 stats["ebuild.output"] += 1
1399 fails["ebuild.output"].append(os.path.join(x, y))
1401 if not portage.eapi_is_supported(myaux["EAPI"]):
1403 stats["EAPI.unsupported"] += 1
1404 fails["EAPI.unsupported"].append(os.path.join(x, y))
1406 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1407 root_config=root_config, type_name="ebuild")
1411 if len(pkgs) != len(ebuildlist):
1412 # If we can't access all the metadata then it's totally unsafe to
1413 # commit since there's no way to generate a correct Manifest.
1414 # Do not try to do any more QA checks on this package since missing
1415 # metadata leads to false positives for several checks, and false
1416 # positives confuse users.
1420 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1421 ebuildlist = sorted(pkgs.values())
1422 ebuildlist = [pkg.pf for pkg in ebuildlist]
1424 for y in checkdirlist:
1425 index = repo_config.find_invalid_path_char(y)
1427 y_relative = os.path.join(checkdir_relative, y)
1428 if vcs is not None and not vcs_new_changed(y_relative):
1429 # If the file isn't in the VCS new or changed set, then
1430 # assume that it's an irrelevant temporary file (Manifest
1431 # entries are not generated for file names containing
1432 # prohibited characters). See bug #406877.
1435 stats["file.name"] += 1
1436 fails["file.name"].append("%s/%s: char '%s'" % \
1437 (checkdir, y, y[index]))
1439 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1444 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1445 encoding=_encodings['fs'], errors='strict'),
1446 mode='r', encoding=_encodings['repo.content'])
1449 except UnicodeDecodeError as ue:
1450 stats["file.UTF8"] += 1
1451 s = ue.object[:ue.start]
1455 s = s[s.rfind("\n") + 1:]
1456 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1461 if vcs in ("git", "hg") and check_ebuild_notadded:
1463 myf = repoman_popen("git ls-files --others %s" % \
1464 (portage._shell_quote(checkdir_relative),))
1466 myf = repoman_popen("hg status --no-status --unknown %s" % \
1467 (portage._shell_quote(checkdir_relative),))
1469 if l[:-1][-7:] == ".ebuild":
1470 stats["ebuild.notadded"] += 1
1471 fails["ebuild.notadded"].append(
1472 os.path.join(x, os.path.basename(l[:-1])))
1475 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1478 myf=open(checkdir+"/CVS/Entries","r")
1480 myf = repoman_popen("svn status --depth=files --verbose " +
1481 portage._shell_quote(checkdir))
1483 myf = repoman_popen("bzr ls -v --kind=file " +
1484 portage._shell_quote(checkdir))
1485 myl = myf.readlines()
1491 splitl=l[1:].split("/")
1494 if splitl[0][-7:]==".ebuild":
1495 eadded.append(splitl[0][:-7])
1500 # tree conflict, new in subversion 1.6
1503 if l[-7:] == ".ebuild":
1504 eadded.append(os.path.basename(l[:-7]))
1509 if l[-7:] == ".ebuild":
1510 eadded.append(os.path.basename(l[:-7]))
1512 myf = repoman_popen("svn status " +
1513 portage._shell_quote(checkdir))
1518 l = l.rstrip().split(' ')[-1]
1519 if l[-7:] == ".ebuild":
1520 eadded.append(os.path.basename(l[:-7]))
1523 stats["CVS/Entries.IO_error"] += 1
1524 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
1529 mf = repoman_settings.repositories.get_repo_for_location(
1530 os.path.dirname(os.path.dirname(checkdir)))
1531 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1532 mydigests=mf.getTypeDigests("DIST")
1534 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1536 src_uri_error = False
1537 for mykey in fetchlist_dict:
1539 myfiles_all.extend(fetchlist_dict[mykey])
1540 except portage.exception.InvalidDependString as e:
1541 src_uri_error = True
1543 portdb.aux_get(mykey, ["SRC_URI"])
1545 # This will be reported as an "ebuild.syntax" error.
1548 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
1549 fails["SRC_URI.syntax"].append(
1550 "%s.ebuild SRC_URI: %s" % (mykey, e))
1552 if not src_uri_error:
1553 # This test can produce false positives if SRC_URI could not
1554 # be parsed for one or more ebuilds. There's no point in
1555 # producing a false error here since the root cause will
1556 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1557 # or "ebuild.sytax".
1558 myfiles_all = set(myfiles_all)
1559 for entry in mydigests:
1560 if entry not in myfiles_all:
1561 stats["digest.unused"] += 1
1562 fails["digest.unused"].append(checkdir+"::"+entry)
1563 for entry in myfiles_all:
1564 if entry not in mydigests:
1565 stats["digest.missing"] += 1
1566 fails["digest.missing"].append(checkdir+"::"+entry)
1569 if os.path.exists(checkdir+"/files"):
1570 filesdirlist=os.listdir(checkdir+"/files")
1572 # recurse through files directory
1573 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1575 y = filesdirlist.pop(0)
1576 relative_path = os.path.join(x, "files", y)
1577 full_path = os.path.join(repodir, relative_path)
1579 mystat = os.stat(full_path)
1580 except OSError as oe:
1582 # don't worry about it. it likely was removed via fix above.
1586 if S_ISDIR(mystat.st_mode):
1587 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1588 if y == "CVS" or y == ".svn":
1590 for z in os.listdir(checkdir+"/files/"+y):
1591 if z == "CVS" or z == ".svn":
1593 filesdirlist.append(y+"/"+z)
1594 # Current policy is no files over 20 KiB, these are the checks. File size between
1595 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1596 elif mystat.st_size > 61440:
1597 stats["file.size.fatal"] += 1
1598 fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1599 elif mystat.st_size > 20480:
1600 stats["file.size"] += 1
1601 fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1603 index = repo_config.find_invalid_path_char(y)
1605 y_relative = os.path.join(checkdir_relative, "files", y)
1606 if vcs is not None and not vcs_new_changed(y_relative):
1607 # If the file isn't in the VCS new or changed set, then
1608 # assume that it's an irrelevant temporary file (Manifest
1609 # entries are not generated for file names containing
1610 # prohibited characters). See bug #406877.
1613 stats["file.name"] += 1
1614 fails["file.name"].append("%s/files/%s: char '%s'" % \
1615 (checkdir, y, y[index]))
1618 if check_changelog and "ChangeLog" not in checkdirlist:
1619 stats["changelog.missing"]+=1
1620 fails["changelog.missing"].append(x+"/ChangeLog")
1623 #metadata.xml file check
1624 if "metadata.xml" not in checkdirlist:
1625 stats["metadata.missing"]+=1
1626 fails["metadata.missing"].append(x+"/metadata.xml")
1627 #metadata.xml parse check
1629 metadata_bad = False
1631 # read metadata.xml into memory
1633 _metadata_xml = xml.etree.ElementTree.parse(
1634 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1635 encoding=_encodings['fs'], errors='strict'),
1636 parser=xml.etree.ElementTree.XMLParser(
1637 target=_MetadataTreeBuilder()))
1638 except (ExpatError, SyntaxError, EnvironmentError) as e:
1640 stats["metadata.bad"] += 1
1641 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1644 # load USE flags from metadata.xml
1646 musedict = utilities.parse_metadata_use(_metadata_xml)
1647 except portage.exception.ParseError as e:
1649 stats["metadata.bad"] += 1
1650 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1652 for atom in chain(*musedict.values()):
1657 except InvalidAtom as e:
1658 stats["metadata.bad"] += 1
1659 fails["metadata.bad"].append(
1660 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1663 stats["metadata.bad"] += 1
1664 fails["metadata.bad"].append(
1665 ("%s/metadata.xml: Atom contains "
1666 "unexpected cat/pn: %s") % (x, atom))
1668 # Run other metadata.xml checkers
1670 utilities.check_metadata(_metadata_xml, herd_base)
1671 except (utilities.UnknownHerdsError, ) as e:
1673 stats["metadata.bad"] += 1
1674 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1677 #Only carry out if in package directory or check forced
1678 if xmllint_capable and not metadata_bad:
1679 # xmlint can produce garbage output even on success, so only dump
1680 # the ouput when it fails.
1681 st, out = repoman_getstatusoutput(
1682 "xmllint --nonet --noout --dtdvalid %s %s" % \
1683 (portage._shell_quote(metadata_dtd),
1684 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1686 print(red("!!!") + " metadata.xml is invalid:")
1687 for z in out.splitlines():
1688 print(red("!!! ")+z)
1689 stats["metadata.bad"]+=1
1690 fails["metadata.bad"].append(x+"/metadata.xml")
1693 muselist = frozenset(musedict)
1695 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1696 changelog_modified = changelog_path in modified_changelogs
1698 # detect unused local USE-descriptions
1699 used_useflags = set()
1701 for y in ebuildlist:
1702 relative_path = os.path.join(x, y + ".ebuild")
1703 full_path = os.path.join(repodir, relative_path)
1704 ebuild_path = y + ".ebuild"
1706 ebuild_path = os.path.join(pkgdir, ebuild_path)
1708 ebuild_path = os.path.join(catdir, ebuild_path)
1709 ebuild_path = os.path.join(".", ebuild_path)
1710 if check_changelog and not changelog_modified \
1711 and ebuild_path in new_ebuilds:
1712 stats['changelog.ebuildadded'] += 1
1713 fails['changelog.ebuildadded'].append(relative_path)
1715 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1716 #ebuild not added to vcs
1717 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1718 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1719 myesplit=portage.pkgsplit(y)
1720 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1721 or pv_toolong_re.search(myesplit[1]) \
1722 or pv_toolong_re.search(myesplit[2]):
1723 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1724 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1726 elif myesplit[0]!=pkgdir:
1727 print(pkgdir,myesplit[0])
1728 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1729 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1736 for k, msgs in pkg.invalid.items():
1738 stats[k] = stats[k] + 1
1739 fails[k].append("%s: %s" % (relative_path, msg))
1742 myaux = pkg._metadata
1743 eapi = myaux["EAPI"]
1744 inherited = pkg.inherited
1745 live_ebuild = live_eclasses.intersection(inherited)
1747 for k, v in myaux.items():
1748 if not isinstance(v, basestring):
1750 m = non_ascii_re.search(v)
1752 stats["variable.invalidchar"] += 1
1753 fails["variable.invalidchar"].append(
1754 ("%s: %s variable contains non-ASCII " + \
1755 "character at position %s") % \
1756 (relative_path, k, m.start() + 1))
1758 if not src_uri_error:
1759 # Check that URIs don't reference a server from thirdpartymirrors.
1760 for uri in portage.dep.use_reduce( \
1761 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1762 contains_mirror = False
1763 for mirror, mirror_alias in thirdpartymirrors.items():
1764 if uri.startswith(mirror):
1765 contains_mirror = True
1767 if not contains_mirror:
1770 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1771 stats["SRC_URI.mirror"] += 1
1772 fails["SRC_URI.mirror"].append(
1773 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1774 (relative_path, mirror, new_uri))
1776 if myaux.get("PROVIDE"):
1777 stats["virtual.oldstyle"]+=1
1778 fails["virtual.oldstyle"].append(relative_path)
1780 for pos, missing_var in enumerate(missingvars):
1781 if not myaux.get(missing_var):
1782 if catdir == "virtual" and \
1783 missing_var in ("HOMEPAGE", "LICENSE"):
1785 if live_ebuild and missing_var == "KEYWORDS":
1787 myqakey=missingvars[pos]+".missing"
1788 stats[myqakey]=stats[myqakey]+1
1789 fails[myqakey].append(x+"/"+y+".ebuild")
1791 if catdir == "virtual":
1792 for var in ("HOMEPAGE", "LICENSE"):
1794 myqakey = var + ".virtual"
1795 stats[myqakey] = stats[myqakey] + 1
1796 fails[myqakey].append(relative_path)
1798 # 14 is the length of DESCRIPTION=""
1799 if len(myaux['DESCRIPTION']) > max_desc_len:
1800 stats['DESCRIPTION.toolong'] += 1
1801 fails['DESCRIPTION.toolong'].append(
1802 "%s: DESCRIPTION is %d characters (max %d)" % \
1803 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1805 keywords = myaux["KEYWORDS"].split()
1806 stable_keywords = []
1807 for keyword in keywords:
1808 if not keyword.startswith("~") and \
1809 not keyword.startswith("-"):
1810 stable_keywords.append(keyword)
1812 if ebuild_path in new_ebuilds and catdir != "virtual":
1813 stable_keywords.sort()
1814 stats["KEYWORDS.stable"] += 1
1815 fails["KEYWORDS.stable"].append(
1816 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1817 " ".join(stable_keywords))
1819 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1820 if not kw.startswith("-"))
1822 previous_keywords = slot_keywords.get(pkg.slot)
1823 if previous_keywords is None:
1824 slot_keywords[pkg.slot] = set()
1825 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1826 dropped_keywords = previous_keywords.difference(ebuild_archs)
1827 if dropped_keywords:
1828 stats["KEYWORDS.dropped"] += 1
1829 fails["KEYWORDS.dropped"].append(
1830 relative_path + ": %s" % \
1831 " ".join(sorted(dropped_keywords)))
1833 slot_keywords[pkg.slot].update(ebuild_archs)
1835 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1836 if "-*" in keywords:
1844 stats["KEYWORDS.stupid"] += 1
1845 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1848 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1849 not be allowed to be marked stable
1851 if live_ebuild and repo_config.name == "gentoo":
1852 bad_stable_keywords = []
1853 for keyword in keywords:
1854 if not keyword.startswith("~") and \
1855 not keyword.startswith("-"):
1856 bad_stable_keywords.append(keyword)
1858 if bad_stable_keywords:
1859 stats["LIVEVCS.stable"] += 1
1860 fails["LIVEVCS.stable"].append(
1861 x + "/" + y + ".ebuild with stable keywords:%s " % \
1862 bad_stable_keywords)
1863 del bad_stable_keywords
1865 if keywords and not has_global_mask(pkg):
1866 stats["LIVEVCS.unmasked"] += 1
1867 fails["LIVEVCS.unmasked"].append(relative_path)
1869 if options.ignore_arches:
1870 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1871 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1874 for keyword in keywords:
1875 if (keyword[0]=="-"):
1877 elif (keyword[0]=="~"):
1880 for expanded_arch in profiles:
1881 if expanded_arch == "**":
1883 arches.add((keyword, expanded_arch,
1884 (expanded_arch, "~" + expanded_arch)))
1886 arches.add((keyword, arch, (arch, keyword)))
1889 for expanded_arch in profiles:
1890 if expanded_arch == "**":
1892 arches.add((keyword, expanded_arch,
1895 arches.add((keyword, keyword, (keyword,)))
1897 # Use an empty profile for checking dependencies of
1898 # packages that have empty KEYWORDS.
1899 arches.add(('**', '**', ('**',)))
1901 unknown_pkgs = set()
1902 baddepsyntax = False
1903 badlicsyntax = False
1904 badprovsyntax = False
1905 catpkg = catdir+"/"+y
1907 inherited_java_eclass = "java-pkg-2" in inherited or \
1908 "java-pkg-opt-2" in inherited
1909 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1910 operator_tokens = set(["||", "(", ")"])
1911 type_list, badsyntax = [], []
1912 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
1913 mydepstr = myaux[mytype]
1915 buildtime = mytype in Package._buildtime_keys
1916 runtime = mytype in Package._runtime_keys
1918 if mytype.endswith("DEPEND"):
1919 token_class=portage.dep.Atom
1922 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1923 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1924 except portage.exception.InvalidDependString as e:
1926 badsyntax.append(str(e))
1928 if atoms and mytype.endswith("DEPEND"):
1930 "test?" in mydepstr.split():
1931 stats[mytype + '.suspect'] += 1
1932 fails[mytype + '.suspect'].append(relative_path + \
1933 ": 'test?' USE conditional in %s" % mytype)
1939 # Skip dependency.unknown for blockers, so that we
1940 # don't encourage people to remove necessary blockers,
1941 # as discussed in bug #382407.
1942 if atom.blocker is None and \
1943 not portdb.xmatch("match-all", atom) and \
1944 not atom.cp.startswith("virtual/"):
1945 unknown_pkgs.add((mytype, atom.unevaluated_atom))
1947 is_blocker = atom.blocker
1949 if catdir != "virtual":
1950 if not is_blocker and \
1951 atom.cp in suspect_virtual:
1952 stats['virtual.suspect'] += 1
1953 fails['virtual.suspect'].append(
1955 ": %s: consider using '%s' instead of '%s'" %
1956 (mytype, suspect_virtual[atom.cp], atom))
1959 not is_blocker and \
1960 not inherited_java_eclass and \
1961 atom.cp == "virtual/jdk":
1962 stats['java.eclassesnotused'] += 1
1963 fails['java.eclassesnotused'].append(relative_path)
1964 elif buildtime and \
1965 not is_blocker and \
1966 not inherited_wxwidgets_eclass and \
1967 atom.cp == "x11-libs/wxGTK":
1968 stats['wxwidgets.eclassnotused'] += 1
1969 fails['wxwidgets.eclassnotused'].append(
1970 (relative_path + ": %ss on x11-libs/wxGTK"
1971 " without inheriting wxwidgets.eclass") % mytype)
1973 if not is_blocker and \
1974 atom.cp in suspect_rdepend:
1975 stats[mytype + '.suspect'] += 1
1976 fails[mytype + '.suspect'].append(
1977 relative_path + ": '%s'" % atom)
1979 if atom.operator == "~" and \
1980 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
1981 qacat = 'dependency.badtilde'
1983 fails[qacat].append(
1984 (relative_path + ": %s uses the ~ operator"
1985 " with a non-zero revision:" + \
1986 " '%s'") % (mytype, atom))
1988 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1990 for m,b in zip(type_list, badsyntax):
1991 if m.endswith("DEPEND"):
1992 qacat = "dependency.syntax"
1994 qacat = m + ".syntax"
1996 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
1998 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
1999 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2000 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2001 badlicsyntax = badlicsyntax > 0
2002 badprovsyntax = badprovsyntax > 0
2004 # uselist checks - global
2007 for myflag in myaux["IUSE"].split():
2008 flag_name = myflag.lstrip("+-")
2009 used_useflags.add(flag_name)
2010 if myflag != flag_name:
2011 default_use.append(myflag)
2012 if flag_name not in uselist:
2013 myuse.append(flag_name)
2015 # uselist checks - metadata
2016 for mypos in range(len(myuse)-1,-1,-1):
2017 if myuse[mypos] and (myuse[mypos] in muselist):
2020 if default_use and not eapi_has_iuse_defaults(eapi):
2021 for myflag in default_use:
2022 stats['EAPI.incompatible'] += 1
2023 fails['EAPI.incompatible'].append(
2024 (relative_path + ": IUSE defaults" + \
2025 " not supported with EAPI='%s':" + \
2026 " '%s'") % (eapi, myflag))
2028 for mypos in range(len(myuse)):
2029 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
2030 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
2033 if not badlicsyntax:
2034 # Parse the LICENSE variable, remove USE conditions and
2036 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2037 # Check each entry to ensure that it exists in PORTDIR's
2038 # license directory.
2039 for lic in licenses:
2040 # Need to check for "||" manually as no portage
2041 # function will remove it without removing values.
2042 if lic not in liclist and lic != "||":
2043 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
2044 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
2045 elif lic in liclist_deprecated:
2046 stats["LICENSE.deprecated"] = stats["LICENSE.deprecated"] + 1
2047 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2050 myuse = myaux["KEYWORDS"].split()
2052 if mykey not in ("-*", "*", "~*"):
2054 if myskey[:1] == "-":
2056 if myskey[:1] == "~":
2058 if myskey not in kwlist:
2059 stats["KEYWORDS.invalid"] += 1
2060 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
2061 elif myskey not in profiles:
2062 stats["KEYWORDS.invalid"] += 1
2063 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
2068 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2069 except portage.exception.InvalidDependString as e:
2070 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
2071 fails["RESTRICT.syntax"].append(
2072 "%s: RESTRICT: %s" % (relative_path, e))
2075 myrestrict = set(myrestrict)
2076 mybadrestrict = myrestrict.difference(valid_restrict)
2078 stats["RESTRICT.invalid"] += len(mybadrestrict)
2079 for mybad in mybadrestrict:
2080 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
2082 required_use = myaux["REQUIRED_USE"]
2084 if not eapi_has_required_use(eapi):
2085 stats['EAPI.incompatible'] += 1
2086 fails['EAPI.incompatible'].append(
2087 relative_path + ": REQUIRED_USE" + \
2088 " not supported with EAPI='%s'" % (eapi,))
2090 portage.dep.check_required_use(required_use, (),
2091 pkg.iuse.is_valid_flag, eapi=eapi)
2092 except portage.exception.InvalidDependString as e:
2093 stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
2094 fails["REQUIRED_USE.syntax"].append(
2095 "%s: REQUIRED_USE: %s" % (relative_path, e))
2099 relative_path = os.path.join(x, y + ".ebuild")
2100 full_path = os.path.join(repodir, relative_path)
2101 if not vcs_preserves_mtime:
2102 if ebuild_path not in new_ebuilds and \
2103 ebuild_path not in modified_ebuilds:
2106 # All ebuilds should have utf_8 encoding.
2107 f = io.open(_unicode_encode(full_path,
2108 encoding=_encodings['fs'], errors='strict'),
2109 mode='r', encoding=_encodings['repo.content'])
2111 for check_name, e in run_checks(f, pkg):
2112 stats[check_name] += 1
2113 fails[check_name].append(relative_path + ': %s' % e)
2116 except UnicodeDecodeError:
2117 # A file.UTF8 failure will have already been recorded above.
2121 # The dep_check() calls are the most expensive QA test. If --force
2122 # is enabled, there's no point in wasting time on these since the
2123 # user is intent on forcing the commit anyway.
2126 relevant_profiles = []
2127 for keyword,arch,groups in arches:
2129 if arch not in profiles:
2130 # A missing profile will create an error further down
2131 # during the KEYWORDS verification.
2133 relevant_profiles.extend((keyword, groups, prof)
2134 for prof in profiles[arch])
2137 return item[2].sub_path
2139 relevant_profiles.sort(key=sort_key)
2141 for keyword, groups, prof in relevant_profiles:
2143 if prof.status not in ("stable", "dev") or \
2144 prof.status == "dev" and not options.include_dev:
2147 dep_settings = arch_caches.get(prof.sub_path)
2148 if dep_settings is None:
2149 dep_settings = portage.config(
2150 config_profile_path=prof.abs_path,
2151 config_incrementals=repoman_incrementals,
2152 config_root=config_root,
2154 _unmatched_removal=options.unmatched_removal,
2156 dep_settings.categories = repoman_settings.categories
2157 if options.without_mask:
2158 dep_settings._mask_manager_obj = \
2159 copy.deepcopy(dep_settings._mask_manager)
2160 dep_settings._mask_manager._pmaskdict.clear()
2161 arch_caches[prof.sub_path] = dep_settings
2163 xmatch_cache_key = (prof.sub_path, tuple(groups))
2164 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2168 xcache = portdb.xcache
2169 xcache.update(shared_xmatch_caches)
2170 arch_xmatch_caches[xmatch_cache_key] = xcache
2172 trees[root]["porttree"].settings = dep_settings
2173 portdb.settings = dep_settings
2174 portdb.xcache = xcache
2175 # for package.use.mask support inside dep_check
2176 dep_settings.setcpv(pkg)
2177 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2178 # just in case, prevent config.reset() from nuking these.
2179 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2181 if not baddepsyntax:
2182 ismasked = not ebuild_archs or \
2183 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2185 if not have_pmasked:
2186 have_pmasked = bool(dep_settings._getMaskAtom(
2187 pkg.cpv, pkg._metadata))
2188 if options.ignore_masked:
2190 #we are testing deps for a masked package; give it some lee-way
2192 matchmode = "minimum-all"
2195 matchmode = "minimum-visible"
2197 if not have_dev_keywords:
2198 have_dev_keywords = \
2199 bool(dev_keywords.intersection(keywords))
2201 if prof.status == "dev":
2202 suffix=suffix+"indev"
2204 for mytype in Package._dep_keys:
2206 mykey = "dependency.bad" + suffix
2207 myvalue = myaux[mytype]
2211 success, atoms = portage.dep_check(myvalue, portdb,
2212 dep_settings, use="all", mode=matchmode,
2218 # Don't bother with dependency.unknown for
2219 # cases in which *DEPEND.bad is triggered.
2221 # dep_check returns all blockers and they
2222 # aren't counted for *DEPEND.bad, so we
2224 if not atom.blocker:
2225 unknown_pkgs.discard(
2226 (mytype, atom.unevaluated_atom))
2228 if not prof.sub_path:
2229 # old-style virtuals currently aren't
2230 # resolvable with empty profile, since
2231 # 'virtuals' mappings are unavailable
2232 # (it would be expensive to search
2233 # for PROVIDE in all ebuilds)
2234 atoms = [atom for atom in atoms if not \
2235 (atom.cp.startswith('virtual/') and \
2236 not portdb.cp_list(atom.cp))]
2238 #we have some unsolvable deps
2239 #remove ! deps, which always show up as unsatisfiable
2240 atoms = [str(atom.unevaluated_atom) \
2241 for atom in atoms if not atom.blocker]
2243 #if we emptied out our list, continue:
2246 stats[mykey]=stats[mykey]+1
2247 fails[mykey].append("%s: %s: %s(%s) %s" % \
2248 (relative_path, mytype, keyword,
2251 stats[mykey]=stats[mykey]+1
2252 fails[mykey].append("%s: %s: %s(%s) %s" % \
2253 (relative_path, mytype, keyword,
2256 if not baddepsyntax and unknown_pkgs:
2258 for mytype, atom in unknown_pkgs:
2259 type_map.setdefault(mytype, set()).add(atom)
2260 for mytype, atoms in type_map.items():
2261 stats["dependency.unknown"] += 1
2262 fails["dependency.unknown"].append("%s: %s: %s" %
2263 (relative_path, mytype, ", ".join(sorted(atoms))))
2265 # check if there are unused local USE-descriptions in metadata.xml
2266 # (unless there are any invalids, to avoid noise)
2268 for myflag in muselist.difference(used_useflags):
2269 stats["metadata.warning"] += 1
2270 fails["metadata.warning"].append(
2271 "%s/metadata.xml: unused local USE-description: '%s'" % \
2274 if options.if_modified == "y" and len(effective_scanlist) < 1:
2275 logging.warn("--if-modified is enabled, but no modified packages were found!")
2277 if options.mode == "manifest":
2280 #dofail will be set to 1 if we have failed in at least one non-warning category
2282 #dowarn will be set to 1 if we tripped any warnings
2284 #dofull will be set if we should print a "repoman full" informational message
2285 dofull = options.mode != 'full'
2291 if x not in qawarnings:
2295 (dowarn and not (options.quiet or options.mode == "scan")):
2298 # Save QA output so that it can be conveniently displayed
2299 # in $EDITOR while the user creates a commit message.
2300 # Otherwise, the user would not be able to see this output
2301 # once the editor has taken over the screen.
2302 qa_output = io.StringIO()
2303 style_file = ConsoleStyleFile(sys.stdout)
2304 if options.mode == 'commit' and \
2305 (not commitmessage or not commitmessage.strip()):
2306 style_file.write_listener = qa_output
2307 console_writer = StyleWriter(file=style_file, maxcol=9999)
2308 console_writer.style_listener = style_file.new_styles
2310 f = formatter.AbstractFormatter(console_writer)
2312 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2315 del console_writer, f, style_file
2316 qa_output = qa_output.getvalue()
2317 qa_output = qa_output.splitlines(True)
2319 def grouplist(mylist,seperator="/"):
2320 """(list,seperator="/") -- Takes a list of elements; groups them into
2321 same initial element categories. Returns a dict of {base:[sublist]}
2322 From: ["blah/foo","spork/spatula","blah/weee/splat"]
2323 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
2326 xs=x.split(seperator)
2329 if xs[0] not in mygroups:
2330 mygroups[xs[0]]=[seperator.join(xs[1:])]
2332 mygroups[xs[0]]+=[seperator.join(xs[1:])]
2335 suggest_ignore_masked = False
2336 suggest_include_dev = False
2338 if have_pmasked and not (options.without_mask or options.ignore_masked):
2339 suggest_ignore_masked = True
2340 if have_dev_keywords and not options.include_dev:
2341 suggest_include_dev = True
2343 if suggest_ignore_masked or suggest_include_dev:
2345 if suggest_ignore_masked:
2346 print(bold("Note: use --without-mask to check " + \
2347 "KEYWORDS on dependencies of masked packages"))
2349 if suggest_include_dev:
2350 print(bold("Note: use --include-dev (-d) to check " + \
2351 "dependencies for 'dev' profiles"))
2354 if options.mode != 'commit':
2356 print(bold("Note: type \"repoman full\" for a complete listing."))
2357 if dowarn and not dofail:
2358 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.\"")
2360 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2362 print(bad("Please fix these important QA issues first."))
2363 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2366 if dofail and can_force and options.force and not options.pretend:
2367 print(green("RepoMan sez:") + \
2368 " \"You want to commit even with these QA issues?\n" + \
2369 " I'll take it this time, but I'm not happy.\"\n")
2371 if options.force and not can_force:
2372 print(bad("The --force option has been disabled due to extraordinary issues."))
2373 print(bad("Please fix these important QA issues first."))
2374 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2378 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2383 myvcstree=portage.cvstree.getentries("./",recursive=1)
2384 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
2385 except SystemExit as e:
2386 raise # TODO propagate this
2388 err("Error retrieving CVS tree; exiting.")
2391 with repoman_popen("svn status --no-ignore") as f:
2392 svnstatus = f.readlines()
2393 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
2394 except SystemExit as e:
2395 raise # TODO propagate this
2397 err("Error retrieving SVN info; exiting.")
2399 # get list of files not under version control or missing
2400 myf = repoman_popen("git ls-files --others")
2401 myunadded = [ "./" + elem[:-1] for elem in myf ]
2405 with repoman_popen("bzr status -S .") as f:
2406 bzrstatus = f.readlines()
2407 myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
2408 except SystemExit as e:
2409 raise # TODO propagate this
2411 err("Error retrieving bzr info; exiting.")
2413 with repoman_popen("hg status --no-status --unknown .") as f:
2414 myunadded = f.readlines()
2415 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2417 # Mercurial doesn't handle manually deleted files as removed from
2418 # the repository, so the user need to remove them before commit,
2419 # using "hg remove [FILES]"
2420 with repoman_popen("hg status --no-status --deleted .") as f:
2421 mydeleted = f.readlines()
2422 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2427 for x in range(len(myunadded)-1,-1,-1):
2428 xs=myunadded[x].split("/")
2430 print("!!! files dir is not added! Please correct this.")
2432 elif xs[-1]=="Manifest":
2433 # It's a manifest... auto add
2434 myautoadd+=[myunadded[x]]
2438 print(red("!!! The following files are in your local tree but are not added to the master"))
2439 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2446 if vcs == "hg" and mydeleted:
2447 print(red("!!! The following files are removed manually from your local tree but are not"))
2448 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2456 mycvstree = cvstree.getentries("./", recursive=1)
2457 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2458 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2459 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
2460 bin_blob_pattern = re.compile("^-kb$")
2461 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2462 recursive=1, basedir="./"))
2466 with repoman_popen("svn status") as f:
2467 svnstatus = f.readlines()
2468 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2469 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2470 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2472 # Subversion expands keywords specified in svn:keywords properties.
2473 with repoman_popen("svn propget -R svn:keywords") as f:
2474 props = f.readlines()
2475 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2476 for prop in props if " - " in prop)
2479 with repoman_popen("git diff-index --name-only "
2480 "--relative --diff-filter=M HEAD") as f:
2481 mychanged = f.readlines()
2482 mychanged = ["./" + elem[:-1] for elem in mychanged]
2484 with repoman_popen("git diff-index --name-only "
2485 "--relative --diff-filter=A HEAD") as f:
2486 mynew = f.readlines()
2487 mynew = ["./" + elem[:-1] for elem in mynew]
2489 with repoman_popen("git diff-index --name-only "
2490 "--relative --diff-filter=D HEAD") as f:
2491 myremoved = f.readlines()
2492 myremoved = ["./" + elem[:-1] for elem in myremoved]
2495 with repoman_popen("bzr status -S .") as f:
2496 bzrstatus = f.readlines()
2497 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
2498 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" ) ]
2499 myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
2500 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" ) ]
2501 # Bazaar expands nothing.
2504 with repoman_popen("hg status --no-status --modified .") as f:
2505 mychanged = f.readlines()
2506 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2508 with repoman_popen("hg status --no-status --added .") as f:
2509 mynew = f.readlines()
2510 mynew = ["./" + elem.rstrip() for elem in mynew]
2512 with repoman_popen("hg status --no-status --removed .") as f:
2513 myremoved = f.readlines()
2514 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2517 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2518 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2520 print("(Didn't find any changed files...)")
2524 # Manifests need to be regenerated after all other commits, so don't commit
2525 # them now even if they have changed.
2528 for f in mychanged + mynew:
2529 if "Manifest" == os.path.basename(f):
2533 myupdates.difference_update(myremoved)
2534 myupdates = list(myupdates)
2535 mymanifests = list(mymanifests)
2539 commitmessage = options.commitmsg
2540 if options.commitmsgfile:
2542 f = io.open(_unicode_encode(options.commitmsgfile,
2543 encoding=_encodings['fs'], errors='strict'),
2544 mode='r', encoding=_encodings['content'], errors='replace')
2545 commitmessage = f.read()
2548 except (IOError, OSError) as e:
2549 if e.errno == errno.ENOENT:
2550 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2553 # We've read the content so the file is no longer needed.
2554 commitmessagefile = None
2555 if not commitmessage or not commitmessage.strip():
2557 editor = os.environ.get("EDITOR")
2558 if editor and utilities.editor_is_executable(editor):
2559 commitmessage = utilities.get_commit_message_with_editor(
2560 editor, message=qa_output)
2562 commitmessage = utilities.get_commit_message_with_stdin()
2563 except KeyboardInterrupt:
2565 if not commitmessage or not commitmessage.strip():
2566 print("* no commit message? aborting commit.")
2568 commitmessage = commitmessage.rstrip()
2569 changelog_msg = commitmessage
2570 portage_version = getattr(portage, "VERSION", None)
2571 if portage_version is None:
2572 sys.stderr.write("Failed to insert portage version in message!\n")
2574 portage_version = "Unknown"
2577 # Use new footer only for git (see bug #438364).
2578 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2580 commit_footer += "\nRepoMan-Options: --force"
2582 commit_footer += "\nManifest-Sign-Key: %s" % \
2583 repoman_settings.get("PORTAGE_GPG_KEY", "")
2585 unameout = platform.system() + " "
2586 if platform.system() in ["Darwin", "SunOS"]:
2587 unameout += platform.processor()
2589 unameout += platform.machine()
2590 commit_footer = "\n\n(Portage version: %s/%s/%s" % \
2591 (portage_version, vcs, unameout)
2593 commit_footer += ", RepoMan options: --force"
2595 commit_footer += ", signed Manifest commit with key %s" % \
2596 repoman_settings.get("PORTAGE_GPG_KEY", "")
2598 commit_footer += ", unsigned Manifest commit"
2599 commit_footer += ")"
2601 commitmessage += commit_footer
2603 if options.echangelog in ('y', 'force'):
2604 logging.info("checking for unmodified ChangeLog files")
2605 committer_name = utilities.get_committer_name(env=repoman_settings)
2606 for x in sorted(vcs_files_to_cps(
2607 chain(myupdates, mymanifests, myremoved))):
2608 catdir, pkgdir = x.split("/")
2609 checkdir = repodir + "/" + x
2610 checkdir_relative = ""
2612 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2614 checkdir_relative = os.path.join(catdir, checkdir_relative)
2615 checkdir_relative = os.path.join(".", checkdir_relative)
2617 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2618 changelog_modified = changelog_path in modified_changelogs
2619 if changelog_modified and options.echangelog != 'force':
2622 # get changes for this package
2623 cdrlen = len(checkdir_relative)
2624 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2625 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2626 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2628 # Skip ChangeLog generation if only the Manifest was modified,
2629 # as discussed in bug #398009.
2630 nontrivial_cl_files = set()
2631 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2632 nontrivial_cl_files.difference_update(['Manifest'])
2633 if not nontrivial_cl_files and options.echangelog != 'force':
2636 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2637 committer_name, changelog_msg,
2638 os.path.join(repodir, 'skel.ChangeLog'),
2640 new=clnew, removed=clremoved, changed=clchanged,
2641 pretend=options.pretend)
2642 if new_changelog is None:
2643 writemsg_level("!!! Updating the ChangeLog failed\n", \
2644 level=logging.ERROR, noiselevel=-1)
2647 # if the ChangeLog was just created, add it to vcs
2649 myautoadd.append(changelog_path)
2650 # myautoadd is appended to myupdates below
2652 myupdates.append(changelog_path)
2654 if options.ask and not options.pretend:
2655 # regenerate Manifest for modified ChangeLog (bug #420735)
2656 repoman_settings["O"] = checkdir
2657 digestgen(mysettings=repoman_settings, myportdb=portdb)
2660 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2661 add_cmd = [vcs, "add"]
2662 add_cmd += myautoadd
2664 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2667 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2668 # Python 3.1 produces the following TypeError if raw bytes are
2669 # passed to subprocess.call():
2670 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2671 # errread, errwrite)
2672 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2673 # raise child_exception
2674 # TypeError: expected an object with the buffer interface
2675 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2676 retcode = subprocess.call(add_cmd)
2677 if retcode != os.EX_OK:
2679 "Exiting on %s error code: %s\n" % (vcs, retcode))
2682 myupdates += myautoadd
2684 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2686 if vcs not in ('cvs', 'svn'):
2687 # With git, bzr and hg, there's never any keyword expansion, so
2688 # there's no need to regenerate manifests and all files will be
2689 # committed in one big commit at the end.
2691 elif not repo_config.thin_manifest:
2693 headerstring = "'\$(Header|Id).*\$'"
2695 svn_keywords = dict((k.lower(), k) for k in [
2698 "LastChangedRevision",
2709 for myfile in myupdates:
2711 # for CVS, no_expansion contains files that are excluded from expansion
2713 if myfile in no_expansion:
2716 # for SVN, expansion contains files that are included in expansion
2718 if myfile not in expansion:
2721 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2722 enabled_keywords = []
2723 for k in expansion[myfile]:
2724 keyword = svn_keywords.get(k.lower())
2725 if keyword is not None:
2726 enabled_keywords.append(keyword)
2728 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2730 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2731 portage._shell_quote(myfile))
2733 myheaders.append(myfile)
2735 print("%s have headers that will change." % green(str(len(myheaders))))
2736 print("* Files with headers will cause the manifests to be changed and committed separately.")
2738 logging.info("myupdates: %s", myupdates)
2739 logging.info("myheaders: %s", myheaders)
2741 if options.ask and userquery('Commit changes?', True) != 'Yes':
2742 print("* aborting commit.")
2743 sys.exit(128 + signal.SIGINT)
2745 # Handle the case where committed files have keywords which
2746 # will change and need a priming commit before the Manifest
2748 if (myupdates or myremoved) and myheaders:
2749 myfiles = myupdates + myremoved
2750 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2751 mymsg = os.fdopen(fd, "wb")
2752 mymsg.write(_unicode_encode(commitmessage))
2756 print(green("Using commit message:"))
2757 print(green("------------------------------------------------------------------------------"))
2758 print(commitmessage)
2759 print(green("------------------------------------------------------------------------------"))
2762 # Having a leading ./ prefix on file paths can trigger a bug in
2763 # the cvs server when committing files to multiple directories,
2764 # so strip the prefix.
2765 myfiles = [f.lstrip("./") for f in myfiles]
2768 commit_cmd.extend(vcs_global_opts)
2769 commit_cmd.append("commit")
2770 commit_cmd.extend(vcs_local_opts)
2771 commit_cmd.extend(["-F", commitmessagefile])
2772 commit_cmd.extend(myfiles)
2776 print("(%s)" % (" ".join(commit_cmd),))
2778 retval = spawn(commit_cmd, env=os.environ)
2779 if retval != os.EX_OK:
2780 writemsg_level(("!!! Exiting on %s (shell) " + \
2781 "error code: %s\n") % (vcs, retval),
2782 level=logging.ERROR, noiselevel=-1)
2786 os.unlink(commitmessagefile)
2790 # Setup the GPG commands
2791 def gpgsign(filename):
2792 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2794 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2795 " Is make.globals missing?")
2796 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2797 "PORTAGE_GPG_KEY" not in repoman_settings:
2798 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2799 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2800 if "PORTAGE_GPG_DIR" not in repoman_settings:
2801 repoman_settings["PORTAGE_GPG_DIR"] = \
2802 os.path.expanduser("~/.gnupg")
2803 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2804 % repoman_settings["PORTAGE_GPG_DIR"])
2806 repoman_settings["PORTAGE_GPG_DIR"] = \
2807 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2808 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2809 raise portage.exception.InvalidLocation(
2810 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2811 repoman_settings["PORTAGE_GPG_DIR"])
2812 gpgvars = {"FILE": filename}
2813 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2814 v = repoman_settings.get(k)
2817 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2819 print("("+gpgcmd+")")
2821 # Encode unicode manually for bug #310789.
2822 gpgcmd = portage.util.shlex_split(gpgcmd)
2823 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
2824 # Python 3.1 does not support bytes in Popen args.
2825 gpgcmd = [_unicode_encode(arg,
2826 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2827 rValue = subprocess.call(gpgcmd)
2828 if rValue == os.EX_OK:
2829 os.rename(filename+".asc", filename)
2831 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2833 def need_signature(filename):
2835 with open(_unicode_encode(filename,
2836 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2837 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2838 except IOError as e:
2839 if e.errno in (errno.ENOENT, errno.ESTALE):
2843 # When files are removed and re-added, the cvs server will put /Attic/
2844 # inside the $Header path. This code detects the problem and corrects it
2845 # so that the Manifest will generate correctly. See bug #169500.
2846 # Use binary mode in order to avoid potential character encoding issues.
2847 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2848 attic_str = b'/Attic/'
2849 attic_replace = b'/'
2851 f = open(_unicode_encode(x,
2852 encoding=_encodings['fs'], errors='strict'),
2854 mylines = f.readlines()
2857 for i, line in enumerate(mylines):
2858 if cvs_header_re.match(line) is not None and \
2860 mylines[i] = line.replace(attic_str, attic_replace)
2863 portage.util.write_atomic(x, b''.join(mylines),
2867 print(green("RepoMan sez:"), "\"You're rather crazy... "
2868 "doing the entire repository.\"\n")
2870 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2872 for x in sorted(vcs_files_to_cps(
2873 chain(myupdates, myremoved, mymanifests))):
2874 repoman_settings["O"] = os.path.join(repodir, x)
2875 digestgen(mysettings=repoman_settings, myportdb=portdb)
2881 for x in sorted(vcs_files_to_cps(
2882 chain(myupdates, myremoved, mymanifests))):
2883 repoman_settings["O"] = os.path.join(repodir, x)
2884 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
2885 if not need_signature(manifest_path):
2887 gpgsign(manifest_path)
2888 except portage.exception.PortageException as e:
2889 portage.writemsg("!!! %s\n" % str(e))
2890 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2894 # It's not safe to use the git commit -a option since there might
2895 # be some modified files elsewhere in the working tree that the
2896 # user doesn't want to commit. Therefore, call git update-index
2897 # in order to ensure that the index is updated with the latest
2898 # versions of all new and modified files in the relevant portion
2899 # of the working tree.
2900 myfiles = mymanifests + myupdates
2902 update_index_cmd = ["git", "update-index"]
2903 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2905 print("(%s)" % (" ".join(update_index_cmd),))
2907 retval = spawn(update_index_cmd, env=os.environ)
2908 if retval != os.EX_OK:
2909 writemsg_level(("!!! Exiting on %s (shell) " + \
2910 "error code: %s\n") % (vcs, retval),
2911 level=logging.ERROR, noiselevel=-1)
2916 myfiles = mymanifests[:]
2917 # If there are no header (SVN/CVS keywords) changes in
2918 # the files, this Manifest commit must include the
2919 # other (yet uncommitted) files.
2921 myfiles += myupdates
2922 myfiles += myremoved
2925 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2926 mymsg = os.fdopen(fd, "wb")
2927 mymsg.write(_unicode_encode(commitmessage))
2931 if options.pretend and vcs is None:
2932 # substitute a bogus value for pretend output
2933 commit_cmd.append("cvs")
2935 commit_cmd.append(vcs)
2936 commit_cmd.extend(vcs_global_opts)
2937 commit_cmd.append("commit")
2938 commit_cmd.extend(vcs_local_opts)
2940 commit_cmd.extend(["--logfile", commitmessagefile])
2941 commit_cmd.extend(myfiles)
2943 commit_cmd.extend(["-F", commitmessagefile])
2944 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2948 print("(%s)" % (" ".join(commit_cmd),))
2950 retval = spawn(commit_cmd, env=os.environ)
2951 if retval != os.EX_OK:
2953 if repo_config.sign_commit and vcs == 'git' and \
2954 not git_supports_gpg_sign():
2955 # Inform user that newer git is needed (bug #403323).
2957 "Git >=1.7.9 is required for signed commits!")
2959 writemsg_level(("!!! Exiting on %s (shell) " + \
2960 "error code: %s\n") % (vcs, retval),
2961 level=logging.ERROR, noiselevel=-1)
2965 os.unlink(commitmessagefile)
2971 print("Commit complete.")
2973 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
2974 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")