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
27 from itertools import chain
28 from stat import S_ISDIR
31 from urllib.parse import urlparse
33 from urlparse import urlparse
35 from os import path as osp
36 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
37 sys.path.insert(0, pym_path)
39 portage._internal_caller = True
40 portage._disable_legacy_globals()
43 import xml.etree.ElementTree
44 from xml.parsers.expat import ExpatError
45 except (SystemExit, KeyboardInterrupt):
47 except (ImportError, SystemError, RuntimeError, Exception):
48 # broken or missing xml support
49 # http://bugs.python.org/issue14988
50 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
51 from portage.output import EOutput
57 from portage import os
58 from portage import _encodings
59 from portage import _unicode_encode
60 from repoman.checks import run_checks
61 from repoman import utilities
62 from repoman.herdbase import make_herd_base
63 from _emerge.Package import Package
64 from _emerge.RootConfig import RootConfig
65 from _emerge.userquery import userquery
66 import portage.checksum
68 from portage import cvstree, normalize_path
69 from portage import util
70 from portage.exception import (FileNotFound, InvalidAtom, MissingParameter,
71 ParseError, PermissionDenied)
72 from portage.dep import Atom
73 from portage.process import find_binary, spawn
74 from portage.output import bold, create_color_func, \
76 from portage.output import ConsoleStyleFile, StyleWriter
77 from portage.util import writemsg_level
78 from portage.package.ebuild.digestgen import digestgen
79 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
81 if sys.hexversion >= 0x3000000:
84 util.initialize_logger()
86 # 14 is the length of DESCRIPTION=""
88 allowed_filename_chars="a-zA-Z0-9._-+:"
89 pv_toolong_re = re.compile(r'[0-9]{19,}')
90 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})!?'
91 bad = create_color_func("BAD")
93 # A sane umask is needed for files that portage creates.
95 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
96 # behave incrementally.
97 repoman_incrementals = tuple(x for x in \
98 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
99 config_root = os.environ.get("PORTAGE_CONFIGROOT")
100 repoman_settings = portage.config(config_root=config_root, local_config=False)
102 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
103 repoman_settings.get('TERM') == 'dumb' or \
104 not sys.stdout.isatty():
108 print("repoman: " + txt)
114 def exithandler(signum=None, frame=None):
115 logging.fatal("Interrupted; exiting...")
119 sys.exit(128 + signum)
121 signal.signal(signal.SIGINT, exithandler)
123 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
124 """Repoman needs its own HelpFormatter for now, because the default ones
125 murder the help text."""
127 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
128 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
130 def format_description(self, description):
133 class RepomanOptionParser(optparse.OptionParser):
134 """Add the on_tail function, ruby has it, optionParser should too
137 def __init__(self, *args, **kwargs):
138 optparse.OptionParser.__init__(self, *args, **kwargs)
141 def on_tail(self, description):
142 self.tail += description
144 def format_help(self, formatter=None):
145 result = optparse.OptionParser.format_help(self, formatter)
150 def ParseArgs(argv, qahelp):
151 """This function uses a customized optionParser to parse command line arguments for repoman
153 argv - a sequence of command line arguments
154 qahelp - a dict of qa warning to help message
156 (opts, args), just like a call to parser.parse_args()
159 argv = portage._decode_argv(argv)
162 'commit' : 'Run a scan then commit changes',
163 'ci' : 'Run a scan then commit changes',
164 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
165 'full' : 'Scan directory tree and print all issues (not a summary)',
166 'help' : 'Show this screen',
167 'manifest' : 'Generate a Manifest (fetches files if necessary)',
168 'manifest-check' : 'Check Manifests for missing or incorrect digests',
169 'scan' : 'Scan directory tree for QA issues'
172 mode_keys = list(modes)
175 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
176 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
177 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
178 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
179 parser.description += "\nmodes: " + " | ".join(map(green, mode_keys))
181 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
182 help='Request a confirmation before commiting')
184 parser.add_option('-m', '--commitmsg', dest='commitmsg',
185 help='specify a commit message on the command line')
187 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
188 help='specify a path to a file that contains a commit message')
190 parser.add_option('--digest',
191 type='choice', choices=('y', 'n'), metavar='<y|n>',
192 help='Automatically update Manifest digests for modified files')
194 parser.add_option('-p', '--pretend', dest='pretend', default=False,
195 action='store_true', help='don\'t commit or fix anything; just show what would be done')
197 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
198 help='do not print unnecessary messages')
201 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
202 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
203 'regardless of modification if \'force\' is specified)')
205 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
206 help='Commit with QA violations')
208 parser.add_option('--vcs', dest='vcs',
209 help='Force using specific VCS instead of autodetection')
211 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
212 help='be very verbose in output', default=0)
214 parser.add_option('-V', '--version', dest='version', action='store_true',
215 help='show version info')
217 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
218 default=False, help='forces the metadata.xml parse check to be carried out')
221 '--if-modified', type='choice', choices=('y', 'n'), default='n',
223 help='only check packages that have uncommitted modifications')
225 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
226 default=False, help='ignore arch-specific failures (where arch != host)')
228 parser.add_option("--ignore-default-opts",
230 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
232 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
233 default=False, help='ignore masked packages (not allowed with commit mode)')
235 parser.add_option('--include-arches', dest='include_arches',
236 metavar='ARCHES', action='append',
237 help='A space separated list of arches used to '
238 'filter the selection of profiles for dependency checks')
240 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
241 default=False, help='include dev profiles in dependency checks')
243 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
244 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
246 parser.add_option('--without-mask', dest='without_mask', action='store_true',
247 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
249 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
250 help='specify which mode repoman will run in (default=full)')
252 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
255 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
257 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
259 sorted_qa = list(qahelp)
262 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
264 opts, args = parser.parse_args(argv[1:])
266 if not opts.ignore_default_opts:
267 default_opts = portage.util.shlex_split(
268 repoman_settings.get("REPOMAN_DEFAULT_OPTS", ""))
270 opts, args = parser.parse_args(default_opts + sys.argv[1:])
272 if opts.mode == 'help':
273 parser.print_help(short=False)
281 parser.error("invalid mode: %s" % arg)
286 if opts.mode == 'ci':
287 opts.mode = 'commit' # backwards compat shortcut
289 if opts.mode == 'commit' and not (opts.force or opts.pretend):
290 if opts.ignore_masked:
291 parser.error('Commit mode and --ignore-masked are not compatible')
292 if opts.without_mask:
293 parser.error('Commit mode and --without-mask are not compatible')
295 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
296 for val in range(opts.verbosity):
297 logger = logging.getLogger()
298 logger.setLevel(logger.getEffectiveLevel() - 10)
300 for val in range(opts.quiet):
301 logger = logging.getLogger()
302 logger.setLevel(logger.getEffectiveLevel() + 10)
307 "CVS/Entries.IO_error": "Attempting to commit, and an IO error was encountered access the Entries file",
308 "ebuild.invalidname": "Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
309 "ebuild.namenomatch": "Ebuild files that do not have the same name as their parent directory",
310 "changelog.ebuildadded": "An ebuild was added but the ChangeLog was not modified",
311 "changelog.missing": "Missing ChangeLog files",
312 "ebuild.notadded": "Ebuilds that exist but have not been added to cvs",
313 "ebuild.patches": "PATCHES variable should be a bash array to ensure white space safety",
314 "changelog.notadded": "ChangeLogs that exist but have not been added to cvs",
315 "dependency.bad": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds)",
316 "dependency.badmasked": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds)",
317 "dependency.badindev": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds) in developing arch",
318 "dependency.badmaskedindev": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds) in developing arch",
319 "dependency.badtilde": "Uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
320 "dependency.syntax": "Syntax error in dependency string (usually an extra/missing space/parenthesis)",
321 "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)",
322 "file.executable": "Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
323 "file.size": "Files in the files directory must be under 20 KiB",
324 "file.size.fatal": "Files in the files directory must be under 60 KiB",
325 "file.name": "File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
326 "file.UTF8": "File is not UTF8 compliant",
327 "inherit.deprecated": "Ebuild inherits a deprecated eclass",
328 "inherit.missing": "Ebuild uses functions from an eclass but does not inherit it",
329 "inherit.unused": "Ebuild inherits an eclass but does not use it",
330 "java.eclassesnotused": "With virtual/jdk in DEPEND you must inherit a java eclass",
331 "wxwidgets.eclassnotused": "Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
332 "KEYWORDS.dropped": "Ebuilds that appear to have dropped KEYWORDS for some arch",
333 "KEYWORDS.missing": "Ebuilds that have a missing or empty KEYWORDS variable",
334 "KEYWORDS.stable": "Ebuilds that have been added directly with stable KEYWORDS",
335 "KEYWORDS.stupid": "Ebuilds that use KEYWORDS=-* instead of package.mask",
336 "LICENSE.missing": "Ebuilds that have a missing or empty LICENSE variable",
337 "LICENSE.virtual": "Virtuals that have a non-empty LICENSE variable",
338 "DESCRIPTION.missing": "Ebuilds that have a missing or empty DESCRIPTION variable",
339 "DESCRIPTION.toolong": "DESCRIPTION is over %d characters" % max_desc_len,
340 "EAPI.definition": "EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
341 "EAPI.deprecated": "Ebuilds that use features that are deprecated in the current EAPI",
342 "EAPI.incompatible": "Ebuilds that use features that are only available with a different EAPI",
343 "EAPI.unsupported": "Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
344 "SLOT.invalid": "Ebuilds that have a missing or invalid SLOT variable value",
345 "HOMEPAGE.missing": "Ebuilds that have a missing or empty HOMEPAGE variable",
346 "HOMEPAGE.virtual": "Virtuals that have a non-empty HOMEPAGE variable",
347 "PDEPEND.suspect": "PDEPEND contains a package that usually only belongs in DEPEND.",
348 "LICENSE.syntax": "Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
349 "PROVIDE.syntax": "Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
350 "PROPERTIES.syntax": "Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
351 "RESTRICT.syntax": "Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
352 "REQUIRED_USE.syntax": "Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
353 "SRC_URI.syntax": "Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
354 "SRC_URI.mirror": "A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
355 "ebuild.syntax": "Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
356 "ebuild.output": "A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
357 "ebuild.nesteddie": "Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
358 "variable.invalidchar": "A variable contains an invalid character that is not part of the ASCII character set",
359 "variable.readonly": "Assigning a readonly variable",
360 "variable.usedwithhelpers": "Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
361 "LIVEVCS.stable": "This ebuild is a live checkout from a VCS but has stable keywords.",
362 "LIVEVCS.unmasked": "This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
363 "IUSE.invalid": "This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
364 "IUSE.missing": "This ebuild has a USE conditional which references a flag that is not listed in IUSE",
365 "LICENSE.invalid": "This ebuild is listing a license that doesnt exist in portages license/ dir.",
366 "LICENSE.deprecated": "This ebuild is listing a deprecated license.",
367 "KEYWORDS.invalid": "This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
368 "RDEPEND.implicit": "RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
369 "RDEPEND.suspect": "RDEPEND contains a package that usually only belongs in DEPEND.",
370 "RESTRICT.invalid": "This ebuild contains invalid RESTRICT values.",
371 "digest.assumed": "Existing digest must be assumed correct (Package level only)",
372 "digest.missing": "Some files listed in SRC_URI aren't referenced in the Manifest",
373 "digest.unused": "Some files listed in the Manifest aren't referenced in SRC_URI",
374 "ebuild.majorsyn": "This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
375 "ebuild.minorsyn": "This ebuild has a minor syntax error that contravenes gentoo coding style",
376 "ebuild.badheader": "This ebuild has a malformed header",
377 "manifest.bad": "Manifest has missing or incorrect digests",
378 "metadata.missing": "Missing metadata.xml files",
379 "metadata.bad": "Bad metadata.xml files",
380 "metadata.warning": "Warnings in metadata.xml files",
381 "portage.internal": "The ebuild uses an internal Portage function or variable",
382 "repo.eapi.banned": "The ebuild uses an EAPI which is banned by the repository's metadata/layout.conf settings",
383 "repo.eapi.deprecated": "The ebuild uses an EAPI which is deprecated by the repository's metadata/layout.conf settings",
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",
428 "repo.eapi.deprecated",
430 "upstream.workaround",
435 if portage.const._ENABLE_INHERIT_CHECK:
436 # This is experimental, so it's non-fatal.
437 qawarnings.add("inherit.missing")
439 non_ascii_re = re.compile(r'[^\x00-\x7f]')
441 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
442 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
443 allvars.update(Package.metadata_keys)
444 allvars = sorted(allvars)
446 for x in missingvars:
449 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
453 valid_restrict = frozenset(["binchecks", "bindist",
454 "fetch", "installsources", "mirror", "preserve-libs",
455 "primaryuri", "splitdebug", "strip", "test", "userpriv"])
457 live_eclasses = frozenset([
468 suspect_rdepend = frozenset([
469 "app-arch/cabextract",
470 "app-arch/rpm2targz",
475 "dev-perl/extutils-pkgconfig",
481 "dev-util/gtk-doc-am",
484 "dev-util/pkg-config-lite",
486 "dev-util/pkgconfig",
487 "dev-util/pkgconfig-openbsd",
491 "media-gfx/ebdftopcf",
493 "sys-devel/autoconf",
494 "sys-devel/automake",
501 "virtual/linux-sources",
508 "dev-util/pkg-config-lite":"virtual/pkgconfig",
509 "dev-util/pkgconf":"virtual/pkgconfig",
510 "dev-util/pkgconfig":"virtual/pkgconfig",
511 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
512 "dev-libs/libusb":"virtual/libusb",
513 "dev-libs/libusbx":"virtual/libusb",
514 "dev-libs/libusb-compat":"virtual/libusb",
517 metadata_xml_encoding = 'UTF-8'
518 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
519 (metadata_xml_encoding,)
520 metadata_doctype_name = 'pkgmetadata'
521 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
522 # force refetch if the local copy creation time is older than this
523 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
526 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
528 options, arguments = ParseArgs(sys.argv, qahelp)
531 print("Portage", portage.VERSION)
534 # Set this to False when an extraordinary issue (generally
535 # something other than a QA issue) makes it impossible to
536 # commit (like if Manifest generation fails).
539 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
543 myreporoot = os.path.basename(portdir_overlay)
544 myreporoot += mydir[len(portdir_overlay):]
547 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
552 vcses = utilities.FindVCS()
554 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
555 print(red('*** Please either clean up your workdir or specify --vcs option.'))
562 if options.if_modified == "y" and vcs is None:
563 logging.info("Not in a version controlled repository; "
564 "disabling --if-modified.")
565 options.if_modified = "n"
567 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
568 vcs_preserves_mtime = vcs in ('cvs',)
570 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
571 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
572 if vcs_global_opts is None:
573 if vcs in ('cvs', 'svn'):
574 vcs_global_opts = "-q"
577 vcs_global_opts = vcs_global_opts.split()
579 if options.mode == 'commit' and not options.pretend and not vcs:
580 logging.info("Not in a version controlled repository; enabling pretend mode.")
581 options.pretend = True
583 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
584 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
585 (repoman_settings.get('PORTDIR_OVERLAY', ''),
586 portage._shell_quote(portdir_overlay))
587 # We have to call the config constructor again so
588 # that config.repositories is initialized correctly.
589 repoman_settings = portage.config(config_root=config_root, local_config=False,
590 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
592 root = repoman_settings['EROOT']
594 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
596 portdb = trees[root]['porttree'].dbapi
598 # Constrain dependency resolution to the master(s)
599 # that are specified in layout.conf.
600 repodir = os.path.realpath(portdir_overlay)
601 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
602 portdb.porttrees = list(repo_config.eclass_db.porttrees)
603 portdir = portdb.porttrees[0]
604 commit_env = os.environ.copy()
606 if repo_config.allow_provide_virtual:
607 qawarnings.add("virtual.oldstyle")
609 if repo_config.sign_commit:
611 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
612 # the commit arguments. If key_id is unspecified, then it must be
613 # configured by `git config user.signingkey key_id`.
614 vcs_local_opts.append("--gpg-sign")
615 if repoman_settings.get("PORTAGE_GPG_DIR"):
616 # Pass GNUPGHOME to git for bug #462362.
617 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
619 # In order to disable manifest signatures, repos may set
620 # "sign-manifests = false" in metadata/layout.conf. This
621 # can be used to prevent merge conflicts like those that
622 # thin-manifests is designed to prevent.
623 sign_manifests = "sign" in repoman_settings.features and \
624 repo_config.sign_manifest
626 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
627 options.mode in ("commit",) and not sign_manifests:
628 msg = ("The '%s' repository has manifest signatures enabled, "
629 "but FEATURES=sign is currently disabled. In order to avoid this "
630 "warning, enable FEATURES=sign in make.conf. Alternatively, "
631 "repositories can disable manifest signatures by setting "
632 "'sign-manifests = false' in metadata/layout.conf.") % \
634 for line in textwrap.wrap(msg, 60):
637 if sign_manifests and options.mode in ("commit",) and \
638 repoman_settings.get("PORTAGE_GPG_KEY") and \
639 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
640 repoman_settings["PORTAGE_GPG_KEY"]) is None:
641 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
642 repoman_settings["PORTAGE_GPG_KEY"])
645 manifest_hashes = repo_config.manifest_hashes
646 if manifest_hashes is None:
647 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
649 if options.mode in ("commit", "fix", "manifest"):
650 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
651 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
652 "metadata/layout.conf does not contain the '%s' hash which "
653 "is required by this portage version. You will have to "
654 "upgrade portage if you want to generate valid manifests for "
655 "this repository.") % \
656 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
657 for line in textwrap.wrap(msg, 70):
661 unsupported_hashes = manifest_hashes.difference(
662 portage.const.MANIFEST2_HASH_FUNCTIONS)
663 if unsupported_hashes:
664 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
665 "metadata/layout.conf contains one or more hash types '%s' "
666 "which are not supported by this portage version. You will "
667 "have to upgrade portage if you want to generate valid "
668 "manifests for this repository.") % \
669 (repo_config.name, " ".join(sorted(unsupported_hashes)))
670 for line in textwrap.wrap(msg, 70):
674 if options.echangelog is None and repo_config.update_changelog:
675 options.echangelog = 'y'
678 options.echangelog = 'n'
680 # The --echangelog option causes automatic ChangeLog generation,
681 # which invalidates changelog.ebuildadded and changelog.missing
683 # Note: Some don't use ChangeLogs in distributed SCMs.
684 # It will be generated on server side from scm log,
685 # before package moves to the rsync server.
686 # This is needed because they try to avoid merge collisions.
687 # Gentoo's Council decided to always use the ChangeLog file.
688 # TODO: shouldn't this just be switched on the repo, iso the VCS?
689 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
691 if 'digest' in repoman_settings.features and options.digest != 'n':
694 logging.debug("vcs: %s" % (vcs,))
695 logging.debug("repo config: %s" % (repo_config,))
696 logging.debug("options: %s" % (options,))
698 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
699 # profile-specific config constructor calls.
700 env = os.environ.copy()
701 env['PORTDIR'] = portdir
702 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
704 logging.info('Setting paths:')
705 logging.info('PORTDIR = "' + portdir + '"')
706 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
708 # It's confusing if these warnings are displayed without the user
709 # being told which profile they come from, so disable them.
710 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
713 for path in repo_config.eclass_db.porttrees:
714 categories.extend(portage.util.grabfile(
715 os.path.join(path, 'profiles', 'categories')))
716 repoman_settings.categories = frozenset(
717 portage.util.stack_lists([categories], incremental=1))
718 categories = repoman_settings.categories
720 portdb.settings = repoman_settings
721 root_config = RootConfig(repoman_settings, trees[root], None)
722 # We really only need to cache the metadata that's necessary for visibility
723 # filtering. Anything else can be discarded to reduce memory consumption.
724 portdb._aux_cache_keys.clear()
725 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
727 reposplit = myreporoot.split(os.path.sep)
728 repolevel = len(reposplit)
730 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
731 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
732 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
733 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
734 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
735 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
736 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
738 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
740 # Make startdir relative to the canonical repodir, so that we can pass
741 # it to digestgen and it won't have to be canonicalized again.
745 startdir = normalize_path(mydir)
746 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
749 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.")
751 def repoman_getstatusoutput(cmd):
753 Implements an interface similar to getstatusoutput(), but with
754 customized unicode handling (see bug #310789) and without the shell.
756 args = portage.util.shlex_split(cmd)
757 encoding = _encodings['fs']
758 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
759 # Python 3.1 does not support bytes in Popen args.
760 args = [_unicode_encode(x,
761 encoding=encoding, errors='strict') for x in args]
762 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
763 stderr=subprocess.STDOUT)
764 output = portage._unicode_decode(proc.communicate()[0],
765 encoding=encoding, errors='strict')
766 if output and output[-1] == "\n":
767 # getstatusoutput strips one newline
769 return (proc.wait(), output)
771 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
773 Implements an interface similar to os.popen(), but with customized
774 unicode handling (see bug #310789) and without the shell.
777 __slots__ = ('_proc', '_stdout')
779 def __init__(self, cmd):
780 args = portage.util.shlex_split(cmd)
781 encoding = _encodings['fs']
782 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
783 # Python 3.1 does not support bytes in Popen args.
784 args = [_unicode_encode(x,
785 encoding=encoding, errors='strict') for x in args]
786 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
787 object.__setattr__(self, '_proc', proc)
788 object.__setattr__(self, '_stdout',
789 codecs.getreader(encoding)(proc.stdout, 'strict'))
791 def _get_target(self):
792 return object.__getattribute__(self, '_stdout')
794 __enter__ = _get_target
796 def __exit__(self, exc_type, exc_value, traceback):
797 proc = object.__getattribute__(self, '_proc')
801 class ProfileDesc(object):
802 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
803 def __init__(self, arch, status, sub_path, tree_path):
807 sub_path = normalize_path(sub_path.lstrip(os.sep))
808 self.sub_path = sub_path
809 self.tree_path = tree_path
811 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
813 self.abs_path = tree_path
818 return 'empty profile'
821 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
823 # get lists of valid keywords, licenses, and use
827 global_pmasklines = []
829 for path in portdb.porttrees:
831 liclist.update(os.listdir(os.path.join(path, "licenses")))
834 kwlist.update(portage.grabfile(os.path.join(path,
835 "profiles", "arch.list")))
837 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
843 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
845 expand_list = os.listdir(expand_desc_dir)
849 for fn in expand_list:
850 if not fn[-5:] == '.desc':
852 use_prefix = fn[:-5].lower() + '_'
853 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
856 uselist.add(use_prefix + x[0])
858 global_pmasklines.append(portage.util.grabfile_package(
859 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
861 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
863 desc_file = io.open(_unicode_encode(desc_path,
864 encoding=_encodings['fs'], errors='strict'),
865 mode='r', encoding=_encodings['repo.content'], errors='replace')
866 except EnvironmentError:
869 for i, x in enumerate(desc_file):
876 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
877 desc_path + " line %d" % (i + 1, ))
878 elif arch[0] not in kwlist:
879 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
880 desc_path + " line %d" % (i + 1, ))
881 elif arch[2] not in valid_profile_types:
882 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
883 desc_path + " line %d" % (i + 1, ))
884 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
885 if not os.path.isdir(profile_desc.abs_path):
887 "Invalid %s profile (%s) for arch %s in %s line %d",
888 arch[2], arch[1], arch[0], desc_path, i + 1)
891 os.path.join(profile_desc.abs_path, 'deprecated')):
893 profile_list.append(profile_desc)
896 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
897 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
899 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
900 global_pmaskdict = {}
901 for x in global_pmasklines:
902 global_pmaskdict.setdefault(x.cp, []).append(x)
903 del global_pmasklines
905 def has_global_mask(pkg):
906 mask_atoms = global_pmaskdict.get(pkg.cp)
910 if portage.dep.match_from_list(x, pkg_list):
914 # Ensure that profile sub_path attributes are unique. Process in reverse order
915 # so that profiles with duplicate sub_path from overlays will override
916 # profiles with the same sub_path from parent repos.
918 profile_list.reverse()
919 profile_sub_paths = set()
920 for prof in profile_list:
921 if prof.sub_path in profile_sub_paths:
923 profile_sub_paths.add(prof.sub_path)
924 profiles.setdefault(prof.arch, []).append(prof)
926 # Use an empty profile for checking dependencies of
927 # packages that have empty KEYWORDS.
928 prof = ProfileDesc('**', 'stable', '', '')
929 profiles.setdefault(prof.arch, []).append(prof)
931 for x in repoman_settings.archlist():
934 if x not in profiles:
935 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
936 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
937 print(red("up with the " + x + " team."))
940 liclist_deprecated = set()
941 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
942 liclist_deprecated.update(
943 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
946 logging.fatal("Couldn't find licenses?")
950 logging.fatal("Couldn't read KEYWORDS from arch.list")
954 logging.fatal("Couldn't find use.desc?")
959 # we are inside a category directory
960 catdir = reposplit[-1]
961 if catdir not in categories:
963 mydirlist = os.listdir(startdir)
965 if x == "CVS" or x.startswith("."):
967 if os.path.isdir(startdir + "/" + x):
968 scanlist.append(catdir + "/" + x)
969 repo_subdir = catdir + os.sep
972 if not os.path.isdir(startdir + "/" + x):
974 for y in os.listdir(startdir + "/" + x):
975 if y == "CVS" or y.startswith("."):
977 if os.path.isdir(startdir + "/" + x + "/" + y):
978 scanlist.append(x + "/" + y)
981 catdir = reposplit[-2]
982 if catdir not in categories:
984 scanlist.append(catdir + "/" + reposplit[-1])
985 repo_subdir = scanlist[-1] + os.sep
987 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
988 ' from the current working directory'
989 logging.critical(msg)
992 repo_subdir_len = len(repo_subdir)
995 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
997 def vcs_files_to_cps(vcs_file_iter):
999 Iterate over the given modified file paths returned from the vcs,
1000 and return a frozenset containing category/pn strings for each
1007 if reposplit[-2] in categories and \
1008 next(vcs_file_iter, None) is not None:
1009 modified_cps.append("/".join(reposplit[-2:]))
1011 elif repolevel == 2:
1012 category = reposplit[-1]
1013 if category in categories:
1014 for filename in vcs_file_iter:
1015 f_split = filename.split(os.sep)
1017 if len(f_split) > 2:
1018 modified_cps.append(category + "/" + f_split[1])
1022 for filename in vcs_file_iter:
1023 f_split = filename.split(os.sep)
1024 # ['.', category, pn, ...]
1025 if len(f_split) > 3 and f_split[1] in categories:
1026 modified_cps.append("/".join(f_split[1:3]))
1028 return frozenset(modified_cps)
1030 def git_supports_gpg_sign():
1031 status, cmd_output = \
1032 repoman_getstatusoutput("git --version")
1033 cmd_output = cmd_output.split()
1035 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1036 if version is not None:
1037 version = [int(x) for x in version.groups()]
1038 if version[0] > 1 or \
1039 (version[0] == 1 and version[1] > 7) or \
1040 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1044 def dev_keywords(profiles):
1046 Create a set of KEYWORDS values that exist in 'dev'
1047 profiles. These are used
1048 to trigger a message notifying the user when they might
1049 want to add the --include-dev option.
1052 for arch, arch_profiles in profiles.items():
1053 for prof in arch_profiles:
1054 arch_set = type_arch_map.get(prof.status)
1055 if arch_set is None:
1057 type_arch_map[prof.status] = arch_set
1060 dev_keywords = type_arch_map.get('dev', set())
1061 dev_keywords.update(['~' + arch for arch in dev_keywords])
1062 return frozenset(dev_keywords)
1064 dev_keywords = dev_keywords(profiles)
1073 xmllint_capable = False
1074 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1076 def fetch_metadata_dtd():
1078 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1079 metadata_dtd_ctime_interval.
1081 @return: True if successful, otherwise False
1085 metadata_dtd_st = None
1086 current_time = int(time.time())
1088 metadata_dtd_st = os.stat(metadata_dtd)
1089 except EnvironmentError as e:
1090 if e.errno not in (errno.ENOENT, errno.ESTALE):
1094 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1095 if abs(current_time - metadata_dtd_st.st_ctime) \
1096 < metadata_dtd_ctime_interval:
1101 print(green("***") + " the local copy of metadata.dtd " + \
1102 "needs to be refetched, doing that now")
1104 parsed_url = urlparse(metadata_dtd_uri)
1105 setting = 'FETCHCOMMAND_' + parsed_url.scheme.upper()
1106 fcmd = repoman_settings.get(setting)
1108 fcmd = repoman_settings.get('FETCHCOMMAND')
1110 logging.error("FETCHCOMMAND is unset")
1113 destdir = repoman_settings["DISTDIR"]
1114 fd, metadata_dtd_tmp = tempfile.mkstemp(
1115 prefix='metadata.dtd.', dir=destdir)
1119 if not portage.getbinpkg.file_get(metadata_dtd_uri,
1121 filename=os.path.basename(metadata_dtd_tmp)):
1122 logging.error("failed to fetch metadata.dtd from '%s'" %
1127 portage.util.apply_secpass_permissions(metadata_dtd_tmp,
1128 gid=portage.data.portage_gid, mode=0o664, mask=0o2)
1129 except portage.exception.PortageException:
1132 os.rename(metadata_dtd_tmp, metadata_dtd)
1135 os.unlink(metadata_dtd_tmp)
1141 if options.mode == "manifest":
1143 elif not find_binary('xmllint'):
1144 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1145 if options.xml_parse or repolevel == 3:
1146 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1149 if not fetch_metadata_dtd():
1151 # this can be problematic if xmllint changes their output
1152 xmllint_capable = True
1154 if options.mode == 'commit' and vcs:
1155 utilities.detect_vcs_conflicts(options, vcs)
1157 if options.mode == "manifest":
1159 elif options.pretend:
1160 print(green("\nRepoMan does a once-over of the neighborhood..."))
1162 print(green("\nRepoMan scours the neighborhood..."))
1165 modified_ebuilds = set()
1166 modified_changelogs = set()
1172 mycvstree = cvstree.getentries("./", recursive=1)
1173 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1174 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1175 if options.if_modified == "y":
1176 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1179 with repoman_popen("svn status") as f:
1180 svnstatus = f.readlines()
1181 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1182 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1183 if options.if_modified == "y":
1184 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1187 with repoman_popen("git diff-index --name-only "
1188 "--relative --diff-filter=M HEAD") as f:
1189 mychanged = f.readlines()
1190 mychanged = ["./" + elem[:-1] for elem in mychanged]
1192 with repoman_popen("git diff-index --name-only "
1193 "--relative --diff-filter=A HEAD") as f:
1194 mynew = f.readlines()
1195 mynew = ["./" + elem[:-1] for elem in mynew]
1196 if options.if_modified == "y":
1197 with repoman_popen("git diff-index --name-only "
1198 "--relative --diff-filter=D HEAD") as f:
1199 myremoved = f.readlines()
1200 myremoved = ["./" + elem[:-1] for elem in myremoved]
1203 with repoman_popen("bzr status -S .") as f:
1204 bzrstatus = f.readlines()
1205 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1206 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1207 if options.if_modified == "y":
1208 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")]
1211 with repoman_popen("hg status --no-status --modified .") as f:
1212 mychanged = f.readlines()
1213 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1214 with repoman_popen("hg status --no-status --added .") as f:
1215 mynew = f.readlines()
1216 mynew = ["./" + elem.rstrip() for elem in mynew]
1217 if options.if_modified == "y":
1218 with repoman_popen("hg status --no-status --removed .") as f:
1219 myremoved = f.readlines()
1220 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1223 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1224 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1225 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1226 if os.path.basename(x) == "ChangeLog")
1228 def vcs_new_changed(relative_path):
1229 for x in chain(mychanged, mynew):
1230 if x == relative_path:
1234 have_pmasked = False
1235 have_dev_keywords = False
1238 # NOTE: match-all caches are not shared due to potential
1239 # differences between profiles in _get_implicit_iuse.
1241 arch_xmatch_caches = {}
1242 shared_xmatch_caches = {"cp-list":{}}
1244 include_arches = None
1245 if options.include_arches:
1246 include_arches = set()
1247 include_arches.update(*[x.split() for x in options.include_arches])
1249 # Disable the "ebuild.notadded" check when not in commit mode and
1250 # running `svn status` in every package dir will be too expensive.
1252 check_ebuild_notadded = not \
1253 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1255 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1256 thirdpartymirrors = {}
1257 for k, v in repoman_settings.thirdpartymirrors().items():
1259 if not v.endswith("/"):
1261 thirdpartymirrors[v] = k
1263 class _XMLParser(xml.etree.ElementTree.XMLParser):
1265 def __init__(self, data, **kwargs):
1266 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1267 self._portage_data = data
1268 if hasattr(self, 'parser'):
1269 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1270 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1271 self._base_StartDoctypeDeclHandler = \
1272 self.parser.StartDoctypeDeclHandler
1273 self.parser.StartDoctypeDeclHandler = \
1274 self._portage_StartDoctypeDeclHandler
1276 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1277 if self._base_XmlDeclHandler is not None:
1278 self._base_XmlDeclHandler(version, encoding, standalone)
1279 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1281 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1282 has_internal_subset):
1283 if self._base_StartDoctypeDeclHandler is not None:
1284 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1285 has_internal_subset)
1286 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1288 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1290 Implements doctype() as required to avoid deprecation warnings with
1293 def doctype(self, name, pubid, system):
1297 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1298 except (EnvironmentError, ParseError, PermissionDenied) as e:
1300 except FileNotFound:
1301 # TODO: Download as we do for metadata.dtd, but add a way to
1302 # disable for non-gentoo repoman users who may not have herds.
1305 effective_scanlist = scanlist
1306 if options.if_modified == "y":
1307 effective_scanlist = sorted(vcs_files_to_cps(
1308 chain(mychanged, mynew, myremoved)))
1310 for x in effective_scanlist:
1311 #ebuilds and digests added to cvs respectively.
1312 logging.info("checking package %s" % x)
1313 # save memory by discarding xmatch caches from previous package(s)
1314 arch_xmatch_caches.clear()
1316 catdir, pkgdir = x.split("/")
1317 checkdir = repodir + "/" + x
1318 checkdir_relative = ""
1320 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1322 checkdir_relative = os.path.join(catdir, checkdir_relative)
1323 checkdir_relative = os.path.join(".", checkdir_relative)
1324 generated_manifest = False
1326 if options.mode == "manifest" or \
1327 (options.mode != 'manifest-check' and options.digest == 'y') or \
1328 options.mode in ('commit', 'fix') and not options.pretend:
1329 auto_assumed = set()
1330 fetchlist_dict = portage.FetchlistDict(checkdir,
1331 repoman_settings, portdb)
1332 if options.mode == 'manifest' and options.force:
1333 portage._doebuild_manifest_exempt_depend += 1
1335 distdir = repoman_settings['DISTDIR']
1336 mf = repoman_settings.repositories.get_repo_for_location(
1337 os.path.dirname(os.path.dirname(checkdir)))
1338 mf = mf.load_manifest(checkdir, distdir,
1339 fetchlist_dict=fetchlist_dict)
1340 mf.create(requiredDistfiles=None,
1341 assumeDistHashesAlways=True)
1342 for distfiles in fetchlist_dict.values():
1343 for distfile in distfiles:
1344 if os.path.isfile(os.path.join(distdir, distfile)):
1345 mf.fhashdict['DIST'].pop(distfile, None)
1347 auto_assumed.add(distfile)
1350 portage._doebuild_manifest_exempt_depend -= 1
1352 repoman_settings["O"] = checkdir
1354 generated_manifest = digestgen(
1355 mysettings=repoman_settings, myportdb=portdb)
1356 except portage.exception.PermissionDenied as e:
1357 generated_manifest = False
1358 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1359 level=logging.ERROR, noiselevel=-1)
1361 if not generated_manifest:
1362 print("Unable to generate manifest.")
1365 if options.mode == "manifest":
1366 if not dofail and options.force and auto_assumed and \
1367 'assume-digests' in repoman_settings.features:
1368 # Show which digests were assumed despite the --force option
1369 # being given. This output will already have been shown by
1370 # digestgen() if assume-digests is not enabled, so only show
1371 # it here if assume-digests is enabled.
1372 pkgs = list(fetchlist_dict)
1374 portage.writemsg_stdout(" digest.assumed" + \
1375 portage.output.colorize("WARN",
1376 str(len(auto_assumed)).rjust(18)) + "\n")
1378 fetchmap = fetchlist_dict[cpv]
1379 pf = portage.catsplit(cpv)[1]
1380 for distfile in sorted(fetchmap):
1381 if distfile in auto_assumed:
1382 portage.writemsg_stdout(
1383 " %s::%s\n" % (pf, distfile))
1388 if not generated_manifest:
1389 repoman_settings['O'] = checkdir
1390 repoman_settings['PORTAGE_QUIET'] = '1'
1391 if not portage.digestcheck([], repoman_settings, strict=1):
1392 stats["manifest.bad"] += 1
1393 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1394 repoman_settings.pop('PORTAGE_QUIET', None)
1396 if options.mode == 'manifest-check':
1399 checkdirlist = os.listdir(checkdir)
1403 for y in checkdirlist:
1404 if (y in no_exec or y.endswith(".ebuild")) and \
1405 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1406 stats["file.executable"] += 1
1407 fails["file.executable"].append(os.path.join(checkdir, y))
1408 if y.endswith(".ebuild"):
1410 ebuildlist.append(pf)
1411 cpv = "%s/%s" % (catdir, pf)
1413 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1416 stats["ebuild.syntax"] += 1
1417 fails["ebuild.syntax"].append(os.path.join(x, y))
1421 stats["ebuild.output"] += 1
1422 fails["ebuild.output"].append(os.path.join(x, y))
1424 if not portage.eapi_is_supported(myaux["EAPI"]):
1426 stats["EAPI.unsupported"] += 1
1427 fails["EAPI.unsupported"].append(os.path.join(x, y))
1429 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1430 root_config=root_config, type_name="ebuild")
1434 if len(pkgs) != len(ebuildlist):
1435 # If we can't access all the metadata then it's totally unsafe to
1436 # commit since there's no way to generate a correct Manifest.
1437 # Do not try to do any more QA checks on this package since missing
1438 # metadata leads to false positives for several checks, and false
1439 # positives confuse users.
1443 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1444 ebuildlist = sorted(pkgs.values())
1445 ebuildlist = [pkg.pf for pkg in ebuildlist]
1447 for y in checkdirlist:
1448 index = repo_config.find_invalid_path_char(y)
1450 y_relative = os.path.join(checkdir_relative, y)
1451 if vcs is not None and not vcs_new_changed(y_relative):
1452 # If the file isn't in the VCS new or changed set, then
1453 # assume that it's an irrelevant temporary file (Manifest
1454 # entries are not generated for file names containing
1455 # prohibited characters). See bug #406877.
1458 stats["file.name"] += 1
1459 fails["file.name"].append("%s/%s: char '%s'" % \
1460 (checkdir, y, y[index]))
1462 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1467 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1468 encoding=_encodings['fs'], errors='strict'),
1469 mode='r', encoding=_encodings['repo.content'])
1472 except UnicodeDecodeError as ue:
1473 stats["file.UTF8"] += 1
1474 s = ue.object[:ue.start]
1478 s = s[s.rfind("\n") + 1:]
1479 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1484 if vcs in ("git", "hg") and check_ebuild_notadded:
1486 myf = repoman_popen("git ls-files --others %s" % \
1487 (portage._shell_quote(checkdir_relative),))
1489 myf = repoman_popen("hg status --no-status --unknown %s" % \
1490 (portage._shell_quote(checkdir_relative),))
1492 if l[:-1][-7:] == ".ebuild":
1493 stats["ebuild.notadded"] += 1
1494 fails["ebuild.notadded"].append(
1495 os.path.join(x, os.path.basename(l[:-1])))
1498 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1501 myf = open(checkdir + "/CVS/Entries", "r")
1503 myf = repoman_popen("svn status --depth=files --verbose " +
1504 portage._shell_quote(checkdir))
1506 myf = repoman_popen("bzr ls -v --kind=file " +
1507 portage._shell_quote(checkdir))
1508 myl = myf.readlines()
1514 splitl = l[1:].split("/")
1517 if splitl[0][-7:] == ".ebuild":
1518 eadded.append(splitl[0][:-7])
1523 # tree conflict, new in subversion 1.6
1526 if l[-7:] == ".ebuild":
1527 eadded.append(os.path.basename(l[:-7]))
1532 if l[-7:] == ".ebuild":
1533 eadded.append(os.path.basename(l[:-7]))
1535 myf = repoman_popen("svn status " +
1536 portage._shell_quote(checkdir))
1537 myl = myf.readlines()
1541 l = l.rstrip().split(' ')[-1]
1542 if l[-7:] == ".ebuild":
1543 eadded.append(os.path.basename(l[:-7]))
1546 stats["CVS/Entries.IO_error"] += 1
1547 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1552 mf = repoman_settings.repositories.get_repo_for_location(
1553 os.path.dirname(os.path.dirname(checkdir)))
1554 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1555 mydigests = mf.getTypeDigests("DIST")
1557 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1559 src_uri_error = False
1560 for mykey in fetchlist_dict:
1562 myfiles_all.extend(fetchlist_dict[mykey])
1563 except portage.exception.InvalidDependString as e:
1564 src_uri_error = True
1566 portdb.aux_get(mykey, ["SRC_URI"])
1568 # This will be reported as an "ebuild.syntax" error.
1571 stats["SRC_URI.syntax"] += 1
1572 fails["SRC_URI.syntax"].append(
1573 "%s.ebuild SRC_URI: %s" % (mykey, e))
1575 if not src_uri_error:
1576 # This test can produce false positives if SRC_URI could not
1577 # be parsed for one or more ebuilds. There's no point in
1578 # producing a false error here since the root cause will
1579 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1580 # or "ebuild.sytax".
1581 myfiles_all = set(myfiles_all)
1582 for entry in mydigests:
1583 if entry not in myfiles_all:
1584 stats["digest.unused"] += 1
1585 fails["digest.unused"].append(checkdir + "::" + entry)
1586 for entry in myfiles_all:
1587 if entry not in mydigests:
1588 stats["digest.missing"] += 1
1589 fails["digest.missing"].append(checkdir + "::" + entry)
1592 if os.path.exists(checkdir + "/files"):
1593 filesdirlist = os.listdir(checkdir + "/files")
1595 # recurse through files directory
1596 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1598 y = filesdirlist.pop(0)
1599 relative_path = os.path.join(x, "files", y)
1600 full_path = os.path.join(repodir, relative_path)
1602 mystat = os.stat(full_path)
1603 except OSError as oe:
1605 # don't worry about it. it likely was removed via fix above.
1609 if S_ISDIR(mystat.st_mode):
1610 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1611 if y == "CVS" or y == ".svn":
1613 for z in os.listdir(checkdir + "/files/" + y):
1614 if z == "CVS" or z == ".svn":
1616 filesdirlist.append(y + "/" + z)
1617 # Current policy is no files over 20 KiB, these are the checks. File size between
1618 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1619 elif mystat.st_size > 61440:
1620 stats["file.size.fatal"] += 1
1621 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1622 elif mystat.st_size > 20480:
1623 stats["file.size"] += 1
1624 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1626 index = repo_config.find_invalid_path_char(y)
1628 y_relative = os.path.join(checkdir_relative, "files", y)
1629 if vcs is not None and not vcs_new_changed(y_relative):
1630 # If the file isn't in the VCS new or changed set, then
1631 # assume that it's an irrelevant temporary file (Manifest
1632 # entries are not generated for file names containing
1633 # prohibited characters). See bug #406877.
1636 stats["file.name"] += 1
1637 fails["file.name"].append("%s/files/%s: char '%s'" % \
1638 (checkdir, y, y[index]))
1641 if check_changelog and "ChangeLog" not in checkdirlist:
1642 stats["changelog.missing"] += 1
1643 fails["changelog.missing"].append(x + "/ChangeLog")
1646 # metadata.xml file check
1647 if "metadata.xml" not in checkdirlist:
1648 stats["metadata.missing"] += 1
1649 fails["metadata.missing"].append(x + "/metadata.xml")
1650 # metadata.xml parse check
1652 metadata_bad = False
1654 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1656 # read metadata.xml into memory
1658 _metadata_xml = xml.etree.ElementTree.parse(
1659 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1660 encoding=_encodings['fs'], errors='strict'),
1662 except (ExpatError, SyntaxError, EnvironmentError) as e:
1664 stats["metadata.bad"] += 1
1665 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1668 if not hasattr(xml_parser, 'parser') or \
1669 sys.hexversion < 0x2070000 or \
1670 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1671 # doctype is not parsed with python 2.6 or 3.1
1674 if "XML_DECLARATION" not in xml_info:
1675 stats["metadata.bad"] += 1
1676 fails["metadata.bad"].append("%s/metadata.xml: "
1677 "xml declaration is missing on first line, "
1678 "should be '%s'" % (x, metadata_xml_declaration))
1680 xml_version, xml_encoding, xml_standalone = \
1681 xml_info["XML_DECLARATION"]
1682 if xml_encoding is None or \
1683 xml_encoding.upper() != metadata_xml_encoding:
1684 stats["metadata.bad"] += 1
1685 if xml_encoding is None:
1686 encoding_problem = "but it is undefined"
1688 encoding_problem = "not '%s'" % xml_encoding
1689 fails["metadata.bad"].append("%s/metadata.xml: "
1690 "xml declaration encoding should be '%s', %s" %
1691 (x, metadata_xml_encoding, encoding_problem))
1693 if "DOCTYPE" not in xml_info:
1695 stats["metadata.bad"] += 1
1696 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1697 "DOCTYPE is missing"))
1699 doctype_name, doctype_system, doctype_pubid = \
1701 if doctype_system != metadata_dtd_uri:
1702 stats["metadata.bad"] += 1
1703 if doctype_system is None:
1704 system_problem = "but it is undefined"
1706 system_problem = "not '%s'" % doctype_system
1707 fails["metadata.bad"].append("%s/metadata.xml: "
1708 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1709 (x, metadata_dtd_uri, system_problem))
1711 if doctype_name != metadata_doctype_name:
1712 stats["metadata.bad"] += 1
1713 fails["metadata.bad"].append("%s/metadata.xml: "
1714 "DOCTYPE: name should be '%s', not '%s'" %
1715 (x, metadata_doctype_name, doctype_name))
1717 # load USE flags from metadata.xml
1719 musedict = utilities.parse_metadata_use(_metadata_xml)
1720 except portage.exception.ParseError as e:
1722 stats["metadata.bad"] += 1
1723 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1725 for atom in chain(*musedict.values()):
1730 except InvalidAtom as e:
1731 stats["metadata.bad"] += 1
1732 fails["metadata.bad"].append(
1733 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1736 stats["metadata.bad"] += 1
1737 fails["metadata.bad"].append(
1738 ("%s/metadata.xml: Atom contains "
1739 "unexpected cat/pn: %s") % (x, atom))
1741 # Run other metadata.xml checkers
1743 utilities.check_metadata(_metadata_xml, herd_base)
1744 except (utilities.UnknownHerdsError, ) as e:
1746 stats["metadata.bad"] += 1
1747 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1750 #Only carry out if in package directory or check forced
1751 if xmllint_capable and not metadata_bad:
1752 # xmlint can produce garbage output even on success, so only dump
1753 # the ouput when it fails.
1754 st, out = repoman_getstatusoutput(
1755 "xmllint --nonet --noout --dtdvalid %s %s" % \
1756 (portage._shell_quote(metadata_dtd),
1757 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1759 print(red("!!!") + " metadata.xml is invalid:")
1760 for z in out.splitlines():
1761 print(red("!!! ") + z)
1762 stats["metadata.bad"] += 1
1763 fails["metadata.bad"].append(x + "/metadata.xml")
1766 muselist = frozenset(musedict)
1768 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1769 changelog_modified = changelog_path in modified_changelogs
1771 # detect unused local USE-descriptions
1772 used_useflags = set()
1774 for y in ebuildlist:
1775 relative_path = os.path.join(x, y + ".ebuild")
1776 full_path = os.path.join(repodir, relative_path)
1777 ebuild_path = y + ".ebuild"
1779 ebuild_path = os.path.join(pkgdir, ebuild_path)
1781 ebuild_path = os.path.join(catdir, ebuild_path)
1782 ebuild_path = os.path.join(".", ebuild_path)
1783 if check_changelog and not changelog_modified \
1784 and ebuild_path in new_ebuilds:
1785 stats['changelog.ebuildadded'] += 1
1786 fails['changelog.ebuildadded'].append(relative_path)
1788 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1789 #ebuild not added to vcs
1790 stats["ebuild.notadded"] += 1
1791 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1792 myesplit = portage.pkgsplit(y)
1793 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1794 or pv_toolong_re.search(myesplit[1]) \
1795 or pv_toolong_re.search(myesplit[2]):
1796 stats["ebuild.invalidname"] += 1
1797 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1799 elif myesplit[0] != pkgdir:
1800 print(pkgdir, myesplit[0])
1801 stats["ebuild.namenomatch"] += 1
1802 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1809 for k, msgs in pkg.invalid.items():
1812 fails[k].append("%s: %s" % (relative_path, msg))
1815 myaux = pkg._metadata
1816 eapi = myaux["EAPI"]
1817 inherited = pkg.inherited
1818 live_ebuild = live_eclasses.intersection(inherited)
1820 if repo_config.eapi_is_banned(eapi):
1821 stats["repo.eapi.banned"] += 1
1822 fails["repo.eapi.banned"].append(
1823 "%s: %s" % (relative_path, eapi))
1825 elif repo_config.eapi_is_deprecated(eapi):
1826 stats["repo.eapi.deprecated"] += 1
1827 fails["repo.eapi.deprecated"].append(
1828 "%s: %s" % (relative_path, eapi))
1830 for k, v in myaux.items():
1831 if not isinstance(v, basestring):
1833 m = non_ascii_re.search(v)
1835 stats["variable.invalidchar"] += 1
1836 fails["variable.invalidchar"].append(
1837 ("%s: %s variable contains non-ASCII " + \
1838 "character at position %s") % \
1839 (relative_path, k, m.start() + 1))
1841 if not src_uri_error:
1842 # Check that URIs don't reference a server from thirdpartymirrors.
1843 for uri in portage.dep.use_reduce( \
1844 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1845 contains_mirror = False
1846 for mirror, mirror_alias in thirdpartymirrors.items():
1847 if uri.startswith(mirror):
1848 contains_mirror = True
1850 if not contains_mirror:
1853 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1854 stats["SRC_URI.mirror"] += 1
1855 fails["SRC_URI.mirror"].append(
1856 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1857 (relative_path, mirror, new_uri))
1859 if myaux.get("PROVIDE"):
1860 stats["virtual.oldstyle"] += 1
1861 fails["virtual.oldstyle"].append(relative_path)
1863 for pos, missing_var in enumerate(missingvars):
1864 if not myaux.get(missing_var):
1865 if catdir == "virtual" and \
1866 missing_var in ("HOMEPAGE", "LICENSE"):
1868 if live_ebuild and missing_var == "KEYWORDS":
1870 myqakey = missingvars[pos] + ".missing"
1872 fails[myqakey].append(x + "/" + y + ".ebuild")
1874 if catdir == "virtual":
1875 for var in ("HOMEPAGE", "LICENSE"):
1877 myqakey = var + ".virtual"
1879 fails[myqakey].append(relative_path)
1881 # 14 is the length of DESCRIPTION=""
1882 if len(myaux['DESCRIPTION']) > max_desc_len:
1883 stats['DESCRIPTION.toolong'] += 1
1884 fails['DESCRIPTION.toolong'].append(
1885 "%s: DESCRIPTION is %d characters (max %d)" % \
1886 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1888 keywords = myaux["KEYWORDS"].split()
1889 stable_keywords = []
1890 for keyword in keywords:
1891 if not keyword.startswith("~") and \
1892 not keyword.startswith("-"):
1893 stable_keywords.append(keyword)
1895 if ebuild_path in new_ebuilds and catdir != "virtual":
1896 stable_keywords.sort()
1897 stats["KEYWORDS.stable"] += 1
1898 fails["KEYWORDS.stable"].append(
1899 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1900 " ".join(stable_keywords))
1902 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1903 if not kw.startswith("-"))
1905 previous_keywords = slot_keywords.get(pkg.slot)
1906 if previous_keywords is None:
1907 slot_keywords[pkg.slot] = set()
1908 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1909 dropped_keywords = previous_keywords.difference(ebuild_archs)
1910 if dropped_keywords:
1911 stats["KEYWORDS.dropped"] += 1
1912 fails["KEYWORDS.dropped"].append(
1913 relative_path + ": %s" % \
1914 " ".join(sorted(dropped_keywords)))
1916 slot_keywords[pkg.slot].update(ebuild_archs)
1918 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1919 if "-*" in keywords:
1927 stats["KEYWORDS.stupid"] += 1
1928 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1931 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1932 not be allowed to be marked stable
1934 if live_ebuild and repo_config.name == "gentoo":
1935 bad_stable_keywords = []
1936 for keyword in keywords:
1937 if not keyword.startswith("~") and \
1938 not keyword.startswith("-"):
1939 bad_stable_keywords.append(keyword)
1941 if bad_stable_keywords:
1942 stats["LIVEVCS.stable"] += 1
1943 fails["LIVEVCS.stable"].append(
1944 x + "/" + y + ".ebuild with stable keywords:%s " % \
1945 bad_stable_keywords)
1946 del bad_stable_keywords
1948 if keywords and not has_global_mask(pkg):
1949 stats["LIVEVCS.unmasked"] += 1
1950 fails["LIVEVCS.unmasked"].append(relative_path)
1952 if options.ignore_arches:
1953 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1954 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1957 for keyword in keywords:
1958 if keyword[0] == "-":
1960 elif keyword[0] == "~":
1963 for expanded_arch in profiles:
1964 if expanded_arch == "**":
1966 arches.add((keyword, expanded_arch,
1967 (expanded_arch, "~" + expanded_arch)))
1969 arches.add((keyword, arch, (arch, keyword)))
1972 for expanded_arch in profiles:
1973 if expanded_arch == "**":
1975 arches.add((keyword, expanded_arch,
1978 arches.add((keyword, keyword, (keyword,)))
1980 # Use an empty profile for checking dependencies of
1981 # packages that have empty KEYWORDS.
1982 arches.add(('**', '**', ('**',)))
1984 unknown_pkgs = set()
1985 baddepsyntax = False
1986 badlicsyntax = False
1987 badprovsyntax = False
1988 catpkg = catdir + "/" + y
1990 inherited_java_eclass = "java-pkg-2" in inherited or \
1991 "java-pkg-opt-2" in inherited
1992 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1993 operator_tokens = set(["||", "(", ")"])
1994 type_list, badsyntax = [], []
1995 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
1996 mydepstr = myaux[mytype]
1998 buildtime = mytype in Package._buildtime_keys
1999 runtime = mytype in Package._runtime_keys
2001 if mytype.endswith("DEPEND"):
2002 token_class = portage.dep.Atom
2005 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
2006 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
2007 except portage.exception.InvalidDependString as e:
2009 badsyntax.append(str(e))
2011 if atoms and mytype.endswith("DEPEND"):
2013 "test?" in mydepstr.split():
2014 stats[mytype + '.suspect'] += 1
2015 fails[mytype + '.suspect'].append(relative_path + \
2016 ": 'test?' USE conditional in %s" % mytype)
2022 # Skip dependency.unknown for blockers, so that we
2023 # don't encourage people to remove necessary blockers,
2024 # as discussed in bug #382407.
2025 if atom.blocker is None and \
2026 not portdb.xmatch("match-all", atom) and \
2027 not atom.cp.startswith("virtual/"):
2028 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2030 is_blocker = atom.blocker
2032 if catdir != "virtual":
2033 if not is_blocker and \
2034 atom.cp in suspect_virtual:
2035 stats['virtual.suspect'] += 1
2036 fails['virtual.suspect'].append(
2038 ": %s: consider using '%s' instead of '%s'" %
2039 (mytype, suspect_virtual[atom.cp], atom))
2042 not is_blocker and \
2043 not inherited_java_eclass and \
2044 atom.cp == "virtual/jdk":
2045 stats['java.eclassesnotused'] += 1
2046 fails['java.eclassesnotused'].append(relative_path)
2047 elif buildtime and \
2048 not is_blocker and \
2049 not inherited_wxwidgets_eclass and \
2050 atom.cp == "x11-libs/wxGTK":
2051 stats['wxwidgets.eclassnotused'] += 1
2052 fails['wxwidgets.eclassnotused'].append(
2053 (relative_path + ": %ss on x11-libs/wxGTK"
2054 " without inheriting wxwidgets.eclass") % mytype)
2056 if not is_blocker and \
2057 atom.cp in suspect_rdepend:
2058 stats[mytype + '.suspect'] += 1
2059 fails[mytype + '.suspect'].append(
2060 relative_path + ": '%s'" % atom)
2062 if atom.operator == "~" and \
2063 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2064 qacat = 'dependency.badtilde'
2066 fails[qacat].append(
2067 (relative_path + ": %s uses the ~ operator"
2068 " with a non-zero revision:" + \
2069 " '%s'") % (mytype, atom))
2071 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2073 for m, b in zip(type_list, badsyntax):
2074 if m.endswith("DEPEND"):
2075 qacat = "dependency.syntax"
2077 qacat = m + ".syntax"
2079 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2081 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2082 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2083 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2084 badlicsyntax = badlicsyntax > 0
2085 badprovsyntax = badprovsyntax > 0
2087 # uselist checks - global
2090 for myflag in myaux["IUSE"].split():
2091 flag_name = myflag.lstrip("+-")
2092 used_useflags.add(flag_name)
2093 if myflag != flag_name:
2094 default_use.append(myflag)
2095 if flag_name not in uselist:
2096 myuse.append(flag_name)
2098 # uselist checks - metadata
2099 for mypos in range(len(myuse)-1, -1, -1):
2100 if myuse[mypos] and (myuse[mypos] in muselist):
2103 if default_use and not eapi_has_iuse_defaults(eapi):
2104 for myflag in default_use:
2105 stats['EAPI.incompatible'] += 1
2106 fails['EAPI.incompatible'].append(
2107 (relative_path + ": IUSE defaults" + \
2108 " not supported with EAPI='%s':" + \
2109 " '%s'") % (eapi, myflag))
2111 for mypos in range(len(myuse)):
2112 stats["IUSE.invalid"] += 1
2113 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2116 if not badlicsyntax:
2117 # Parse the LICENSE variable, remove USE conditions and
2119 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2120 # Check each entry to ensure that it exists in PORTDIR's
2121 # license directory.
2122 for lic in licenses:
2123 # Need to check for "||" manually as no portage
2124 # function will remove it without removing values.
2125 if lic not in liclist and lic != "||":
2126 stats["LICENSE.invalid"] += 1
2127 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2128 elif lic in liclist_deprecated:
2129 stats["LICENSE.deprecated"] += 1
2130 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2133 myuse = myaux["KEYWORDS"].split()
2135 if mykey not in ("-*", "*", "~*"):
2137 if myskey[:1] == "-":
2139 if myskey[:1] == "~":
2141 if myskey not in kwlist:
2142 stats["KEYWORDS.invalid"] += 1
2143 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2144 elif myskey not in profiles:
2145 stats["KEYWORDS.invalid"] += 1
2146 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2151 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2152 except portage.exception.InvalidDependString as e:
2153 stats["RESTRICT.syntax"] += 1
2154 fails["RESTRICT.syntax"].append(
2155 "%s: RESTRICT: %s" % (relative_path, e))
2158 myrestrict = set(myrestrict)
2159 mybadrestrict = myrestrict.difference(valid_restrict)
2161 stats["RESTRICT.invalid"] += len(mybadrestrict)
2162 for mybad in mybadrestrict:
2163 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2165 required_use = myaux["REQUIRED_USE"]
2167 if not eapi_has_required_use(eapi):
2168 stats['EAPI.incompatible'] += 1
2169 fails['EAPI.incompatible'].append(
2170 relative_path + ": REQUIRED_USE" + \
2171 " not supported with EAPI='%s'" % (eapi,))
2173 portage.dep.check_required_use(required_use, (),
2174 pkg.iuse.is_valid_flag, eapi=eapi)
2175 except portage.exception.InvalidDependString as e:
2176 stats["REQUIRED_USE.syntax"] += 1
2177 fails["REQUIRED_USE.syntax"].append(
2178 "%s: REQUIRED_USE: %s" % (relative_path, e))
2182 relative_path = os.path.join(x, y + ".ebuild")
2183 full_path = os.path.join(repodir, relative_path)
2184 if not vcs_preserves_mtime:
2185 if ebuild_path not in new_ebuilds and \
2186 ebuild_path not in modified_ebuilds:
2189 # All ebuilds should have utf_8 encoding.
2190 f = io.open(_unicode_encode(full_path,
2191 encoding=_encodings['fs'], errors='strict'),
2192 mode='r', encoding=_encodings['repo.content'])
2194 for check_name, e in run_checks(f, pkg):
2195 stats[check_name] += 1
2196 fails[check_name].append(relative_path + ': %s' % e)
2199 except UnicodeDecodeError:
2200 # A file.UTF8 failure will have already been recorded above.
2204 # The dep_check() calls are the most expensive QA test. If --force
2205 # is enabled, there's no point in wasting time on these since the
2206 # user is intent on forcing the commit anyway.
2209 relevant_profiles = []
2210 for keyword, arch, groups in arches:
2211 if arch not in profiles:
2212 # A missing profile will create an error further down
2213 # during the KEYWORDS verification.
2216 if include_arches is not None:
2217 if arch not in include_arches:
2220 relevant_profiles.extend((keyword, groups, prof)
2221 for prof in profiles[arch])
2224 return item[2].sub_path
2226 relevant_profiles.sort(key=sort_key)
2228 for keyword, groups, prof in relevant_profiles:
2230 if prof.status not in ("stable", "dev") or \
2231 prof.status == "dev" and not options.include_dev:
2234 dep_settings = arch_caches.get(prof.sub_path)
2235 if dep_settings is None:
2236 dep_settings = portage.config(
2237 config_profile_path=prof.abs_path,
2238 config_incrementals=repoman_incrementals,
2239 config_root=config_root,
2241 _unmatched_removal=options.unmatched_removal,
2243 dep_settings.categories = repoman_settings.categories
2244 if options.without_mask:
2245 dep_settings._mask_manager_obj = \
2246 copy.deepcopy(dep_settings._mask_manager)
2247 dep_settings._mask_manager._pmaskdict.clear()
2248 arch_caches[prof.sub_path] = dep_settings
2250 xmatch_cache_key = (prof.sub_path, tuple(groups))
2251 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2255 xcache = portdb.xcache
2256 xcache.update(shared_xmatch_caches)
2257 arch_xmatch_caches[xmatch_cache_key] = xcache
2259 trees[root]["porttree"].settings = dep_settings
2260 portdb.settings = dep_settings
2261 portdb.xcache = xcache
2263 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2264 # just in case, prevent config.reset() from nuking these.
2265 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2267 # This attribute is used in dbapi._match_use() to apply
2268 # use.stable.{mask,force} settings based on the stable
2269 # status of the parent package. This is required in order
2270 # for USE deps of unstable packages to be resolved correctly,
2271 # since otherwise use.stable.{mask,force} settings of
2272 # dependencies may conflict (see bug #456342).
2273 dep_settings._parent_stable = dep_settings._isStable(pkg)
2275 # Handle package.use*.{force,mask) calculation, for use
2277 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2278 pkg, stable=dep_settings._parent_stable)
2279 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2280 pkg, stable=dep_settings._parent_stable)
2282 if not baddepsyntax:
2283 ismasked = not ebuild_archs or \
2284 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2286 if not have_pmasked:
2287 have_pmasked = bool(dep_settings._getMaskAtom(
2288 pkg.cpv, pkg._metadata))
2289 if options.ignore_masked:
2291 #we are testing deps for a masked package; give it some lee-way
2293 matchmode = "minimum-all"
2296 matchmode = "minimum-visible"
2298 if not have_dev_keywords:
2299 have_dev_keywords = \
2300 bool(dev_keywords.intersection(keywords))
2302 if prof.status == "dev":
2303 suffix = suffix + "indev"
2305 for mytype in Package._dep_keys:
2307 mykey = "dependency.bad" + suffix
2308 myvalue = myaux[mytype]
2312 success, atoms = portage.dep_check(myvalue, portdb,
2313 dep_settings, use="all", mode=matchmode,
2319 # Don't bother with dependency.unknown for
2320 # cases in which *DEPEND.bad is triggered.
2322 # dep_check returns all blockers and they
2323 # aren't counted for *DEPEND.bad, so we
2325 if not atom.blocker:
2326 unknown_pkgs.discard(
2327 (mytype, atom.unevaluated_atom))
2329 if not prof.sub_path:
2330 # old-style virtuals currently aren't
2331 # resolvable with empty profile, since
2332 # 'virtuals' mappings are unavailable
2333 # (it would be expensive to search
2334 # for PROVIDE in all ebuilds)
2335 atoms = [atom for atom in atoms if not \
2336 (atom.cp.startswith('virtual/') and \
2337 not portdb.cp_list(atom.cp))]
2339 #we have some unsolvable deps
2340 #remove ! deps, which always show up as unsatisfiable
2341 atoms = [str(atom.unevaluated_atom) \
2342 for atom in atoms if not atom.blocker]
2344 #if we emptied out our list, continue:
2348 fails[mykey].append("%s: %s: %s(%s) %s" % \
2349 (relative_path, mytype, keyword,
2353 fails[mykey].append("%s: %s: %s(%s) %s" % \
2354 (relative_path, mytype, keyword,
2357 if not baddepsyntax and unknown_pkgs:
2359 for mytype, atom in unknown_pkgs:
2360 type_map.setdefault(mytype, set()).add(atom)
2361 for mytype, atoms in type_map.items():
2362 stats["dependency.unknown"] += 1
2363 fails["dependency.unknown"].append("%s: %s: %s" %
2364 (relative_path, mytype, ", ".join(sorted(atoms))))
2366 # check if there are unused local USE-descriptions in metadata.xml
2367 # (unless there are any invalids, to avoid noise)
2369 for myflag in muselist.difference(used_useflags):
2370 stats["metadata.warning"] += 1
2371 fails["metadata.warning"].append(
2372 "%s/metadata.xml: unused local USE-description: '%s'" % \
2375 if options.if_modified == "y" and len(effective_scanlist) < 1:
2376 logging.warn("--if-modified is enabled, but no modified packages were found!")
2378 if options.mode == "manifest":
2381 # dofail will be set to 1 if we have failed in at least one non-warning category
2383 # dowarn will be set to 1 if we tripped any warnings
2385 # dofull will be set if we should print a "repoman full" informational message
2386 dofull = options.mode != 'full'
2392 if x not in qawarnings:
2396 (dowarn and not (options.quiet or options.mode == "scan")):
2399 # Save QA output so that it can be conveniently displayed
2400 # in $EDITOR while the user creates a commit message.
2401 # Otherwise, the user would not be able to see this output
2402 # once the editor has taken over the screen.
2403 qa_output = io.StringIO()
2404 style_file = ConsoleStyleFile(sys.stdout)
2405 if options.mode == 'commit' and \
2406 (not commitmessage or not commitmessage.strip()):
2407 style_file.write_listener = qa_output
2408 console_writer = StyleWriter(file=style_file, maxcol=9999)
2409 console_writer.style_listener = style_file.new_styles
2411 f = formatter.AbstractFormatter(console_writer)
2413 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2416 del console_writer, f, style_file
2417 qa_output = qa_output.getvalue()
2418 qa_output = qa_output.splitlines(True)
2420 suggest_ignore_masked = False
2421 suggest_include_dev = False
2423 if have_pmasked and not (options.without_mask or options.ignore_masked):
2424 suggest_ignore_masked = True
2425 if have_dev_keywords and not options.include_dev:
2426 suggest_include_dev = True
2428 if suggest_ignore_masked or suggest_include_dev:
2430 if suggest_ignore_masked:
2431 print(bold("Note: use --without-mask to check " + \
2432 "KEYWORDS on dependencies of masked packages"))
2434 if suggest_include_dev:
2435 print(bold("Note: use --include-dev (-d) to check " + \
2436 "dependencies for 'dev' profiles"))
2439 if options.mode != 'commit':
2441 print(bold("Note: type \"repoman full\" for a complete listing."))
2442 if dowarn and not dofail:
2443 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.\"")
2445 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2447 print(bad("Please fix these important QA issues first."))
2448 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2451 if dofail and can_force and options.force and not options.pretend:
2452 print(green("RepoMan sez:") + \
2453 " \"You want to commit even with these QA issues?\n" + \
2454 " I'll take it this time, but I'm not happy.\"\n")
2456 if options.force and not can_force:
2457 print(bad("The --force option has been disabled due to extraordinary issues."))
2458 print(bad("Please fix these important QA issues first."))
2459 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2463 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2468 myvcstree = portage.cvstree.getentries("./", recursive=1)
2469 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2470 except SystemExit as e:
2471 raise # TODO propagate this
2473 err("Error retrieving CVS tree; exiting.")
2476 with repoman_popen("svn status --no-ignore") as f:
2477 svnstatus = f.readlines()
2478 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2479 except SystemExit as e:
2480 raise # TODO propagate this
2482 err("Error retrieving SVN info; exiting.")
2484 # get list of files not under version control or missing
2485 myf = repoman_popen("git ls-files --others")
2486 myunadded = ["./" + elem[:-1] for elem in myf]
2490 with repoman_popen("bzr status -S .") as f:
2491 bzrstatus = f.readlines()
2492 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2493 except SystemExit as e:
2494 raise # TODO propagate this
2496 err("Error retrieving bzr info; exiting.")
2498 with repoman_popen("hg status --no-status --unknown .") as f:
2499 myunadded = f.readlines()
2500 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2502 # Mercurial doesn't handle manually deleted files as removed from
2503 # the repository, so the user need to remove them before commit,
2504 # using "hg remove [FILES]"
2505 with repoman_popen("hg status --no-status --deleted .") as f:
2506 mydeleted = f.readlines()
2507 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2512 for x in range(len(myunadded)-1, -1, -1):
2513 xs = myunadded[x].split("/")
2514 if xs[-1] == "files":
2515 print("!!! files dir is not added! Please correct this.")
2517 elif xs[-1] == "Manifest":
2518 # It's a manifest... auto add
2519 myautoadd += [myunadded[x]]
2523 print(red("!!! The following files are in your local tree but are not added to the master"))
2524 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2531 if vcs == "hg" and mydeleted:
2532 print(red("!!! The following files are removed manually from your local tree but are not"))
2533 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2541 mycvstree = cvstree.getentries("./", recursive=1)
2542 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2543 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2544 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2545 bin_blob_pattern = re.compile("^-kb$")
2546 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2547 recursive=1, basedir="./"))
2550 with repoman_popen("svn status") as f:
2551 svnstatus = f.readlines()
2552 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2553 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2554 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2556 # Subversion expands keywords specified in svn:keywords properties.
2557 with repoman_popen("svn propget -R svn:keywords") as f:
2558 props = f.readlines()
2559 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2560 for prop in props if " - " in prop)
2563 with repoman_popen("git diff-index --name-only "
2564 "--relative --diff-filter=M HEAD") as f:
2565 mychanged = f.readlines()
2566 mychanged = ["./" + elem[:-1] for elem in mychanged]
2568 with repoman_popen("git diff-index --name-only "
2569 "--relative --diff-filter=A HEAD") as f:
2570 mynew = f.readlines()
2571 mynew = ["./" + elem[:-1] for elem in mynew]
2573 with repoman_popen("git diff-index --name-only "
2574 "--relative --diff-filter=D HEAD") as f:
2575 myremoved = f.readlines()
2576 myremoved = ["./" + elem[:-1] for elem in myremoved]
2579 with repoman_popen("bzr status -S .") as f:
2580 bzrstatus = f.readlines()
2581 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2582 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")]
2583 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2584 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")]
2585 # Bazaar expands nothing.
2588 with repoman_popen("hg status --no-status --modified .") as f:
2589 mychanged = f.readlines()
2590 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2592 with repoman_popen("hg status --no-status --added .") as f:
2593 mynew = f.readlines()
2594 mynew = ["./" + elem.rstrip() for elem in mynew]
2596 with repoman_popen("hg status --no-status --removed .") as f:
2597 myremoved = f.readlines()
2598 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2601 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2602 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2604 print("(Didn't find any changed files...)")
2608 # Manifests need to be regenerated after all other commits, so don't commit
2609 # them now even if they have changed.
2612 for f in mychanged + mynew:
2613 if "Manifest" == os.path.basename(f):
2617 myupdates.difference_update(myremoved)
2618 myupdates = list(myupdates)
2619 mymanifests = list(mymanifests)
2623 commitmessage = options.commitmsg
2624 if options.commitmsgfile:
2626 f = io.open(_unicode_encode(options.commitmsgfile,
2627 encoding=_encodings['fs'], errors='strict'),
2628 mode='r', encoding=_encodings['content'], errors='replace')
2629 commitmessage = f.read()
2632 except (IOError, OSError) as e:
2633 if e.errno == errno.ENOENT:
2634 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2637 # We've read the content so the file is no longer needed.
2638 commitmessagefile = None
2639 if not commitmessage or not commitmessage.strip():
2641 editor = os.environ.get("EDITOR")
2642 if editor and utilities.editor_is_executable(editor):
2643 commitmessage = utilities.get_commit_message_with_editor(
2644 editor, message=qa_output)
2646 commitmessage = utilities.get_commit_message_with_stdin()
2647 except KeyboardInterrupt:
2649 if not commitmessage or not commitmessage.strip():
2650 print("* no commit message? aborting commit.")
2652 commitmessage = commitmessage.rstrip()
2653 changelog_msg = commitmessage
2654 portage_version = getattr(portage, "VERSION", None)
2655 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2656 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2657 if portage_version is None:
2658 sys.stderr.write("Failed to insert portage version in message!\n")
2660 portage_version = "Unknown"
2664 report_options.append("--force")
2665 if options.ignore_arches:
2666 report_options.append("--ignore-arches")
2667 if include_arches is not None:
2668 report_options.append("--include-arches=\"%s\"" %
2669 " ".join(sorted(include_arches)))
2672 # Use new footer only for git (see bug #438364).
2673 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2675 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2677 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2679 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2681 unameout = platform.system() + " "
2682 if platform.system() in ["Darwin", "SunOS"]:
2683 unameout += platform.processor()
2685 unameout += platform.machine()
2686 commit_footer = "\n\n"
2688 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2689 commit_footer += "(Portage version: %s/%s/%s" % \
2690 (portage_version, vcs, unameout)
2692 commit_footer += ", RepoMan options: " + " ".join(report_options)
2694 commit_footer += ", signed Manifest commit with key %s" % \
2697 commit_footer += ", unsigned Manifest commit"
2698 commit_footer += ")"
2700 commitmessage += commit_footer
2702 if options.echangelog in ('y', 'force'):
2703 logging.info("checking for unmodified ChangeLog files")
2704 committer_name = utilities.get_committer_name(env=repoman_settings)
2705 for x in sorted(vcs_files_to_cps(
2706 chain(myupdates, mymanifests, myremoved))):
2707 catdir, pkgdir = x.split("/")
2708 checkdir = repodir + "/" + x
2709 checkdir_relative = ""
2711 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2713 checkdir_relative = os.path.join(catdir, checkdir_relative)
2714 checkdir_relative = os.path.join(".", checkdir_relative)
2716 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2717 changelog_modified = changelog_path in modified_changelogs
2718 if changelog_modified and options.echangelog != 'force':
2721 # get changes for this package
2722 cdrlen = len(checkdir_relative)
2723 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2724 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2725 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2727 # Skip ChangeLog generation if only the Manifest was modified,
2728 # as discussed in bug #398009.
2729 nontrivial_cl_files = set()
2730 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2731 nontrivial_cl_files.difference_update(['Manifest'])
2732 if not nontrivial_cl_files and options.echangelog != 'force':
2735 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2736 committer_name, changelog_msg,
2737 os.path.join(repodir, 'skel.ChangeLog'),
2739 new=clnew, removed=clremoved, changed=clchanged,
2740 pretend=options.pretend)
2741 if new_changelog is None:
2742 writemsg_level("!!! Updating the ChangeLog failed\n", \
2743 level=logging.ERROR, noiselevel=-1)
2746 # if the ChangeLog was just created, add it to vcs
2748 myautoadd.append(changelog_path)
2749 # myautoadd is appended to myupdates below
2751 myupdates.append(changelog_path)
2753 if options.ask and not options.pretend:
2754 # regenerate Manifest for modified ChangeLog (bug #420735)
2755 repoman_settings["O"] = checkdir
2756 digestgen(mysettings=repoman_settings, myportdb=portdb)
2759 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2760 add_cmd = [vcs, "add"]
2761 add_cmd += myautoadd
2763 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2766 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2767 # Python 3.1 produces the following TypeError if raw bytes are
2768 # passed to subprocess.call():
2769 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2770 # errread, errwrite)
2771 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2772 # raise child_exception
2773 # TypeError: expected an object with the buffer interface
2774 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2775 retcode = subprocess.call(add_cmd)
2776 if retcode != os.EX_OK:
2778 "Exiting on %s error code: %s\n" % (vcs, retcode))
2781 myupdates += myautoadd
2783 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2785 if vcs not in ('cvs', 'svn'):
2786 # With git, bzr and hg, there's never any keyword expansion, so
2787 # there's no need to regenerate manifests and all files will be
2788 # committed in one big commit at the end.
2790 elif not repo_config.thin_manifest:
2792 headerstring = "'\$(Header|Id).*\$'"
2794 svn_keywords = dict((k.lower(), k) for k in [
2797 "LastChangedRevision",
2808 for myfile in myupdates:
2810 # for CVS, no_expansion contains files that are excluded from expansion
2812 if myfile in no_expansion:
2815 # for SVN, expansion contains files that are included in expansion
2817 if myfile not in expansion:
2820 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2821 enabled_keywords = []
2822 for k in expansion[myfile]:
2823 keyword = svn_keywords.get(k.lower())
2824 if keyword is not None:
2825 enabled_keywords.append(keyword)
2827 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2829 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2830 portage._shell_quote(myfile))
2832 myheaders.append(myfile)
2834 print("%s have headers that will change." % green(str(len(myheaders))))
2835 print("* Files with headers will cause the manifests to be changed and committed separately.")
2837 logging.info("myupdates: %s", myupdates)
2838 logging.info("myheaders: %s", myheaders)
2840 if options.ask and userquery('Commit changes?', True) != 'Yes':
2841 print("* aborting commit.")
2842 sys.exit(128 + signal.SIGINT)
2844 # Handle the case where committed files have keywords which
2845 # will change and need a priming commit before the Manifest
2847 if (myupdates or myremoved) and myheaders:
2848 myfiles = myupdates + myremoved
2849 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2850 mymsg = os.fdopen(fd, "wb")
2851 mymsg.write(_unicode_encode(commitmessage))
2855 print(green("Using commit message:"))
2856 print(green("------------------------------------------------------------------------------"))
2857 print(commitmessage)
2858 print(green("------------------------------------------------------------------------------"))
2861 # Having a leading ./ prefix on file paths can trigger a bug in
2862 # the cvs server when committing files to multiple directories,
2863 # so strip the prefix.
2864 myfiles = [f.lstrip("./") for f in myfiles]
2867 commit_cmd.extend(vcs_global_opts)
2868 commit_cmd.append("commit")
2869 commit_cmd.extend(vcs_local_opts)
2870 commit_cmd.extend(["-F", commitmessagefile])
2871 commit_cmd.extend(myfiles)
2875 print("(%s)" % (" ".join(commit_cmd),))
2877 retval = spawn(commit_cmd, env=commit_env)
2878 if retval != os.EX_OK:
2879 writemsg_level(("!!! Exiting on %s (shell) " + \
2880 "error code: %s\n") % (vcs, retval),
2881 level=logging.ERROR, noiselevel=-1)
2885 os.unlink(commitmessagefile)
2889 # Setup the GPG commands
2890 def gpgsign(filename):
2891 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2893 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2894 " Is make.globals missing?")
2895 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2896 "PORTAGE_GPG_KEY" not in repoman_settings:
2897 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2898 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2899 if "PORTAGE_GPG_DIR" not in repoman_settings:
2900 repoman_settings["PORTAGE_GPG_DIR"] = \
2901 os.path.expanduser("~/.gnupg")
2902 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2903 % repoman_settings["PORTAGE_GPG_DIR"])
2905 repoman_settings["PORTAGE_GPG_DIR"] = \
2906 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2907 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2908 raise portage.exception.InvalidLocation(
2909 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2910 repoman_settings["PORTAGE_GPG_DIR"])
2911 gpgvars = {"FILE": filename}
2912 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2913 v = repoman_settings.get(k)
2916 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2918 print("(" + gpgcmd + ")")
2920 # Encode unicode manually for bug #310789.
2921 gpgcmd = portage.util.shlex_split(gpgcmd)
2922 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
2923 # Python 3.1 does not support bytes in Popen args.
2924 gpgcmd = [_unicode_encode(arg,
2925 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2926 rValue = subprocess.call(gpgcmd)
2927 if rValue == os.EX_OK:
2928 os.rename(filename + ".asc", filename)
2930 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2932 def need_signature(filename):
2934 with open(_unicode_encode(filename,
2935 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2936 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2937 except IOError as e:
2938 if e.errno in (errno.ENOENT, errno.ESTALE):
2942 # When files are removed and re-added, the cvs server will put /Attic/
2943 # inside the $Header path. This code detects the problem and corrects it
2944 # so that the Manifest will generate correctly. See bug #169500.
2945 # Use binary mode in order to avoid potential character encoding issues.
2946 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2947 attic_str = b'/Attic/'
2948 attic_replace = b'/'
2950 f = open(_unicode_encode(x,
2951 encoding=_encodings['fs'], errors='strict'),
2953 mylines = f.readlines()
2956 for i, line in enumerate(mylines):
2957 if cvs_header_re.match(line) is not None and \
2959 mylines[i] = line.replace(attic_str, attic_replace)
2962 portage.util.write_atomic(x, b''.join(mylines),
2966 print(green("RepoMan sez:"), "\"You're rather crazy... "
2967 "doing the entire repository.\"\n")
2969 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2971 for x in sorted(vcs_files_to_cps(
2972 chain(myupdates, myremoved, mymanifests))):
2973 repoman_settings["O"] = os.path.join(repodir, x)
2974 digestgen(mysettings=repoman_settings, myportdb=portdb)
2980 for x in sorted(vcs_files_to_cps(
2981 chain(myupdates, myremoved, mymanifests))):
2982 repoman_settings["O"] = os.path.join(repodir, x)
2983 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
2984 if not need_signature(manifest_path):
2986 gpgsign(manifest_path)
2987 except portage.exception.PortageException as e:
2988 portage.writemsg("!!! %s\n" % str(e))
2989 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2993 # It's not safe to use the git commit -a option since there might
2994 # be some modified files elsewhere in the working tree that the
2995 # user doesn't want to commit. Therefore, call git update-index
2996 # in order to ensure that the index is updated with the latest
2997 # versions of all new and modified files in the relevant portion
2998 # of the working tree.
2999 myfiles = mymanifests + myupdates
3001 update_index_cmd = ["git", "update-index"]
3002 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
3004 print("(%s)" % (" ".join(update_index_cmd),))
3006 retval = spawn(update_index_cmd, env=os.environ)
3007 if retval != os.EX_OK:
3008 writemsg_level(("!!! Exiting on %s (shell) " + \
3009 "error code: %s\n") % (vcs, retval),
3010 level=logging.ERROR, noiselevel=-1)
3014 myfiles = mymanifests[:]
3015 # If there are no header (SVN/CVS keywords) changes in
3016 # the files, this Manifest commit must include the
3017 # other (yet uncommitted) files.
3019 myfiles += myupdates
3020 myfiles += myremoved
3023 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3024 mymsg = os.fdopen(fd, "wb")
3025 mymsg.write(_unicode_encode(commitmessage))
3029 if options.pretend and vcs is None:
3030 # substitute a bogus value for pretend output
3031 commit_cmd.append("cvs")
3033 commit_cmd.append(vcs)
3034 commit_cmd.extend(vcs_global_opts)
3035 commit_cmd.append("commit")
3036 commit_cmd.extend(vcs_local_opts)
3038 commit_cmd.extend(["--logfile", commitmessagefile])
3039 commit_cmd.extend(myfiles)
3041 commit_cmd.extend(["-F", commitmessagefile])
3042 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3046 print("(%s)" % (" ".join(commit_cmd),))
3048 retval = spawn(commit_cmd, env=commit_env)
3049 if retval != os.EX_OK:
3050 if repo_config.sign_commit and vcs == 'git' and \
3051 not git_supports_gpg_sign():
3052 # Inform user that newer git is needed (bug #403323).
3054 "Git >=1.7.9 is required for signed commits!")
3056 writemsg_level(("!!! Exiting on %s (shell) " + \
3057 "error code: %s\n") % (vcs, retval),
3058 level=logging.ERROR, noiselevel=-1)
3062 os.unlink(commitmessagefile)
3068 print("Commit complete.")
3070 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3071 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")