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 its 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('--include-arches', dest='include_arches',
239 metavar='ARCHES', action='append',
240 help='A space separated list of arches used to '
241 'filter the selection of profiles for dependency checks')
243 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
244 default=False, help='include dev profiles in dependency checks')
246 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
247 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
249 parser.add_option('--without-mask', dest='without_mask', action='store_true',
250 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
252 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
253 help='specify which mode repoman will run in (default=full)')
255 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
258 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
260 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
262 sorted_qa = list(qahelp)
265 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
267 opts, args = parser.parse_args(argv[1:])
269 if not opts.ignore_default_opts:
270 default_opts = repoman_settings.get("REPOMAN_DEFAULT_OPTS", "").split()
272 opts, args = parser.parse_args(default_opts + sys.argv[1:])
274 if opts.mode == 'help':
275 parser.print_help(short=False)
283 parser.error("invalid mode: %s" % arg)
288 if opts.mode == 'ci':
289 opts.mode = 'commit' # backwards compat shortcut
291 if opts.mode == 'commit' and not (opts.force or opts.pretend):
292 if opts.ignore_masked:
293 parser.error('Commit mode and --ignore-masked are not compatible')
294 if opts.without_mask:
295 parser.error('Commit mode and --without-mask are not compatible')
297 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
298 for val in range(opts.verbosity):
299 logger = logging.getLogger()
300 logger.setLevel(logger.getEffectiveLevel() - 10)
302 for val in range(opts.quiet):
303 logger = logging.getLogger()
304 logger.setLevel(logger.getEffectiveLevel() + 10)
309 "CVS/Entries.IO_error": "Attempting to commit, and an IO error was encountered access the Entries file",
310 "ebuild.invalidname": "Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
311 "ebuild.namenomatch": "Ebuild files that do not have the same name as their parent directory",
312 "changelog.ebuildadded": "An ebuild was added but the ChangeLog was not modified",
313 "changelog.missing": "Missing ChangeLog files",
314 "ebuild.notadded": "Ebuilds that exist but have not been added to cvs",
315 "ebuild.patches": "PATCHES variable should be a bash array to ensure white space safety",
316 "changelog.notadded": "ChangeLogs that exist but have not been added to cvs",
317 "dependency.bad": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds)",
318 "dependency.badmasked": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds)",
319 "dependency.badindev": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds) in developing arch",
320 "dependency.badmaskedindev": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds) in developing arch",
321 "dependency.badtilde": "Uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
322 "dependency.syntax": "Syntax error in dependency string (usually an extra/missing space/parenthesis)",
323 "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)",
324 "file.executable": "Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
325 "file.size": "Files in the files directory must be under 20 KiB",
326 "file.size.fatal": "Files in the files directory must be under 60 KiB",
327 "file.name": "File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
328 "file.UTF8": "File is not UTF8 compliant",
329 "inherit.deprecated": "Ebuild inherits a deprecated eclass",
330 "inherit.missing": "Ebuild uses functions from an eclass but does not inherit it",
331 "inherit.unused": "Ebuild inherits an eclass but does not use it",
332 "java.eclassesnotused": "With virtual/jdk in DEPEND you must inherit a java eclass",
333 "wxwidgets.eclassnotused": "Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
334 "KEYWORDS.dropped": "Ebuilds that appear to have dropped KEYWORDS for some arch",
335 "KEYWORDS.missing": "Ebuilds that have a missing or empty KEYWORDS variable",
336 "KEYWORDS.stable": "Ebuilds that have been added directly with stable KEYWORDS",
337 "KEYWORDS.stupid": "Ebuilds that use KEYWORDS=-* instead of package.mask",
338 "LICENSE.missing": "Ebuilds that have a missing or empty LICENSE variable",
339 "LICENSE.virtual": "Virtuals that have a non-empty LICENSE variable",
340 "DESCRIPTION.missing": "Ebuilds that have a missing or empty DESCRIPTION variable",
341 "DESCRIPTION.toolong": "DESCRIPTION is over %d characters" % max_desc_len,
342 "EAPI.definition": "EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
343 "EAPI.deprecated": "Ebuilds that use features that are deprecated in the current EAPI",
344 "EAPI.incompatible": "Ebuilds that use features that are only available with a different EAPI",
345 "EAPI.unsupported": "Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
346 "SLOT.invalid": "Ebuilds that have a missing or invalid SLOT variable value",
347 "HOMEPAGE.missing": "Ebuilds that have a missing or empty HOMEPAGE variable",
348 "HOMEPAGE.virtual": "Virtuals that have a non-empty HOMEPAGE variable",
349 "PDEPEND.suspect": "PDEPEND contains a package that usually only belongs in DEPEND.",
350 "LICENSE.syntax": "Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
351 "PROVIDE.syntax": "Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
352 "PROPERTIES.syntax": "Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
353 "RESTRICT.syntax": "Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
354 "REQUIRED_USE.syntax": "Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
355 "SRC_URI.syntax": "Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
356 "SRC_URI.mirror": "A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
357 "ebuild.syntax": "Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
358 "ebuild.output": "A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
359 "ebuild.nesteddie": "Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
360 "variable.invalidchar": "A variable contains an invalid character that is not part of the ASCII character set",
361 "variable.readonly": "Assigning a readonly variable",
362 "variable.usedwithhelpers": "Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
363 "LIVEVCS.stable": "This ebuild is a live checkout from a VCS but has stable keywords.",
364 "LIVEVCS.unmasked": "This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
365 "IUSE.invalid": "This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
366 "IUSE.missing": "This ebuild has a USE conditional which references a flag that is not listed in IUSE",
367 "LICENSE.invalid": "This ebuild is listing a license that doesnt exist in portages license/ dir.",
368 "LICENSE.deprecated": "This ebuild is listing a deprecated license.",
369 "KEYWORDS.invalid": "This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
370 "RDEPEND.implicit": "RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
371 "RDEPEND.suspect": "RDEPEND contains a package that usually only belongs in DEPEND.",
372 "RESTRICT.invalid": "This ebuild contains invalid RESTRICT values.",
373 "digest.assumed": "Existing digest must be assumed correct (Package level only)",
374 "digest.missing": "Some files listed in SRC_URI aren't referenced in the Manifest",
375 "digest.unused": "Some files listed in the Manifest aren't referenced in SRC_URI",
376 "ebuild.majorsyn": "This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
377 "ebuild.minorsyn": "This ebuild has a minor syntax error that contravenes gentoo coding style",
378 "ebuild.badheader": "This ebuild has a malformed header",
379 "manifest.bad": "Manifest has missing or incorrect digests",
380 "metadata.missing": "Missing metadata.xml files",
381 "metadata.bad": "Bad metadata.xml files",
382 "metadata.warning": "Warnings in metadata.xml files",
383 "portage.internal": "The ebuild uses an internal Portage function or variable",
384 "virtual.oldstyle": "The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
385 "virtual.suspect": "Ebuild contains a package that usually should be pulled via virtual/, not directly.",
386 "usage.obsolete": "The ebuild makes use of an obsolete construct",
387 "upstream.workaround": "The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
390 qacats = list(qahelp)
395 "changelog.notadded",
396 "dependency.unknown",
401 "dependency.badmasked",
402 "dependency.badindev",
403 "dependency.badmaskedindev",
404 "dependency.badtilde",
405 "DESCRIPTION.toolong",
408 "LICENSE.deprecated",
423 "inherit.deprecated",
424 "java.eclassesnotused",
425 "wxwidgets.eclassnotused",
429 "upstream.workaround",
434 if portage.const._ENABLE_INHERIT_CHECK:
435 # This is experimental, so it's non-fatal.
436 qawarnings.add("inherit.missing")
438 non_ascii_re = re.compile(r'[^\x00-\x7f]')
440 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
441 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
442 allvars.update(Package.metadata_keys)
443 allvars = sorted(allvars)
445 for x in missingvars:
448 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
452 valid_restrict = frozenset(["binchecks", "bindist",
453 "fetch", "installsources", "mirror", "preserve-libs",
454 "primaryuri", "splitdebug", "strip", "test", "userpriv"])
456 live_eclasses = frozenset([
467 suspect_rdepend = frozenset([
468 "app-arch/cabextract",
469 "app-arch/rpm2targz",
474 "dev-perl/extutils-pkgconfig",
480 "dev-util/gtk-doc-am",
483 "dev-util/pkg-config-lite",
485 "dev-util/pkgconfig",
486 "dev-util/pkgconfig-openbsd",
490 "media-gfx/ebdftopcf",
492 "sys-devel/autoconf",
493 "sys-devel/automake",
500 "virtual/linux-sources",
507 "dev-util/pkg-config-lite":"virtual/pkgconfig",
508 "dev-util/pkgconf":"virtual/pkgconfig",
509 "dev-util/pkgconfig":"virtual/pkgconfig",
510 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
511 "dev-libs/libusb":"virtual/libusb",
512 "dev-libs/libusbx":"virtual/libusb",
513 "dev-libs/libusb-compat":"virtual/libusb",
516 metadata_xml_encoding = 'UTF-8'
517 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
518 (metadata_xml_encoding,)
519 metadata_doctype_name = 'pkgmetadata'
520 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
521 # force refetch if the local copy creation time is older than this
522 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
525 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
527 options, arguments = ParseArgs(sys.argv, qahelp)
530 print("Portage", portage.VERSION)
533 # Set this to False when an extraordinary issue (generally
534 # something other than a QA issue) makes it impossible to
535 # commit (like if Manifest generation fails).
538 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
542 myreporoot = os.path.basename(portdir_overlay)
543 myreporoot += mydir[len(portdir_overlay):]
546 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
551 vcses = utilities.FindVCS()
553 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
554 print(red('*** Please either clean up your workdir or specify --vcs option.'))
561 if options.if_modified == "y" and vcs is None:
562 logging.info("Not in a version controlled repository; "
563 "disabling --if-modified.")
564 options.if_modified = "n"
566 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
567 vcs_preserves_mtime = vcs in ('cvs',)
569 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
570 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
571 if vcs_global_opts is None:
572 if vcs in ('cvs', 'svn'):
573 vcs_global_opts = "-q"
576 vcs_global_opts = vcs_global_opts.split()
578 if options.mode == 'commit' and not options.pretend and not vcs:
579 logging.info("Not in a version controlled repository; enabling pretend mode.")
580 options.pretend = True
582 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
583 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
584 (repoman_settings.get('PORTDIR_OVERLAY', ''),
585 portage._shell_quote(portdir_overlay))
586 # We have to call the config constructor again so
587 # that config.repositories is initialized correctly.
588 repoman_settings = portage.config(config_root=config_root, local_config=False,
589 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
591 root = repoman_settings['EROOT']
593 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
595 portdb = trees[root]['porttree'].dbapi
597 # Constrain dependency resolution to the master(s)
598 # that are specified in layout.conf.
599 repodir = os.path.realpath(portdir_overlay)
600 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
601 portdb.porttrees = list(repo_config.eclass_db.porttrees)
602 portdir = portdb.porttrees[0]
603 commit_env = os.environ.copy()
605 if repo_config.allow_provide_virtual:
606 qawarnings.add("virtual.oldstyle")
608 if repo_config.sign_commit:
610 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
611 # the commit arguments. If key_id is unspecified, then it must be
612 # configured by `git config user.signingkey key_id`.
613 vcs_local_opts.append("--gpg-sign")
614 if repoman_settings.get("PORTAGE_GPG_DIR"):
615 # Pass GNUPGHOME to git for bug #462362.
616 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
618 # In order to disable manifest signatures, repos may set
619 # "sign-manifests = false" in metadata/layout.conf. This
620 # can be used to prevent merge conflicts like those that
621 # thin-manifests is designed to prevent.
622 sign_manifests = "sign" in repoman_settings.features and \
623 repo_config.sign_manifest
625 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
626 options.mode in ("commit",) and not sign_manifests:
627 msg = ("The '%s' repository has manifest signatures enabled, "
628 "but FEATURES=sign is currently disabled. In order to avoid this "
629 "warning, enable FEATURES=sign in make.conf. Alternatively, "
630 "repositories can disable manifest signatures by setting "
631 "'sign-manifests = false' in metadata/layout.conf.") % \
633 for line in textwrap.wrap(msg, 60):
636 if sign_manifests and options.mode in ("commit",) and \
637 repoman_settings.get("PORTAGE_GPG_KEY") and \
638 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
639 repoman_settings["PORTAGE_GPG_KEY"]) is None:
640 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
641 repoman_settings["PORTAGE_GPG_KEY"])
644 manifest_hashes = repo_config.manifest_hashes
645 if manifest_hashes is None:
646 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
648 if options.mode in ("commit", "fix", "manifest"):
649 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
650 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
651 "metadata/layout.conf does not contain the '%s' hash which "
652 "is required by this portage version. You will have to "
653 "upgrade portage if you want to generate valid manifests for "
654 "this repository.") % \
655 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
656 for line in textwrap.wrap(msg, 70):
660 unsupported_hashes = manifest_hashes.difference(
661 portage.const.MANIFEST2_HASH_FUNCTIONS)
662 if unsupported_hashes:
663 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
664 "metadata/layout.conf contains one or more hash types '%s' "
665 "which are not supported by this portage version. You will "
666 "have to upgrade portage if you want to generate valid "
667 "manifests for this repository.") % \
668 (repo_config.name, " ".join(sorted(unsupported_hashes)))
669 for line in textwrap.wrap(msg, 70):
673 if options.echangelog is None and repo_config.update_changelog:
674 options.echangelog = 'y'
677 options.echangelog = 'n'
679 # The --echangelog option causes automatic ChangeLog generation,
680 # which invalidates changelog.ebuildadded and changelog.missing
682 # Note: Some don't use ChangeLogs in distributed SCMs.
683 # It will be generated on server side from scm log,
684 # before package moves to the rsync server.
685 # This is needed because they try to avoid merge collisions.
686 # Gentoo's Council decided to always use the ChangeLog file.
687 # TODO: shouldn't this just be switched on the repo, iso the VCS?
688 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
690 if 'digest' in repoman_settings.features and options.digest != 'n':
693 logging.debug("vcs: %s" % (vcs,))
694 logging.debug("repo config: %s" % (repo_config,))
695 logging.debug("options: %s" % (options,))
697 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
698 # profile-specific config constructor calls.
699 env = os.environ.copy()
700 env['PORTDIR'] = portdir
701 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
703 logging.info('Setting paths:')
704 logging.info('PORTDIR = "' + portdir + '"')
705 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
707 # It's confusing if these warnings are displayed without the user
708 # being told which profile they come from, so disable them.
709 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
712 for path in repo_config.eclass_db.porttrees:
713 categories.extend(portage.util.grabfile(
714 os.path.join(path, 'profiles', 'categories')))
715 repoman_settings.categories = frozenset(
716 portage.util.stack_lists([categories], incremental=1))
717 categories = repoman_settings.categories
719 portdb.settings = repoman_settings
720 root_config = RootConfig(repoman_settings, trees[root], None)
721 # We really only need to cache the metadata that's necessary for visibility
722 # filtering. Anything else can be discarded to reduce memory consumption.
723 portdb._aux_cache_keys.clear()
724 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
726 reposplit = myreporoot.split(os.path.sep)
727 repolevel = len(reposplit)
729 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
730 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
731 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
732 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
733 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
734 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
735 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
737 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
739 # Make startdir relative to the canonical repodir, so that we can pass
740 # it to digestgen and it won't have to be canonicalized again.
744 startdir = normalize_path(mydir)
745 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
748 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.")
750 def repoman_getstatusoutput(cmd):
752 Implements an interface similar to getstatusoutput(), but with
753 customized unicode handling (see bug #310789) and without the shell.
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 stderr=subprocess.STDOUT)
763 output = portage._unicode_decode(proc.communicate()[0],
764 encoding=encoding, errors='strict')
765 if output and output[-1] == "\n":
766 # getstatusoutput strips one newline
768 return (proc.wait(), output)
770 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
772 Implements an interface similar to os.popen(), but with customized
773 unicode handling (see bug #310789) and without the shell.
776 __slots__ = ('_proc', '_stdout')
778 def __init__(self, cmd):
779 args = portage.util.shlex_split(cmd)
780 encoding = _encodings['fs']
781 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
782 # Python 3.1 does not support bytes in Popen args.
783 args = [_unicode_encode(x,
784 encoding=encoding, errors='strict') for x in args]
785 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
786 object.__setattr__(self, '_proc', proc)
787 object.__setattr__(self, '_stdout',
788 codecs.getreader(encoding)(proc.stdout, 'strict'))
790 def _get_target(self):
791 return object.__getattribute__(self, '_stdout')
793 __enter__ = _get_target
795 def __exit__(self, exc_type, exc_value, traceback):
796 proc = object.__getattribute__(self, '_proc')
800 class ProfileDesc(object):
801 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
802 def __init__(self, arch, status, sub_path, tree_path):
806 sub_path = normalize_path(sub_path.lstrip(os.sep))
807 self.sub_path = sub_path
808 self.tree_path = tree_path
810 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
812 self.abs_path = tree_path
817 return 'empty profile'
820 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
822 # get lists of valid keywords, licenses, and use
826 global_pmasklines = []
828 for path in portdb.porttrees:
830 liclist.update(os.listdir(os.path.join(path, "licenses")))
833 kwlist.update(portage.grabfile(os.path.join(path,
834 "profiles", "arch.list")))
836 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
842 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
844 expand_list = os.listdir(expand_desc_dir)
848 for fn in expand_list:
849 if not fn[-5:] == '.desc':
851 use_prefix = fn[:-5].lower() + '_'
852 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
855 uselist.add(use_prefix + x[0])
857 global_pmasklines.append(portage.util.grabfile_package(
858 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
860 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
862 desc_file = io.open(_unicode_encode(desc_path,
863 encoding=_encodings['fs'], errors='strict'),
864 mode='r', encoding=_encodings['repo.content'], errors='replace')
865 except EnvironmentError:
868 for i, x in enumerate(desc_file):
875 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
876 desc_path + " line %d" % (i + 1, ))
877 elif arch[0] not in kwlist:
878 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
879 desc_path + " line %d" % (i + 1, ))
880 elif arch[2] not in valid_profile_types:
881 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
882 desc_path + " line %d" % (i + 1, ))
883 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
884 if not os.path.isdir(profile_desc.abs_path):
886 "Invalid %s profile (%s) for arch %s in %s line %d",
887 arch[2], arch[1], arch[0], desc_path, i + 1)
890 os.path.join(profile_desc.abs_path, 'deprecated')):
892 profile_list.append(profile_desc)
895 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
896 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
898 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
899 global_pmaskdict = {}
900 for x in global_pmasklines:
901 global_pmaskdict.setdefault(x.cp, []).append(x)
902 del global_pmasklines
904 def has_global_mask(pkg):
905 mask_atoms = global_pmaskdict.get(pkg.cp)
909 if portage.dep.match_from_list(x, pkg_list):
913 # Ensure that profile sub_path attributes are unique. Process in reverse order
914 # so that profiles with duplicate sub_path from overlays will override
915 # profiles with the same sub_path from parent repos.
917 profile_list.reverse()
918 profile_sub_paths = set()
919 for prof in profile_list:
920 if prof.sub_path in profile_sub_paths:
922 profile_sub_paths.add(prof.sub_path)
923 profiles.setdefault(prof.arch, []).append(prof)
925 # Use an empty profile for checking dependencies of
926 # packages that have empty KEYWORDS.
927 prof = ProfileDesc('**', 'stable', '', '')
928 profiles.setdefault(prof.arch, []).append(prof)
930 for x in repoman_settings.archlist():
933 if x not in profiles:
934 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
935 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
936 print(red("up with the " + x + " team."))
939 liclist_deprecated = set()
940 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
941 liclist_deprecated.update(
942 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
945 logging.fatal("Couldn't find licenses?")
949 logging.fatal("Couldn't read KEYWORDS from arch.list")
953 logging.fatal("Couldn't find use.desc?")
958 # we are inside a category directory
959 catdir = reposplit[-1]
960 if catdir not in categories:
962 mydirlist = os.listdir(startdir)
964 if x == "CVS" or x.startswith("."):
966 if os.path.isdir(startdir + "/" + x):
967 scanlist.append(catdir + "/" + x)
968 repo_subdir = catdir + os.sep
971 if not os.path.isdir(startdir + "/" + x):
973 for y in os.listdir(startdir + "/" + x):
974 if y == "CVS" or y.startswith("."):
976 if os.path.isdir(startdir + "/" + x + "/" + y):
977 scanlist.append(x + "/" + y)
980 catdir = reposplit[-2]
981 if catdir not in categories:
983 scanlist.append(catdir + "/" + reposplit[-1])
984 repo_subdir = scanlist[-1] + os.sep
986 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
987 ' from the current working directory'
988 logging.critical(msg)
991 repo_subdir_len = len(repo_subdir)
994 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
996 def vcs_files_to_cps(vcs_file_iter):
998 Iterate over the given modified file paths returned from the vcs,
999 and return a frozenset containing category/pn strings for each
1006 if reposplit[-2] in categories and \
1007 next(vcs_file_iter, None) is not None:
1008 modified_cps.append("/".join(reposplit[-2:]))
1010 elif repolevel == 2:
1011 category = reposplit[-1]
1012 if category in categories:
1013 for filename in vcs_file_iter:
1014 f_split = filename.split(os.sep)
1016 if len(f_split) > 2:
1017 modified_cps.append(category + "/" + f_split[1])
1021 for filename in vcs_file_iter:
1022 f_split = filename.split(os.sep)
1023 # ['.', category, pn, ...]
1024 if len(f_split) > 3 and f_split[1] in categories:
1025 modified_cps.append("/".join(f_split[1:3]))
1027 return frozenset(modified_cps)
1029 def git_supports_gpg_sign():
1030 status, cmd_output = \
1031 repoman_getstatusoutput("git --version")
1032 cmd_output = cmd_output.split()
1034 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1035 if version is not None:
1036 version = [int(x) for x in version.groups()]
1037 if version[0] > 1 or \
1038 (version[0] == 1 and version[1] > 7) or \
1039 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1043 def dev_keywords(profiles):
1045 Create a set of KEYWORDS values that exist in 'dev'
1046 profiles. These are used
1047 to trigger a message notifying the user when they might
1048 want to add the --include-dev option.
1051 for arch, arch_profiles in profiles.items():
1052 for prof in arch_profiles:
1053 arch_set = type_arch_map.get(prof.status)
1054 if arch_set is None:
1056 type_arch_map[prof.status] = arch_set
1059 dev_keywords = type_arch_map.get('dev', set())
1060 dev_keywords.update(['~' + arch for arch in dev_keywords])
1061 return frozenset(dev_keywords)
1063 dev_keywords = dev_keywords(profiles)
1072 xmllint_capable = False
1073 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1076 """Parse a RFC 822 date and time string.
1077 This is required for python3 compatibility, since the
1078 rfc822.parsedate() function is not available."""
1081 for x in s.upper().split():
1082 for y in x.split(','):
1086 if len(s_split) != 6:
1089 # %a, %d %b %Y %H:%M:%S %Z
1090 a, d, b, Y, H_M_S, Z = s_split
1092 # Convert month to integer, since strptime %w is locale-dependent.
1093 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
1094 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
1095 m = month_map.get(b)
1100 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
1102 def fetch_metadata_dtd():
1104 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1105 metadata_dtd_ctime_interval.
1107 @return: True if successful, otherwise False
1111 metadata_dtd_st = None
1112 current_time = int(time.time())
1114 metadata_dtd_st = os.stat(metadata_dtd)
1115 except EnvironmentError as e:
1116 if e.errno not in (errno.ENOENT, errno.ESTALE):
1120 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1121 if abs(current_time - metadata_dtd_st.st_ctime) \
1122 < metadata_dtd_ctime_interval:
1127 print(green("***") + " the local copy of metadata.dtd " + \
1128 "needs to be refetched, doing that now")
1131 url_f = urllib_request_urlopen(metadata_dtd_uri)
1132 msg_info = url_f.info()
1133 last_modified = msg_info.get('last-modified')
1134 if last_modified is not None:
1135 last_modified = parsedate(last_modified)
1136 if last_modified is not None:
1137 last_modified = calendar.timegm(last_modified)
1139 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
1141 local_f = open(metadata_dtd_tmp, mode='wb')
1142 local_f.write(url_f.read())
1144 if last_modified is not None:
1146 os.utime(metadata_dtd_tmp,
1147 (int(last_modified), int(last_modified)))
1149 # This fails on some odd non-unix-like filesystems.
1150 # We don't really need the mtime to be preserved
1151 # anyway here (currently we use ctime to trigger
1152 # fetch), so just ignore it.
1154 os.rename(metadata_dtd_tmp, metadata_dtd)
1157 os.unlink(metadata_dtd_tmp)
1163 except EnvironmentError as e:
1165 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
1166 print(red("!!!")+" exception '%s' though." % (e,))
1167 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
1172 if options.mode == "manifest":
1174 elif not find_binary('xmllint'):
1175 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1176 if options.xml_parse or repolevel == 3:
1177 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1180 if not fetch_metadata_dtd():
1182 # this can be problematic if xmllint changes their output
1183 xmllint_capable = True
1185 if options.mode == 'commit' and vcs:
1186 utilities.detect_vcs_conflicts(options, vcs)
1188 if options.mode == "manifest":
1190 elif options.pretend:
1191 print(green("\nRepoMan does a once-over of the neighborhood..."))
1193 print(green("\nRepoMan scours the neighborhood..."))
1196 modified_ebuilds = set()
1197 modified_changelogs = set()
1203 mycvstree = cvstree.getentries("./", recursive=1)
1204 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1205 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1206 if options.if_modified == "y":
1207 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1210 with repoman_popen("svn status") as f:
1211 svnstatus = f.readlines()
1212 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1213 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1214 if options.if_modified == "y":
1215 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1218 with repoman_popen("git diff-index --name-only "
1219 "--relative --diff-filter=M HEAD") as f:
1220 mychanged = f.readlines()
1221 mychanged = ["./" + elem[:-1] for elem in mychanged]
1223 with repoman_popen("git diff-index --name-only "
1224 "--relative --diff-filter=A HEAD") as f:
1225 mynew = f.readlines()
1226 mynew = ["./" + elem[:-1] for elem in mynew]
1227 if options.if_modified == "y":
1228 with repoman_popen("git diff-index --name-only "
1229 "--relative --diff-filter=D HEAD") as f:
1230 myremoved = f.readlines()
1231 myremoved = ["./" + elem[:-1] for elem in myremoved]
1234 with repoman_popen("bzr status -S .") as f:
1235 bzrstatus = f.readlines()
1236 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1237 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1238 if options.if_modified == "y":
1239 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")]
1242 with repoman_popen("hg status --no-status --modified .") as f:
1243 mychanged = f.readlines()
1244 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1245 with repoman_popen("hg status --no-status --added .") as f:
1246 mynew = f.readlines()
1247 mynew = ["./" + elem.rstrip() for elem in mynew]
1248 if options.if_modified == "y":
1249 with repoman_popen("hg status --no-status --removed .") as f:
1250 myremoved = f.readlines()
1251 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1254 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1255 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1256 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1257 if os.path.basename(x) == "ChangeLog")
1259 def vcs_new_changed(relative_path):
1260 for x in chain(mychanged, mynew):
1261 if x == relative_path:
1265 have_pmasked = False
1266 have_dev_keywords = False
1269 # NOTE: match-all caches are not shared due to potential
1270 # differences between profiles in _get_implicit_iuse.
1272 arch_xmatch_caches = {}
1273 shared_xmatch_caches = {"cp-list":{}}
1275 include_arches = None
1276 if options.include_arches:
1277 include_arches = set()
1278 include_arches.update(*[x.split() for x in options.include_arches])
1280 # Disable the "ebuild.notadded" check when not in commit mode and
1281 # running `svn status` in every package dir will be too expensive.
1283 check_ebuild_notadded = not \
1284 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1286 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1287 thirdpartymirrors = {}
1288 for k, v in repoman_settings.thirdpartymirrors().items():
1290 if not v.endswith("/"):
1292 thirdpartymirrors[v] = k
1294 class _XMLParser(xml.etree.ElementTree.XMLParser):
1296 def __init__(self, data, **kwargs):
1297 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1298 self._portage_data = data
1299 if hasattr(self, 'parser'):
1300 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1301 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1302 self._base_StartDoctypeDeclHandler = \
1303 self.parser.StartDoctypeDeclHandler
1304 self.parser.StartDoctypeDeclHandler = \
1305 self._portage_StartDoctypeDeclHandler
1307 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1308 if self._base_XmlDeclHandler is not None:
1309 self._base_XmlDeclHandler(version, encoding, standalone)
1310 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1312 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1313 has_internal_subset):
1314 if self._base_StartDoctypeDeclHandler is not None:
1315 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1316 has_internal_subset)
1317 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1319 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1321 Implements doctype() as required to avoid deprecation warnings with
1324 def doctype(self, name, pubid, system):
1328 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1329 except (EnvironmentError, ParseError, PermissionDenied) as e:
1331 except FileNotFound:
1332 # TODO: Download as we do for metadata.dtd, but add a way to
1333 # disable for non-gentoo repoman users who may not have herds.
1336 effective_scanlist = scanlist
1337 if options.if_modified == "y":
1338 effective_scanlist = sorted(vcs_files_to_cps(
1339 chain(mychanged, mynew, myremoved)))
1341 for x in effective_scanlist:
1342 #ebuilds and digests added to cvs respectively.
1343 logging.info("checking package %s" % x)
1344 # save memory by discarding xmatch caches from previous package(s)
1345 arch_xmatch_caches.clear()
1347 catdir, pkgdir = x.split("/")
1348 checkdir = repodir + "/" + x
1349 checkdir_relative = ""
1351 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1353 checkdir_relative = os.path.join(catdir, checkdir_relative)
1354 checkdir_relative = os.path.join(".", checkdir_relative)
1355 generated_manifest = False
1357 if options.mode == "manifest" or \
1358 (options.mode != 'manifest-check' and options.digest == 'y') or \
1359 options.mode in ('commit', 'fix') and not options.pretend:
1360 auto_assumed = set()
1361 fetchlist_dict = portage.FetchlistDict(checkdir,
1362 repoman_settings, portdb)
1363 if options.mode == 'manifest' and options.force:
1364 portage._doebuild_manifest_exempt_depend += 1
1366 distdir = repoman_settings['DISTDIR']
1367 mf = repoman_settings.repositories.get_repo_for_location(
1368 os.path.dirname(os.path.dirname(checkdir)))
1369 mf = mf.load_manifest(checkdir, distdir,
1370 fetchlist_dict=fetchlist_dict)
1371 mf.create(requiredDistfiles=None,
1372 assumeDistHashesAlways=True)
1373 for distfiles in fetchlist_dict.values():
1374 for distfile in distfiles:
1375 if os.path.isfile(os.path.join(distdir, distfile)):
1376 mf.fhashdict['DIST'].pop(distfile, None)
1378 auto_assumed.add(distfile)
1381 portage._doebuild_manifest_exempt_depend -= 1
1383 repoman_settings["O"] = checkdir
1385 generated_manifest = digestgen(
1386 mysettings=repoman_settings, myportdb=portdb)
1387 except portage.exception.PermissionDenied as e:
1388 generated_manifest = False
1389 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1390 level=logging.ERROR, noiselevel=-1)
1392 if not generated_manifest:
1393 print("Unable to generate manifest.")
1396 if options.mode == "manifest":
1397 if not dofail and options.force and auto_assumed and \
1398 'assume-digests' in repoman_settings.features:
1399 # Show which digests were assumed despite the --force option
1400 # being given. This output will already have been shown by
1401 # digestgen() if assume-digests is not enabled, so only show
1402 # it here if assume-digests is enabled.
1403 pkgs = list(fetchlist_dict)
1405 portage.writemsg_stdout(" digest.assumed" + \
1406 portage.output.colorize("WARN",
1407 str(len(auto_assumed)).rjust(18)) + "\n")
1409 fetchmap = fetchlist_dict[cpv]
1410 pf = portage.catsplit(cpv)[1]
1411 for distfile in sorted(fetchmap):
1412 if distfile in auto_assumed:
1413 portage.writemsg_stdout(
1414 " %s::%s\n" % (pf, distfile))
1419 if not generated_manifest:
1420 repoman_settings['O'] = checkdir
1421 repoman_settings['PORTAGE_QUIET'] = '1'
1422 if not portage.digestcheck([], repoman_settings, strict=1):
1423 stats["manifest.bad"] += 1
1424 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1425 repoman_settings.pop('PORTAGE_QUIET', None)
1427 if options.mode == 'manifest-check':
1430 checkdirlist = os.listdir(checkdir)
1434 for y in checkdirlist:
1435 if (y in no_exec or y.endswith(".ebuild")) and \
1436 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1437 stats["file.executable"] += 1
1438 fails["file.executable"].append(os.path.join(checkdir, y))
1439 if y.endswith(".ebuild"):
1441 ebuildlist.append(pf)
1442 cpv = "%s/%s" % (catdir, pf)
1444 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1447 stats["ebuild.syntax"] += 1
1448 fails["ebuild.syntax"].append(os.path.join(x, y))
1452 stats["ebuild.output"] += 1
1453 fails["ebuild.output"].append(os.path.join(x, y))
1455 if not portage.eapi_is_supported(myaux["EAPI"]):
1457 stats["EAPI.unsupported"] += 1
1458 fails["EAPI.unsupported"].append(os.path.join(x, y))
1460 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1461 root_config=root_config, type_name="ebuild")
1465 if len(pkgs) != len(ebuildlist):
1466 # If we can't access all the metadata then it's totally unsafe to
1467 # commit since there's no way to generate a correct Manifest.
1468 # Do not try to do any more QA checks on this package since missing
1469 # metadata leads to false positives for several checks, and false
1470 # positives confuse users.
1474 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1475 ebuildlist = sorted(pkgs.values())
1476 ebuildlist = [pkg.pf for pkg in ebuildlist]
1478 for y in checkdirlist:
1479 index = repo_config.find_invalid_path_char(y)
1481 y_relative = os.path.join(checkdir_relative, y)
1482 if vcs is not None and not vcs_new_changed(y_relative):
1483 # If the file isn't in the VCS new or changed set, then
1484 # assume that it's an irrelevant temporary file (Manifest
1485 # entries are not generated for file names containing
1486 # prohibited characters). See bug #406877.
1489 stats["file.name"] += 1
1490 fails["file.name"].append("%s/%s: char '%s'" % \
1491 (checkdir, y, y[index]))
1493 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1498 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1499 encoding=_encodings['fs'], errors='strict'),
1500 mode='r', encoding=_encodings['repo.content'])
1503 except UnicodeDecodeError as ue:
1504 stats["file.UTF8"] += 1
1505 s = ue.object[:ue.start]
1509 s = s[s.rfind("\n") + 1:]
1510 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1515 if vcs in ("git", "hg") and check_ebuild_notadded:
1517 myf = repoman_popen("git ls-files --others %s" % \
1518 (portage._shell_quote(checkdir_relative),))
1520 myf = repoman_popen("hg status --no-status --unknown %s" % \
1521 (portage._shell_quote(checkdir_relative),))
1523 if l[:-1][-7:] == ".ebuild":
1524 stats["ebuild.notadded"] += 1
1525 fails["ebuild.notadded"].append(
1526 os.path.join(x, os.path.basename(l[:-1])))
1529 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1532 myf = open(checkdir + "/CVS/Entries", "r")
1534 myf = repoman_popen("svn status --depth=files --verbose " +
1535 portage._shell_quote(checkdir))
1537 myf = repoman_popen("bzr ls -v --kind=file " +
1538 portage._shell_quote(checkdir))
1539 myl = myf.readlines()
1545 splitl = l[1:].split("/")
1548 if splitl[0][-7:] == ".ebuild":
1549 eadded.append(splitl[0][:-7])
1554 # tree conflict, new in subversion 1.6
1557 if l[-7:] == ".ebuild":
1558 eadded.append(os.path.basename(l[:-7]))
1563 if l[-7:] == ".ebuild":
1564 eadded.append(os.path.basename(l[:-7]))
1566 myf = repoman_popen("svn status " +
1567 portage._shell_quote(checkdir))
1568 myl = myf.readlines()
1572 l = l.rstrip().split(' ')[-1]
1573 if l[-7:] == ".ebuild":
1574 eadded.append(os.path.basename(l[:-7]))
1577 stats["CVS/Entries.IO_error"] += 1
1578 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1583 mf = repoman_settings.repositories.get_repo_for_location(
1584 os.path.dirname(os.path.dirname(checkdir)))
1585 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1586 mydigests = mf.getTypeDigests("DIST")
1588 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1590 src_uri_error = False
1591 for mykey in fetchlist_dict:
1593 myfiles_all.extend(fetchlist_dict[mykey])
1594 except portage.exception.InvalidDependString as e:
1595 src_uri_error = True
1597 portdb.aux_get(mykey, ["SRC_URI"])
1599 # This will be reported as an "ebuild.syntax" error.
1602 stats["SRC_URI.syntax"] += 1
1603 fails["SRC_URI.syntax"].append(
1604 "%s.ebuild SRC_URI: %s" % (mykey, e))
1606 if not src_uri_error:
1607 # This test can produce false positives if SRC_URI could not
1608 # be parsed for one or more ebuilds. There's no point in
1609 # producing a false error here since the root cause will
1610 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1611 # or "ebuild.sytax".
1612 myfiles_all = set(myfiles_all)
1613 for entry in mydigests:
1614 if entry not in myfiles_all:
1615 stats["digest.unused"] += 1
1616 fails["digest.unused"].append(checkdir + "::" + entry)
1617 for entry in myfiles_all:
1618 if entry not in mydigests:
1619 stats["digest.missing"] += 1
1620 fails["digest.missing"].append(checkdir + "::" + entry)
1623 if os.path.exists(checkdir + "/files"):
1624 filesdirlist = os.listdir(checkdir + "/files")
1626 # recurse through files directory
1627 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1629 y = filesdirlist.pop(0)
1630 relative_path = os.path.join(x, "files", y)
1631 full_path = os.path.join(repodir, relative_path)
1633 mystat = os.stat(full_path)
1634 except OSError as oe:
1636 # don't worry about it. it likely was removed via fix above.
1640 if S_ISDIR(mystat.st_mode):
1641 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1642 if y == "CVS" or y == ".svn":
1644 for z in os.listdir(checkdir + "/files/" + y):
1645 if z == "CVS" or z == ".svn":
1647 filesdirlist.append(y + "/" + z)
1648 # Current policy is no files over 20 KiB, these are the checks. File size between
1649 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1650 elif mystat.st_size > 61440:
1651 stats["file.size.fatal"] += 1
1652 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1653 elif mystat.st_size > 20480:
1654 stats["file.size"] += 1
1655 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1657 index = repo_config.find_invalid_path_char(y)
1659 y_relative = os.path.join(checkdir_relative, "files", y)
1660 if vcs is not None and not vcs_new_changed(y_relative):
1661 # If the file isn't in the VCS new or changed set, then
1662 # assume that it's an irrelevant temporary file (Manifest
1663 # entries are not generated for file names containing
1664 # prohibited characters). See bug #406877.
1667 stats["file.name"] += 1
1668 fails["file.name"].append("%s/files/%s: char '%s'" % \
1669 (checkdir, y, y[index]))
1672 if check_changelog and "ChangeLog" not in checkdirlist:
1673 stats["changelog.missing"] += 1
1674 fails["changelog.missing"].append(x + "/ChangeLog")
1677 # metadata.xml file check
1678 if "metadata.xml" not in checkdirlist:
1679 stats["metadata.missing"] += 1
1680 fails["metadata.missing"].append(x + "/metadata.xml")
1681 # metadata.xml parse check
1683 metadata_bad = False
1685 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1687 # read metadata.xml into memory
1689 _metadata_xml = xml.etree.ElementTree.parse(
1690 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1691 encoding=_encodings['fs'], errors='strict'),
1693 except (ExpatError, SyntaxError, EnvironmentError) as e:
1695 stats["metadata.bad"] += 1
1696 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1699 if not hasattr(xml_parser, 'parser') or \
1700 sys.hexversion < 0x2070000 or \
1701 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1702 # doctype is not parsed with python 2.6 or 3.1
1705 if "XML_DECLARATION" not in xml_info:
1706 stats["metadata.bad"] += 1
1707 fails["metadata.bad"].append("%s/metadata.xml: "
1708 "xml declaration is missing on first line, "
1709 "should be '%s'" % (x, metadata_xml_declaration))
1711 xml_version, xml_encoding, xml_standalone = \
1712 xml_info["XML_DECLARATION"]
1713 if xml_encoding is None or \
1714 xml_encoding.upper() != metadata_xml_encoding:
1715 stats["metadata.bad"] += 1
1716 if xml_encoding is None:
1717 encoding_problem = "but it is undefined"
1719 encoding_problem = "not '%s'" % xml_encoding
1720 fails["metadata.bad"].append("%s/metadata.xml: "
1721 "xml declaration encoding should be '%s', %s" %
1722 (x, metadata_xml_encoding, encoding_problem))
1724 if "DOCTYPE" not in xml_info:
1726 stats["metadata.bad"] += 1
1727 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1728 "DOCTYPE is missing"))
1730 doctype_name, doctype_system, doctype_pubid = \
1732 if doctype_system != metadata_dtd_uri:
1733 stats["metadata.bad"] += 1
1734 if doctype_system is None:
1735 system_problem = "but it is undefined"
1737 system_problem = "not '%s'" % doctype_system
1738 fails["metadata.bad"].append("%s/metadata.xml: "
1739 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1740 (x, metadata_dtd_uri, system_problem))
1742 if doctype_name != metadata_doctype_name:
1743 stats["metadata.bad"] += 1
1744 fails["metadata.bad"].append("%s/metadata.xml: "
1745 "DOCTYPE: name should be '%s', not '%s'" %
1746 (x, metadata_doctype_name, doctype_name))
1748 # load USE flags from metadata.xml
1750 musedict = utilities.parse_metadata_use(_metadata_xml)
1751 except portage.exception.ParseError as e:
1753 stats["metadata.bad"] += 1
1754 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1756 for atom in chain(*musedict.values()):
1761 except InvalidAtom as e:
1762 stats["metadata.bad"] += 1
1763 fails["metadata.bad"].append(
1764 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1767 stats["metadata.bad"] += 1
1768 fails["metadata.bad"].append(
1769 ("%s/metadata.xml: Atom contains "
1770 "unexpected cat/pn: %s") % (x, atom))
1772 # Run other metadata.xml checkers
1774 utilities.check_metadata(_metadata_xml, herd_base)
1775 except (utilities.UnknownHerdsError, ) as e:
1777 stats["metadata.bad"] += 1
1778 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1781 #Only carry out if in package directory or check forced
1782 if xmllint_capable and not metadata_bad:
1783 # xmlint can produce garbage output even on success, so only dump
1784 # the ouput when it fails.
1785 st, out = repoman_getstatusoutput(
1786 "xmllint --nonet --noout --dtdvalid %s %s" % \
1787 (portage._shell_quote(metadata_dtd),
1788 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1790 print(red("!!!") + " metadata.xml is invalid:")
1791 for z in out.splitlines():
1792 print(red("!!! ") + z)
1793 stats["metadata.bad"] += 1
1794 fails["metadata.bad"].append(x + "/metadata.xml")
1797 muselist = frozenset(musedict)
1799 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1800 changelog_modified = changelog_path in modified_changelogs
1802 # detect unused local USE-descriptions
1803 used_useflags = set()
1805 for y in ebuildlist:
1806 relative_path = os.path.join(x, y + ".ebuild")
1807 full_path = os.path.join(repodir, relative_path)
1808 ebuild_path = y + ".ebuild"
1810 ebuild_path = os.path.join(pkgdir, ebuild_path)
1812 ebuild_path = os.path.join(catdir, ebuild_path)
1813 ebuild_path = os.path.join(".", ebuild_path)
1814 if check_changelog and not changelog_modified \
1815 and ebuild_path in new_ebuilds:
1816 stats['changelog.ebuildadded'] += 1
1817 fails['changelog.ebuildadded'].append(relative_path)
1819 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1820 #ebuild not added to vcs
1821 stats["ebuild.notadded"] += 1
1822 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1823 myesplit = portage.pkgsplit(y)
1824 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1825 or pv_toolong_re.search(myesplit[1]) \
1826 or pv_toolong_re.search(myesplit[2]):
1827 stats["ebuild.invalidname"] += 1
1828 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1830 elif myesplit[0] != pkgdir:
1831 print(pkgdir, myesplit[0])
1832 stats["ebuild.namenomatch"] += 1
1833 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1840 for k, msgs in pkg.invalid.items():
1843 fails[k].append("%s: %s" % (relative_path, msg))
1846 myaux = pkg._metadata
1847 eapi = myaux["EAPI"]
1848 inherited = pkg.inherited
1849 live_ebuild = live_eclasses.intersection(inherited)
1851 for k, v in myaux.items():
1852 if not isinstance(v, basestring):
1854 m = non_ascii_re.search(v)
1856 stats["variable.invalidchar"] += 1
1857 fails["variable.invalidchar"].append(
1858 ("%s: %s variable contains non-ASCII " + \
1859 "character at position %s") % \
1860 (relative_path, k, m.start() + 1))
1862 if not src_uri_error:
1863 # Check that URIs don't reference a server from thirdpartymirrors.
1864 for uri in portage.dep.use_reduce( \
1865 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1866 contains_mirror = False
1867 for mirror, mirror_alias in thirdpartymirrors.items():
1868 if uri.startswith(mirror):
1869 contains_mirror = True
1871 if not contains_mirror:
1874 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1875 stats["SRC_URI.mirror"] += 1
1876 fails["SRC_URI.mirror"].append(
1877 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1878 (relative_path, mirror, new_uri))
1880 if myaux.get("PROVIDE"):
1881 stats["virtual.oldstyle"] += 1
1882 fails["virtual.oldstyle"].append(relative_path)
1884 for pos, missing_var in enumerate(missingvars):
1885 if not myaux.get(missing_var):
1886 if catdir == "virtual" and \
1887 missing_var in ("HOMEPAGE", "LICENSE"):
1889 if live_ebuild and missing_var == "KEYWORDS":
1891 myqakey = missingvars[pos] + ".missing"
1893 fails[myqakey].append(x + "/" + y + ".ebuild")
1895 if catdir == "virtual":
1896 for var in ("HOMEPAGE", "LICENSE"):
1898 myqakey = var + ".virtual"
1900 fails[myqakey].append(relative_path)
1902 # 14 is the length of DESCRIPTION=""
1903 if len(myaux['DESCRIPTION']) > max_desc_len:
1904 stats['DESCRIPTION.toolong'] += 1
1905 fails['DESCRIPTION.toolong'].append(
1906 "%s: DESCRIPTION is %d characters (max %d)" % \
1907 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1909 keywords = myaux["KEYWORDS"].split()
1910 stable_keywords = []
1911 for keyword in keywords:
1912 if not keyword.startswith("~") and \
1913 not keyword.startswith("-"):
1914 stable_keywords.append(keyword)
1916 if ebuild_path in new_ebuilds and catdir != "virtual":
1917 stable_keywords.sort()
1918 stats["KEYWORDS.stable"] += 1
1919 fails["KEYWORDS.stable"].append(
1920 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1921 " ".join(stable_keywords))
1923 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1924 if not kw.startswith("-"))
1926 previous_keywords = slot_keywords.get(pkg.slot)
1927 if previous_keywords is None:
1928 slot_keywords[pkg.slot] = set()
1929 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1930 dropped_keywords = previous_keywords.difference(ebuild_archs)
1931 if dropped_keywords:
1932 stats["KEYWORDS.dropped"] += 1
1933 fails["KEYWORDS.dropped"].append(
1934 relative_path + ": %s" % \
1935 " ".join(sorted(dropped_keywords)))
1937 slot_keywords[pkg.slot].update(ebuild_archs)
1939 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1940 if "-*" in keywords:
1948 stats["KEYWORDS.stupid"] += 1
1949 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1952 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1953 not be allowed to be marked stable
1955 if live_ebuild and repo_config.name == "gentoo":
1956 bad_stable_keywords = []
1957 for keyword in keywords:
1958 if not keyword.startswith("~") and \
1959 not keyword.startswith("-"):
1960 bad_stable_keywords.append(keyword)
1962 if bad_stable_keywords:
1963 stats["LIVEVCS.stable"] += 1
1964 fails["LIVEVCS.stable"].append(
1965 x + "/" + y + ".ebuild with stable keywords:%s " % \
1966 bad_stable_keywords)
1967 del bad_stable_keywords
1969 if keywords and not has_global_mask(pkg):
1970 stats["LIVEVCS.unmasked"] += 1
1971 fails["LIVEVCS.unmasked"].append(relative_path)
1973 if options.ignore_arches:
1974 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1975 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1978 for keyword in keywords:
1979 if keyword[0] == "-":
1981 elif keyword[0] == "~":
1984 for expanded_arch in profiles:
1985 if expanded_arch == "**":
1987 arches.add((keyword, expanded_arch,
1988 (expanded_arch, "~" + expanded_arch)))
1990 arches.add((keyword, arch, (arch, keyword)))
1993 for expanded_arch in profiles:
1994 if expanded_arch == "**":
1996 arches.add((keyword, expanded_arch,
1999 arches.add((keyword, keyword, (keyword,)))
2001 # Use an empty profile for checking dependencies of
2002 # packages that have empty KEYWORDS.
2003 arches.add(('**', '**', ('**',)))
2005 unknown_pkgs = set()
2006 baddepsyntax = False
2007 badlicsyntax = False
2008 badprovsyntax = False
2009 catpkg = catdir + "/" + y
2011 inherited_java_eclass = "java-pkg-2" in inherited or \
2012 "java-pkg-opt-2" in inherited
2013 inherited_wxwidgets_eclass = "wxwidgets" in inherited
2014 operator_tokens = set(["||", "(", ")"])
2015 type_list, badsyntax = [], []
2016 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
2017 mydepstr = myaux[mytype]
2019 buildtime = mytype in Package._buildtime_keys
2020 runtime = mytype in Package._runtime_keys
2022 if mytype.endswith("DEPEND"):
2023 token_class = portage.dep.Atom
2026 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
2027 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
2028 except portage.exception.InvalidDependString as e:
2030 badsyntax.append(str(e))
2032 if atoms and mytype.endswith("DEPEND"):
2034 "test?" in mydepstr.split():
2035 stats[mytype + '.suspect'] += 1
2036 fails[mytype + '.suspect'].append(relative_path + \
2037 ": 'test?' USE conditional in %s" % mytype)
2043 # Skip dependency.unknown for blockers, so that we
2044 # don't encourage people to remove necessary blockers,
2045 # as discussed in bug #382407.
2046 if atom.blocker is None and \
2047 not portdb.xmatch("match-all", atom) and \
2048 not atom.cp.startswith("virtual/"):
2049 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2051 is_blocker = atom.blocker
2053 if catdir != "virtual":
2054 if not is_blocker and \
2055 atom.cp in suspect_virtual:
2056 stats['virtual.suspect'] += 1
2057 fails['virtual.suspect'].append(
2059 ": %s: consider using '%s' instead of '%s'" %
2060 (mytype, suspect_virtual[atom.cp], atom))
2063 not is_blocker and \
2064 not inherited_java_eclass and \
2065 atom.cp == "virtual/jdk":
2066 stats['java.eclassesnotused'] += 1
2067 fails['java.eclassesnotused'].append(relative_path)
2068 elif buildtime and \
2069 not is_blocker and \
2070 not inherited_wxwidgets_eclass and \
2071 atom.cp == "x11-libs/wxGTK":
2072 stats['wxwidgets.eclassnotused'] += 1
2073 fails['wxwidgets.eclassnotused'].append(
2074 (relative_path + ": %ss on x11-libs/wxGTK"
2075 " without inheriting wxwidgets.eclass") % mytype)
2077 if not is_blocker and \
2078 atom.cp in suspect_rdepend:
2079 stats[mytype + '.suspect'] += 1
2080 fails[mytype + '.suspect'].append(
2081 relative_path + ": '%s'" % atom)
2083 if atom.operator == "~" and \
2084 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2085 qacat = 'dependency.badtilde'
2087 fails[qacat].append(
2088 (relative_path + ": %s uses the ~ operator"
2089 " with a non-zero revision:" + \
2090 " '%s'") % (mytype, atom))
2092 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2094 for m, b in zip(type_list, badsyntax):
2095 if m.endswith("DEPEND"):
2096 qacat = "dependency.syntax"
2098 qacat = m + ".syntax"
2100 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2102 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2103 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2104 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2105 badlicsyntax = badlicsyntax > 0
2106 badprovsyntax = badprovsyntax > 0
2108 # uselist checks - global
2111 for myflag in myaux["IUSE"].split():
2112 flag_name = myflag.lstrip("+-")
2113 used_useflags.add(flag_name)
2114 if myflag != flag_name:
2115 default_use.append(myflag)
2116 if flag_name not in uselist:
2117 myuse.append(flag_name)
2119 # uselist checks - metadata
2120 for mypos in range(len(myuse)-1, -1, -1):
2121 if myuse[mypos] and (myuse[mypos] in muselist):
2124 if default_use and not eapi_has_iuse_defaults(eapi):
2125 for myflag in default_use:
2126 stats['EAPI.incompatible'] += 1
2127 fails['EAPI.incompatible'].append(
2128 (relative_path + ": IUSE defaults" + \
2129 " not supported with EAPI='%s':" + \
2130 " '%s'") % (eapi, myflag))
2132 for mypos in range(len(myuse)):
2133 stats["IUSE.invalid"] += 1
2134 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2137 if not badlicsyntax:
2138 # Parse the LICENSE variable, remove USE conditions and
2140 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2141 # Check each entry to ensure that it exists in PORTDIR's
2142 # license directory.
2143 for lic in licenses:
2144 # Need to check for "||" manually as no portage
2145 # function will remove it without removing values.
2146 if lic not in liclist and lic != "||":
2147 stats["LICENSE.invalid"] += 1
2148 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2149 elif lic in liclist_deprecated:
2150 stats["LICENSE.deprecated"] += 1
2151 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2154 myuse = myaux["KEYWORDS"].split()
2156 if mykey not in ("-*", "*", "~*"):
2158 if myskey[:1] == "-":
2160 if myskey[:1] == "~":
2162 if myskey not in kwlist:
2163 stats["KEYWORDS.invalid"] += 1
2164 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2165 elif myskey not in profiles:
2166 stats["KEYWORDS.invalid"] += 1
2167 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2172 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2173 except portage.exception.InvalidDependString as e:
2174 stats["RESTRICT.syntax"] += 1
2175 fails["RESTRICT.syntax"].append(
2176 "%s: RESTRICT: %s" % (relative_path, e))
2179 myrestrict = set(myrestrict)
2180 mybadrestrict = myrestrict.difference(valid_restrict)
2182 stats["RESTRICT.invalid"] += len(mybadrestrict)
2183 for mybad in mybadrestrict:
2184 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2186 required_use = myaux["REQUIRED_USE"]
2188 if not eapi_has_required_use(eapi):
2189 stats['EAPI.incompatible'] += 1
2190 fails['EAPI.incompatible'].append(
2191 relative_path + ": REQUIRED_USE" + \
2192 " not supported with EAPI='%s'" % (eapi,))
2194 portage.dep.check_required_use(required_use, (),
2195 pkg.iuse.is_valid_flag, eapi=eapi)
2196 except portage.exception.InvalidDependString as e:
2197 stats["REQUIRED_USE.syntax"] += 1
2198 fails["REQUIRED_USE.syntax"].append(
2199 "%s: REQUIRED_USE: %s" % (relative_path, e))
2203 relative_path = os.path.join(x, y + ".ebuild")
2204 full_path = os.path.join(repodir, relative_path)
2205 if not vcs_preserves_mtime:
2206 if ebuild_path not in new_ebuilds and \
2207 ebuild_path not in modified_ebuilds:
2210 # All ebuilds should have utf_8 encoding.
2211 f = io.open(_unicode_encode(full_path,
2212 encoding=_encodings['fs'], errors='strict'),
2213 mode='r', encoding=_encodings['repo.content'])
2215 for check_name, e in run_checks(f, pkg):
2216 stats[check_name] += 1
2217 fails[check_name].append(relative_path + ': %s' % e)
2220 except UnicodeDecodeError:
2221 # A file.UTF8 failure will have already been recorded above.
2225 # The dep_check() calls are the most expensive QA test. If --force
2226 # is enabled, there's no point in wasting time on these since the
2227 # user is intent on forcing the commit anyway.
2230 relevant_profiles = []
2231 for keyword, arch, groups in arches:
2232 if arch not in profiles:
2233 # A missing profile will create an error further down
2234 # during the KEYWORDS verification.
2237 if include_arches is not None:
2238 if arch not in include_arches:
2241 relevant_profiles.extend((keyword, groups, prof)
2242 for prof in profiles[arch])
2245 return item[2].sub_path
2247 relevant_profiles.sort(key=sort_key)
2249 for keyword, groups, prof in relevant_profiles:
2251 if prof.status not in ("stable", "dev") or \
2252 prof.status == "dev" and not options.include_dev:
2255 dep_settings = arch_caches.get(prof.sub_path)
2256 if dep_settings is None:
2257 dep_settings = portage.config(
2258 config_profile_path=prof.abs_path,
2259 config_incrementals=repoman_incrementals,
2260 config_root=config_root,
2262 _unmatched_removal=options.unmatched_removal,
2264 dep_settings.categories = repoman_settings.categories
2265 if options.without_mask:
2266 dep_settings._mask_manager_obj = \
2267 copy.deepcopy(dep_settings._mask_manager)
2268 dep_settings._mask_manager._pmaskdict.clear()
2269 arch_caches[prof.sub_path] = dep_settings
2271 xmatch_cache_key = (prof.sub_path, tuple(groups))
2272 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2276 xcache = portdb.xcache
2277 xcache.update(shared_xmatch_caches)
2278 arch_xmatch_caches[xmatch_cache_key] = xcache
2280 trees[root]["porttree"].settings = dep_settings
2281 portdb.settings = dep_settings
2282 portdb.xcache = xcache
2284 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2285 # just in case, prevent config.reset() from nuking these.
2286 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2288 # This attribute is used in dbapi._match_use() to apply
2289 # use.stable.{mask,force} settings based on the stable
2290 # status of the parent package. This is required in order
2291 # for USE deps of unstable packages to be resolved correctly,
2292 # since otherwise use.stable.{mask,force} settings of
2293 # dependencies may conflict (see bug #456342).
2294 dep_settings._parent_stable = dep_settings._isStable(pkg)
2296 # Handle package.use*.{force,mask) calculation, for use
2298 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2299 pkg, stable=dep_settings._parent_stable)
2300 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2301 pkg, stable=dep_settings._parent_stable)
2303 if not baddepsyntax:
2304 ismasked = not ebuild_archs or \
2305 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2307 if not have_pmasked:
2308 have_pmasked = bool(dep_settings._getMaskAtom(
2309 pkg.cpv, pkg._metadata))
2310 if options.ignore_masked:
2312 #we are testing deps for a masked package; give it some lee-way
2314 matchmode = "minimum-all"
2317 matchmode = "minimum-visible"
2319 if not have_dev_keywords:
2320 have_dev_keywords = \
2321 bool(dev_keywords.intersection(keywords))
2323 if prof.status == "dev":
2324 suffix = suffix + "indev"
2326 for mytype in Package._dep_keys:
2328 mykey = "dependency.bad" + suffix
2329 myvalue = myaux[mytype]
2333 success, atoms = portage.dep_check(myvalue, portdb,
2334 dep_settings, use="all", mode=matchmode,
2340 # Don't bother with dependency.unknown for
2341 # cases in which *DEPEND.bad is triggered.
2343 # dep_check returns all blockers and they
2344 # aren't counted for *DEPEND.bad, so we
2346 if not atom.blocker:
2347 unknown_pkgs.discard(
2348 (mytype, atom.unevaluated_atom))
2350 if not prof.sub_path:
2351 # old-style virtuals currently aren't
2352 # resolvable with empty profile, since
2353 # 'virtuals' mappings are unavailable
2354 # (it would be expensive to search
2355 # for PROVIDE in all ebuilds)
2356 atoms = [atom for atom in atoms if not \
2357 (atom.cp.startswith('virtual/') and \
2358 not portdb.cp_list(atom.cp))]
2360 #we have some unsolvable deps
2361 #remove ! deps, which always show up as unsatisfiable
2362 atoms = [str(atom.unevaluated_atom) \
2363 for atom in atoms if not atom.blocker]
2365 #if we emptied out our list, continue:
2369 fails[mykey].append("%s: %s: %s(%s) %s" % \
2370 (relative_path, mytype, keyword,
2374 fails[mykey].append("%s: %s: %s(%s) %s" % \
2375 (relative_path, mytype, keyword,
2378 if not baddepsyntax and unknown_pkgs:
2380 for mytype, atom in unknown_pkgs:
2381 type_map.setdefault(mytype, set()).add(atom)
2382 for mytype, atoms in type_map.items():
2383 stats["dependency.unknown"] += 1
2384 fails["dependency.unknown"].append("%s: %s: %s" %
2385 (relative_path, mytype, ", ".join(sorted(atoms))))
2387 # check if there are unused local USE-descriptions in metadata.xml
2388 # (unless there are any invalids, to avoid noise)
2390 for myflag in muselist.difference(used_useflags):
2391 stats["metadata.warning"] += 1
2392 fails["metadata.warning"].append(
2393 "%s/metadata.xml: unused local USE-description: '%s'" % \
2396 if options.if_modified == "y" and len(effective_scanlist) < 1:
2397 logging.warn("--if-modified is enabled, but no modified packages were found!")
2399 if options.mode == "manifest":
2402 # dofail will be set to 1 if we have failed in at least one non-warning category
2404 # dowarn will be set to 1 if we tripped any warnings
2406 # dofull will be set if we should print a "repoman full" informational message
2407 dofull = options.mode != 'full'
2413 if x not in qawarnings:
2417 (dowarn and not (options.quiet or options.mode == "scan")):
2420 # Save QA output so that it can be conveniently displayed
2421 # in $EDITOR while the user creates a commit message.
2422 # Otherwise, the user would not be able to see this output
2423 # once the editor has taken over the screen.
2424 qa_output = io.StringIO()
2425 style_file = ConsoleStyleFile(sys.stdout)
2426 if options.mode == 'commit' and \
2427 (not commitmessage or not commitmessage.strip()):
2428 style_file.write_listener = qa_output
2429 console_writer = StyleWriter(file=style_file, maxcol=9999)
2430 console_writer.style_listener = style_file.new_styles
2432 f = formatter.AbstractFormatter(console_writer)
2434 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2437 del console_writer, f, style_file
2438 qa_output = qa_output.getvalue()
2439 qa_output = qa_output.splitlines(True)
2441 suggest_ignore_masked = False
2442 suggest_include_dev = False
2444 if have_pmasked and not (options.without_mask or options.ignore_masked):
2445 suggest_ignore_masked = True
2446 if have_dev_keywords and not options.include_dev:
2447 suggest_include_dev = True
2449 if suggest_ignore_masked or suggest_include_dev:
2451 if suggest_ignore_masked:
2452 print(bold("Note: use --without-mask to check " + \
2453 "KEYWORDS on dependencies of masked packages"))
2455 if suggest_include_dev:
2456 print(bold("Note: use --include-dev (-d) to check " + \
2457 "dependencies for 'dev' profiles"))
2460 if options.mode != 'commit':
2462 print(bold("Note: type \"repoman full\" for a complete listing."))
2463 if dowarn and not dofail:
2464 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.\"")
2466 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2468 print(bad("Please fix these important QA issues first."))
2469 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2472 if dofail and can_force and options.force and not options.pretend:
2473 print(green("RepoMan sez:") + \
2474 " \"You want to commit even with these QA issues?\n" + \
2475 " I'll take it this time, but I'm not happy.\"\n")
2477 if options.force and not can_force:
2478 print(bad("The --force option has been disabled due to extraordinary issues."))
2479 print(bad("Please fix these important QA issues first."))
2480 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2484 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2489 myvcstree = portage.cvstree.getentries("./", recursive=1)
2490 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2491 except SystemExit as e:
2492 raise # TODO propagate this
2494 err("Error retrieving CVS tree; exiting.")
2497 with repoman_popen("svn status --no-ignore") as f:
2498 svnstatus = f.readlines()
2499 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2500 except SystemExit as e:
2501 raise # TODO propagate this
2503 err("Error retrieving SVN info; exiting.")
2505 # get list of files not under version control or missing
2506 myf = repoman_popen("git ls-files --others")
2507 myunadded = ["./" + elem[:-1] for elem in myf]
2511 with repoman_popen("bzr status -S .") as f:
2512 bzrstatus = f.readlines()
2513 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2514 except SystemExit as e:
2515 raise # TODO propagate this
2517 err("Error retrieving bzr info; exiting.")
2519 with repoman_popen("hg status --no-status --unknown .") as f:
2520 myunadded = f.readlines()
2521 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2523 # Mercurial doesn't handle manually deleted files as removed from
2524 # the repository, so the user need to remove them before commit,
2525 # using "hg remove [FILES]"
2526 with repoman_popen("hg status --no-status --deleted .") as f:
2527 mydeleted = f.readlines()
2528 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2533 for x in range(len(myunadded)-1, -1, -1):
2534 xs = myunadded[x].split("/")
2535 if xs[-1] == "files":
2536 print("!!! files dir is not added! Please correct this.")
2538 elif xs[-1] == "Manifest":
2539 # It's a manifest... auto add
2540 myautoadd += [myunadded[x]]
2544 print(red("!!! The following files are in your local tree but are not added to the master"))
2545 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2552 if vcs == "hg" and mydeleted:
2553 print(red("!!! The following files are removed manually from your local tree but are not"))
2554 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2562 mycvstree = cvstree.getentries("./", recursive=1)
2563 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2564 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2565 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2566 bin_blob_pattern = re.compile("^-kb$")
2567 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2568 recursive=1, basedir="./"))
2571 with repoman_popen("svn status") as f:
2572 svnstatus = f.readlines()
2573 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2574 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2575 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2577 # Subversion expands keywords specified in svn:keywords properties.
2578 with repoman_popen("svn propget -R svn:keywords") as f:
2579 props = f.readlines()
2580 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2581 for prop in props if " - " in prop)
2584 with repoman_popen("git diff-index --name-only "
2585 "--relative --diff-filter=M HEAD") as f:
2586 mychanged = f.readlines()
2587 mychanged = ["./" + elem[:-1] for elem in mychanged]
2589 with repoman_popen("git diff-index --name-only "
2590 "--relative --diff-filter=A HEAD") as f:
2591 mynew = f.readlines()
2592 mynew = ["./" + elem[:-1] for elem in mynew]
2594 with repoman_popen("git diff-index --name-only "
2595 "--relative --diff-filter=D HEAD") as f:
2596 myremoved = f.readlines()
2597 myremoved = ["./" + elem[:-1] for elem in myremoved]
2600 with repoman_popen("bzr status -S .") as f:
2601 bzrstatus = f.readlines()
2602 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2603 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")]
2604 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2605 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")]
2606 # Bazaar expands nothing.
2609 with repoman_popen("hg status --no-status --modified .") as f:
2610 mychanged = f.readlines()
2611 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2613 with repoman_popen("hg status --no-status --added .") as f:
2614 mynew = f.readlines()
2615 mynew = ["./" + elem.rstrip() for elem in mynew]
2617 with repoman_popen("hg status --no-status --removed .") as f:
2618 myremoved = f.readlines()
2619 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2622 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2623 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2625 print("(Didn't find any changed files...)")
2629 # Manifests need to be regenerated after all other commits, so don't commit
2630 # them now even if they have changed.
2633 for f in mychanged + mynew:
2634 if "Manifest" == os.path.basename(f):
2638 myupdates.difference_update(myremoved)
2639 myupdates = list(myupdates)
2640 mymanifests = list(mymanifests)
2644 commitmessage = options.commitmsg
2645 if options.commitmsgfile:
2647 f = io.open(_unicode_encode(options.commitmsgfile,
2648 encoding=_encodings['fs'], errors='strict'),
2649 mode='r', encoding=_encodings['content'], errors='replace')
2650 commitmessage = f.read()
2653 except (IOError, OSError) as e:
2654 if e.errno == errno.ENOENT:
2655 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2658 # We've read the content so the file is no longer needed.
2659 commitmessagefile = None
2660 if not commitmessage or not commitmessage.strip():
2662 editor = os.environ.get("EDITOR")
2663 if editor and utilities.editor_is_executable(editor):
2664 commitmessage = utilities.get_commit_message_with_editor(
2665 editor, message=qa_output)
2667 commitmessage = utilities.get_commit_message_with_stdin()
2668 except KeyboardInterrupt:
2670 if not commitmessage or not commitmessage.strip():
2671 print("* no commit message? aborting commit.")
2673 commitmessage = commitmessage.rstrip()
2674 changelog_msg = commitmessage
2675 portage_version = getattr(portage, "VERSION", None)
2676 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2677 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2678 if portage_version is None:
2679 sys.stderr.write("Failed to insert portage version in message!\n")
2681 portage_version = "Unknown"
2685 report_options.append("--force")
2686 if options.ignore_arches:
2687 report_options.append("--ignore-arches")
2688 if include_arches is not None:
2689 report_options.append("--include-arches=\"%s\"" %
2690 " ".join(sorted(include_arches)))
2693 # Use new footer only for git (see bug #438364).
2694 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2696 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2698 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2700 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2702 unameout = platform.system() + " "
2703 if platform.system() in ["Darwin", "SunOS"]:
2704 unameout += platform.processor()
2706 unameout += platform.machine()
2707 commit_footer = "\n\n"
2709 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2710 commit_footer += "(Portage version: %s/%s/%s" % \
2711 (portage_version, vcs, unameout)
2713 commit_footer += ", RepoMan options: " + " ".join(report_options)
2715 commit_footer += ", signed Manifest commit with key %s" % \
2718 commit_footer += ", unsigned Manifest commit"
2719 commit_footer += ")"
2721 commitmessage += commit_footer
2723 if options.echangelog in ('y', 'force'):
2724 logging.info("checking for unmodified ChangeLog files")
2725 committer_name = utilities.get_committer_name(env=repoman_settings)
2726 for x in sorted(vcs_files_to_cps(
2727 chain(myupdates, mymanifests, myremoved))):
2728 catdir, pkgdir = x.split("/")
2729 checkdir = repodir + "/" + x
2730 checkdir_relative = ""
2732 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2734 checkdir_relative = os.path.join(catdir, checkdir_relative)
2735 checkdir_relative = os.path.join(".", checkdir_relative)
2737 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2738 changelog_modified = changelog_path in modified_changelogs
2739 if changelog_modified and options.echangelog != 'force':
2742 # get changes for this package
2743 cdrlen = len(checkdir_relative)
2744 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2745 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2746 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2748 # Skip ChangeLog generation if only the Manifest was modified,
2749 # as discussed in bug #398009.
2750 nontrivial_cl_files = set()
2751 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2752 nontrivial_cl_files.difference_update(['Manifest'])
2753 if not nontrivial_cl_files and options.echangelog != 'force':
2756 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2757 committer_name, changelog_msg,
2758 os.path.join(repodir, 'skel.ChangeLog'),
2760 new=clnew, removed=clremoved, changed=clchanged,
2761 pretend=options.pretend)
2762 if new_changelog is None:
2763 writemsg_level("!!! Updating the ChangeLog failed\n", \
2764 level=logging.ERROR, noiselevel=-1)
2767 # if the ChangeLog was just created, add it to vcs
2769 myautoadd.append(changelog_path)
2770 # myautoadd is appended to myupdates below
2772 myupdates.append(changelog_path)
2774 if options.ask and not options.pretend:
2775 # regenerate Manifest for modified ChangeLog (bug #420735)
2776 repoman_settings["O"] = checkdir
2777 digestgen(mysettings=repoman_settings, myportdb=portdb)
2780 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2781 add_cmd = [vcs, "add"]
2782 add_cmd += myautoadd
2784 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2787 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2788 # Python 3.1 produces the following TypeError if raw bytes are
2789 # passed to subprocess.call():
2790 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2791 # errread, errwrite)
2792 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2793 # raise child_exception
2794 # TypeError: expected an object with the buffer interface
2795 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2796 retcode = subprocess.call(add_cmd)
2797 if retcode != os.EX_OK:
2799 "Exiting on %s error code: %s\n" % (vcs, retcode))
2802 myupdates += myautoadd
2804 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2806 if vcs not in ('cvs', 'svn'):
2807 # With git, bzr and hg, there's never any keyword expansion, so
2808 # there's no need to regenerate manifests and all files will be
2809 # committed in one big commit at the end.
2811 elif not repo_config.thin_manifest:
2813 headerstring = "'\$(Header|Id).*\$'"
2815 svn_keywords = dict((k.lower(), k) for k in [
2818 "LastChangedRevision",
2829 for myfile in myupdates:
2831 # for CVS, no_expansion contains files that are excluded from expansion
2833 if myfile in no_expansion:
2836 # for SVN, expansion contains files that are included in expansion
2838 if myfile not in expansion:
2841 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2842 enabled_keywords = []
2843 for k in expansion[myfile]:
2844 keyword = svn_keywords.get(k.lower())
2845 if keyword is not None:
2846 enabled_keywords.append(keyword)
2848 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2850 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2851 portage._shell_quote(myfile))
2853 myheaders.append(myfile)
2855 print("%s have headers that will change." % green(str(len(myheaders))))
2856 print("* Files with headers will cause the manifests to be changed and committed separately.")
2858 logging.info("myupdates: %s", myupdates)
2859 logging.info("myheaders: %s", myheaders)
2861 if options.ask and userquery('Commit changes?', True) != 'Yes':
2862 print("* aborting commit.")
2863 sys.exit(128 + signal.SIGINT)
2865 # Handle the case where committed files have keywords which
2866 # will change and need a priming commit before the Manifest
2868 if (myupdates or myremoved) and myheaders:
2869 myfiles = myupdates + myremoved
2870 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2871 mymsg = os.fdopen(fd, "wb")
2872 mymsg.write(_unicode_encode(commitmessage))
2876 print(green("Using commit message:"))
2877 print(green("------------------------------------------------------------------------------"))
2878 print(commitmessage)
2879 print(green("------------------------------------------------------------------------------"))
2882 # Having a leading ./ prefix on file paths can trigger a bug in
2883 # the cvs server when committing files to multiple directories,
2884 # so strip the prefix.
2885 myfiles = [f.lstrip("./") for f in myfiles]
2888 commit_cmd.extend(vcs_global_opts)
2889 commit_cmd.append("commit")
2890 commit_cmd.extend(vcs_local_opts)
2891 commit_cmd.extend(["-F", commitmessagefile])
2892 commit_cmd.extend(myfiles)
2896 print("(%s)" % (" ".join(commit_cmd),))
2898 retval = spawn(commit_cmd, env=commit_env)
2899 if retval != os.EX_OK:
2900 writemsg_level(("!!! Exiting on %s (shell) " + \
2901 "error code: %s\n") % (vcs, retval),
2902 level=logging.ERROR, noiselevel=-1)
2906 os.unlink(commitmessagefile)
2910 # Setup the GPG commands
2911 def gpgsign(filename):
2912 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2914 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2915 " Is make.globals missing?")
2916 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2917 "PORTAGE_GPG_KEY" not in repoman_settings:
2918 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2919 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2920 if "PORTAGE_GPG_DIR" not in repoman_settings:
2921 repoman_settings["PORTAGE_GPG_DIR"] = \
2922 os.path.expanduser("~/.gnupg")
2923 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2924 % repoman_settings["PORTAGE_GPG_DIR"])
2926 repoman_settings["PORTAGE_GPG_DIR"] = \
2927 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2928 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2929 raise portage.exception.InvalidLocation(
2930 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2931 repoman_settings["PORTAGE_GPG_DIR"])
2932 gpgvars = {"FILE": filename}
2933 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2934 v = repoman_settings.get(k)
2937 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2939 print("(" + gpgcmd + ")")
2941 # Encode unicode manually for bug #310789.
2942 gpgcmd = portage.util.shlex_split(gpgcmd)
2943 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
2944 # Python 3.1 does not support bytes in Popen args.
2945 gpgcmd = [_unicode_encode(arg,
2946 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2947 rValue = subprocess.call(gpgcmd)
2948 if rValue == os.EX_OK:
2949 os.rename(filename + ".asc", filename)
2951 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2953 def need_signature(filename):
2955 with open(_unicode_encode(filename,
2956 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2957 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2958 except IOError as e:
2959 if e.errno in (errno.ENOENT, errno.ESTALE):
2963 # When files are removed and re-added, the cvs server will put /Attic/
2964 # inside the $Header path. This code detects the problem and corrects it
2965 # so that the Manifest will generate correctly. See bug #169500.
2966 # Use binary mode in order to avoid potential character encoding issues.
2967 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2968 attic_str = b'/Attic/'
2969 attic_replace = b'/'
2971 f = open(_unicode_encode(x,
2972 encoding=_encodings['fs'], errors='strict'),
2974 mylines = f.readlines()
2977 for i, line in enumerate(mylines):
2978 if cvs_header_re.match(line) is not None and \
2980 mylines[i] = line.replace(attic_str, attic_replace)
2983 portage.util.write_atomic(x, b''.join(mylines),
2987 print(green("RepoMan sez:"), "\"You're rather crazy... "
2988 "doing the entire repository.\"\n")
2990 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2992 for x in sorted(vcs_files_to_cps(
2993 chain(myupdates, myremoved, mymanifests))):
2994 repoman_settings["O"] = os.path.join(repodir, x)
2995 digestgen(mysettings=repoman_settings, myportdb=portdb)
3001 for x in sorted(vcs_files_to_cps(
3002 chain(myupdates, myremoved, mymanifests))):
3003 repoman_settings["O"] = os.path.join(repodir, x)
3004 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
3005 if not need_signature(manifest_path):
3007 gpgsign(manifest_path)
3008 except portage.exception.PortageException as e:
3009 portage.writemsg("!!! %s\n" % str(e))
3010 portage.writemsg("!!! Disabled FEATURES='sign'\n")
3014 # It's not safe to use the git commit -a option since there might
3015 # be some modified files elsewhere in the working tree that the
3016 # user doesn't want to commit. Therefore, call git update-index
3017 # in order to ensure that the index is updated with the latest
3018 # versions of all new and modified files in the relevant portion
3019 # of the working tree.
3020 myfiles = mymanifests + myupdates
3022 update_index_cmd = ["git", "update-index"]
3023 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
3025 print("(%s)" % (" ".join(update_index_cmd),))
3027 retval = spawn(update_index_cmd, env=os.environ)
3028 if retval != os.EX_OK:
3029 writemsg_level(("!!! Exiting on %s (shell) " + \
3030 "error code: %s\n") % (vcs, retval),
3031 level=logging.ERROR, noiselevel=-1)
3035 myfiles = mymanifests[:]
3036 # If there are no header (SVN/CVS keywords) changes in
3037 # the files, this Manifest commit must include the
3038 # other (yet uncommitted) files.
3040 myfiles += myupdates
3041 myfiles += myremoved
3044 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3045 mymsg = os.fdopen(fd, "wb")
3046 mymsg.write(_unicode_encode(commitmessage))
3050 if options.pretend and vcs is None:
3051 # substitute a bogus value for pretend output
3052 commit_cmd.append("cvs")
3054 commit_cmd.append(vcs)
3055 commit_cmd.extend(vcs_global_opts)
3056 commit_cmd.append("commit")
3057 commit_cmd.extend(vcs_local_opts)
3059 commit_cmd.extend(["--logfile", commitmessagefile])
3060 commit_cmd.extend(myfiles)
3062 commit_cmd.extend(["-F", commitmessagefile])
3063 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3067 print("(%s)" % (" ".join(commit_cmd),))
3069 retval = spawn(commit_cmd, env=commit_env)
3070 if retval != os.EX_OK:
3071 if repo_config.sign_commit and vcs == 'git' and \
3072 not git_supports_gpg_sign():
3073 # Inform user that newer git is needed (bug #403323).
3075 "Git >=1.7.9 is required for signed commits!")
3077 writemsg_level(("!!! Exiting on %s (shell) " + \
3078 "error code: %s\n") % (vcs, retval),
3079 level=logging.ERROR, noiselevel=-1)
3083 os.unlink(commitmessagefile)
3089 print("Commit complete.")
3091 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3092 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")