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 if argv and isinstance(argv[0], bytes):
160 argv = [portage._unicode_decode(x) for x in argv]
163 'commit' : 'Run a scan then commit changes',
164 'ci' : 'Run a scan then commit changes',
165 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
166 'full' : 'Scan directory tree and print all issues (not a summary)',
167 'help' : 'Show this screen',
168 'manifest' : 'Generate a Manifest (fetches files if necessary)',
169 'manifest-check' : 'Check Manifests for missing or incorrect digests',
170 'scan' : 'Scan directory tree for QA issues'
173 mode_keys = list(modes)
176 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
177 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
178 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
179 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
180 parser.description += "\nmodes: " + " | ".join(map(green, mode_keys))
182 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
183 help='Request a confirmation before commiting')
185 parser.add_option('-m', '--commitmsg', dest='commitmsg',
186 help='specify a commit message on the command line')
188 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
189 help='specify a path to a file that contains a commit message')
191 parser.add_option('--digest',
192 type='choice', choices=('y', 'n'), metavar='<y|n>',
193 help='Automatically update Manifest digests for modified files')
195 parser.add_option('-p', '--pretend', dest='pretend', default=False,
196 action='store_true', help='don\'t commit or fix anything; just show what would be done')
198 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
199 help='do not print unnecessary messages')
202 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
203 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
204 'regardless of modification if \'force\' is specified)')
206 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
207 help='Commit with QA violations')
209 parser.add_option('--vcs', dest='vcs',
210 help='Force using specific VCS instead of autodetection')
212 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
213 help='be very verbose in output', default=0)
215 parser.add_option('-V', '--version', dest='version', action='store_true',
216 help='show version info')
218 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
219 default=False, help='forces the metadata.xml parse check to be carried out')
222 '--if-modified', type='choice', choices=('y', 'n'), default='n',
224 help='only check packages that have uncommitted modifications')
226 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
227 default=False, help='ignore arch-specific failures (where arch != host)')
229 parser.add_option("--ignore-default-opts",
231 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
233 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
234 default=False, help='ignore masked packages (not allowed with commit mode)')
236 parser.add_option('--include-arches', dest='include_arches',
237 metavar='ARCHES', action='append',
238 help='A space separated list of arches used to '
239 'filter the selection of profiles for dependency checks')
241 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
242 default=False, help='include dev profiles in dependency checks')
244 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
245 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
247 parser.add_option('--without-mask', dest='without_mask', action='store_true',
248 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
250 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
251 help='specify which mode repoman will run in (default=full)')
253 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
256 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
258 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
260 sorted_qa = list(qahelp)
263 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
265 opts, args = parser.parse_args(argv[1:])
267 if not opts.ignore_default_opts:
268 default_opts = portage.util.shlex_split(
269 repoman_settings.get("REPOMAN_DEFAULT_OPTS", ""))
271 opts, args = parser.parse_args(default_opts + sys.argv[1:])
273 if opts.mode == 'help':
274 parser.print_help(short=False)
282 parser.error("invalid mode: %s" % arg)
287 if opts.mode == 'ci':
288 opts.mode = 'commit' # backwards compat shortcut
290 if opts.mode == 'commit' and not (opts.force or opts.pretend):
291 if opts.ignore_masked:
292 parser.error('Commit mode and --ignore-masked are not compatible')
293 if opts.without_mask:
294 parser.error('Commit mode and --without-mask are not compatible')
296 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
297 for val in range(opts.verbosity):
298 logger = logging.getLogger()
299 logger.setLevel(logger.getEffectiveLevel() - 10)
301 for val in range(opts.quiet):
302 logger = logging.getLogger()
303 logger.setLevel(logger.getEffectiveLevel() + 10)
308 "CVS/Entries.IO_error": "Attempting to commit, and an IO error was encountered access the Entries file",
309 "ebuild.invalidname": "Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
310 "ebuild.namenomatch": "Ebuild files that do not have the same name as their parent directory",
311 "changelog.ebuildadded": "An ebuild was added but the ChangeLog was not modified",
312 "changelog.missing": "Missing ChangeLog files",
313 "ebuild.notadded": "Ebuilds that exist but have not been added to cvs",
314 "ebuild.patches": "PATCHES variable should be a bash array to ensure white space safety",
315 "changelog.notadded": "ChangeLogs that exist but have not been added to cvs",
316 "dependency.bad": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds)",
317 "dependency.badmasked": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds)",
318 "dependency.badindev": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds) in developing arch",
319 "dependency.badmaskedindev": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds) in developing arch",
320 "dependency.badtilde": "Uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
321 "dependency.syntax": "Syntax error in dependency string (usually an extra/missing space/parenthesis)",
322 "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)",
323 "file.executable": "Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
324 "file.size": "Files in the files directory must be under 20 KiB",
325 "file.size.fatal": "Files in the files directory must be under 60 KiB",
326 "file.name": "File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
327 "file.UTF8": "File is not UTF8 compliant",
328 "inherit.deprecated": "Ebuild inherits a deprecated eclass",
329 "inherit.missing": "Ebuild uses functions from an eclass but does not inherit it",
330 "inherit.unused": "Ebuild inherits an eclass but does not use it",
331 "java.eclassesnotused": "With virtual/jdk in DEPEND you must inherit a java eclass",
332 "wxwidgets.eclassnotused": "Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
333 "KEYWORDS.dropped": "Ebuilds that appear to have dropped KEYWORDS for some arch",
334 "KEYWORDS.missing": "Ebuilds that have a missing or empty KEYWORDS variable",
335 "KEYWORDS.stable": "Ebuilds that have been added directly with stable KEYWORDS",
336 "KEYWORDS.stupid": "Ebuilds that use KEYWORDS=-* instead of package.mask",
337 "LICENSE.missing": "Ebuilds that have a missing or empty LICENSE variable",
338 "LICENSE.virtual": "Virtuals that have a non-empty LICENSE variable",
339 "DESCRIPTION.missing": "Ebuilds that have a missing or empty DESCRIPTION variable",
340 "DESCRIPTION.toolong": "DESCRIPTION is over %d characters" % max_desc_len,
341 "EAPI.definition": "EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
342 "EAPI.deprecated": "Ebuilds that use features that are deprecated in the current EAPI",
343 "EAPI.incompatible": "Ebuilds that use features that are only available with a different EAPI",
344 "EAPI.unsupported": "Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
345 "SLOT.invalid": "Ebuilds that have a missing or invalid SLOT variable value",
346 "HOMEPAGE.missing": "Ebuilds that have a missing or empty HOMEPAGE variable",
347 "HOMEPAGE.virtual": "Virtuals that have a non-empty HOMEPAGE variable",
348 "PDEPEND.suspect": "PDEPEND contains a package that usually only belongs in DEPEND.",
349 "LICENSE.syntax": "Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
350 "PROVIDE.syntax": "Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
351 "PROPERTIES.syntax": "Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
352 "RESTRICT.syntax": "Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
353 "REQUIRED_USE.syntax": "Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
354 "SRC_URI.syntax": "Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
355 "SRC_URI.mirror": "A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
356 "ebuild.syntax": "Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
357 "ebuild.output": "A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
358 "ebuild.nesteddie": "Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
359 "variable.invalidchar": "A variable contains an invalid character that is not part of the ASCII character set",
360 "variable.readonly": "Assigning a readonly variable",
361 "variable.usedwithhelpers": "Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
362 "LIVEVCS.stable": "This ebuild is a live checkout from a VCS but has stable keywords.",
363 "LIVEVCS.unmasked": "This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
364 "IUSE.invalid": "This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
365 "IUSE.missing": "This ebuild has a USE conditional which references a flag that is not listed in IUSE",
366 "LICENSE.invalid": "This ebuild is listing a license that doesnt exist in portages license/ dir.",
367 "LICENSE.deprecated": "This ebuild is listing a deprecated license.",
368 "KEYWORDS.invalid": "This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
369 "RDEPEND.implicit": "RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
370 "RDEPEND.suspect": "RDEPEND contains a package that usually only belongs in DEPEND.",
371 "RESTRICT.invalid": "This ebuild contains invalid RESTRICT values.",
372 "digest.assumed": "Existing digest must be assumed correct (Package level only)",
373 "digest.missing": "Some files listed in SRC_URI aren't referenced in the Manifest",
374 "digest.unused": "Some files listed in the Manifest aren't referenced in SRC_URI",
375 "ebuild.majorsyn": "This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
376 "ebuild.minorsyn": "This ebuild has a minor syntax error that contravenes gentoo coding style",
377 "ebuild.badheader": "This ebuild has a malformed header",
378 "manifest.bad": "Manifest has missing or incorrect digests",
379 "metadata.missing": "Missing metadata.xml files",
380 "metadata.bad": "Bad metadata.xml files",
381 "metadata.warning": "Warnings in metadata.xml files",
382 "portage.internal": "The ebuild uses an internal Portage function or variable",
383 "virtual.oldstyle": "The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
384 "virtual.suspect": "Ebuild contains a package that usually should be pulled via virtual/, not directly.",
385 "usage.obsolete": "The ebuild makes use of an obsolete construct",
386 "upstream.workaround": "The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
389 qacats = list(qahelp)
394 "changelog.notadded",
395 "dependency.unknown",
400 "dependency.badmasked",
401 "dependency.badindev",
402 "dependency.badmaskedindev",
403 "dependency.badtilde",
404 "DESCRIPTION.toolong",
407 "LICENSE.deprecated",
422 "inherit.deprecated",
423 "java.eclassesnotused",
424 "wxwidgets.eclassnotused",
428 "upstream.workaround",
433 if portage.const._ENABLE_INHERIT_CHECK:
434 # This is experimental, so it's non-fatal.
435 qawarnings.add("inherit.missing")
437 non_ascii_re = re.compile(r'[^\x00-\x7f]')
439 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
440 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
441 allvars.update(Package.metadata_keys)
442 allvars = sorted(allvars)
444 for x in missingvars:
447 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
451 valid_restrict = frozenset(["binchecks", "bindist",
452 "fetch", "installsources", "mirror", "preserve-libs",
453 "primaryuri", "splitdebug", "strip", "test", "userpriv"])
455 live_eclasses = frozenset([
466 suspect_rdepend = frozenset([
467 "app-arch/cabextract",
468 "app-arch/rpm2targz",
473 "dev-perl/extutils-pkgconfig",
479 "dev-util/gtk-doc-am",
482 "dev-util/pkg-config-lite",
484 "dev-util/pkgconfig",
485 "dev-util/pkgconfig-openbsd",
489 "media-gfx/ebdftopcf",
491 "sys-devel/autoconf",
492 "sys-devel/automake",
499 "virtual/linux-sources",
506 "dev-util/pkg-config-lite":"virtual/pkgconfig",
507 "dev-util/pkgconf":"virtual/pkgconfig",
508 "dev-util/pkgconfig":"virtual/pkgconfig",
509 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
510 "dev-libs/libusb":"virtual/libusb",
511 "dev-libs/libusbx":"virtual/libusb",
512 "dev-libs/libusb-compat":"virtual/libusb",
515 metadata_xml_encoding = 'UTF-8'
516 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
517 (metadata_xml_encoding,)
518 metadata_doctype_name = 'pkgmetadata'
519 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
520 # force refetch if the local copy creation time is older than this
521 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
524 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
526 options, arguments = ParseArgs(sys.argv, qahelp)
529 print("Portage", portage.VERSION)
532 # Set this to False when an extraordinary issue (generally
533 # something other than a QA issue) makes it impossible to
534 # commit (like if Manifest generation fails).
537 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
541 myreporoot = os.path.basename(portdir_overlay)
542 myreporoot += mydir[len(portdir_overlay):]
545 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
550 vcses = utilities.FindVCS()
552 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
553 print(red('*** Please either clean up your workdir or specify --vcs option.'))
560 if options.if_modified == "y" and vcs is None:
561 logging.info("Not in a version controlled repository; "
562 "disabling --if-modified.")
563 options.if_modified = "n"
565 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
566 vcs_preserves_mtime = vcs in ('cvs',)
568 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
569 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
570 if vcs_global_opts is None:
571 if vcs in ('cvs', 'svn'):
572 vcs_global_opts = "-q"
575 vcs_global_opts = vcs_global_opts.split()
577 if options.mode == 'commit' and not options.pretend and not vcs:
578 logging.info("Not in a version controlled repository; enabling pretend mode.")
579 options.pretend = True
581 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
582 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
583 (repoman_settings.get('PORTDIR_OVERLAY', ''),
584 portage._shell_quote(portdir_overlay))
585 # We have to call the config constructor again so
586 # that config.repositories is initialized correctly.
587 repoman_settings = portage.config(config_root=config_root, local_config=False,
588 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
590 root = repoman_settings['EROOT']
592 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
594 portdb = trees[root]['porttree'].dbapi
596 # Constrain dependency resolution to the master(s)
597 # that are specified in layout.conf.
598 repodir = os.path.realpath(portdir_overlay)
599 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
600 portdb.porttrees = list(repo_config.eclass_db.porttrees)
601 portdir = portdb.porttrees[0]
602 commit_env = os.environ.copy()
604 if repo_config.allow_provide_virtual:
605 qawarnings.add("virtual.oldstyle")
607 if repo_config.sign_commit:
609 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
610 # the commit arguments. If key_id is unspecified, then it must be
611 # configured by `git config user.signingkey key_id`.
612 vcs_local_opts.append("--gpg-sign")
613 if repoman_settings.get("PORTAGE_GPG_DIR"):
614 # Pass GNUPGHOME to git for bug #462362.
615 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
617 # In order to disable manifest signatures, repos may set
618 # "sign-manifests = false" in metadata/layout.conf. This
619 # can be used to prevent merge conflicts like those that
620 # thin-manifests is designed to prevent.
621 sign_manifests = "sign" in repoman_settings.features and \
622 repo_config.sign_manifest
624 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
625 options.mode in ("commit",) and not sign_manifests:
626 msg = ("The '%s' repository has manifest signatures enabled, "
627 "but FEATURES=sign is currently disabled. In order to avoid this "
628 "warning, enable FEATURES=sign in make.conf. Alternatively, "
629 "repositories can disable manifest signatures by setting "
630 "'sign-manifests = false' in metadata/layout.conf.") % \
632 for line in textwrap.wrap(msg, 60):
635 if sign_manifests and options.mode in ("commit",) and \
636 repoman_settings.get("PORTAGE_GPG_KEY") and \
637 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
638 repoman_settings["PORTAGE_GPG_KEY"]) is None:
639 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
640 repoman_settings["PORTAGE_GPG_KEY"])
643 manifest_hashes = repo_config.manifest_hashes
644 if manifest_hashes is None:
645 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
647 if options.mode in ("commit", "fix", "manifest"):
648 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
649 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
650 "metadata/layout.conf does not contain the '%s' hash which "
651 "is required by this portage version. You will have to "
652 "upgrade portage if you want to generate valid manifests for "
653 "this repository.") % \
654 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
655 for line in textwrap.wrap(msg, 70):
659 unsupported_hashes = manifest_hashes.difference(
660 portage.const.MANIFEST2_HASH_FUNCTIONS)
661 if unsupported_hashes:
662 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
663 "metadata/layout.conf contains one or more hash types '%s' "
664 "which are not supported by this portage version. You will "
665 "have to upgrade portage if you want to generate valid "
666 "manifests for this repository.") % \
667 (repo_config.name, " ".join(sorted(unsupported_hashes)))
668 for line in textwrap.wrap(msg, 70):
672 if options.echangelog is None and repo_config.update_changelog:
673 options.echangelog = 'y'
676 options.echangelog = 'n'
678 # The --echangelog option causes automatic ChangeLog generation,
679 # which invalidates changelog.ebuildadded and changelog.missing
681 # Note: Some don't use ChangeLogs in distributed SCMs.
682 # It will be generated on server side from scm log,
683 # before package moves to the rsync server.
684 # This is needed because they try to avoid merge collisions.
685 # Gentoo's Council decided to always use the ChangeLog file.
686 # TODO: shouldn't this just be switched on the repo, iso the VCS?
687 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
689 if 'digest' in repoman_settings.features and options.digest != 'n':
692 logging.debug("vcs: %s" % (vcs,))
693 logging.debug("repo config: %s" % (repo_config,))
694 logging.debug("options: %s" % (options,))
696 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
697 # profile-specific config constructor calls.
698 env = os.environ.copy()
699 env['PORTDIR'] = portdir
700 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
702 logging.info('Setting paths:')
703 logging.info('PORTDIR = "' + portdir + '"')
704 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
706 # It's confusing if these warnings are displayed without the user
707 # being told which profile they come from, so disable them.
708 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
711 for path in repo_config.eclass_db.porttrees:
712 categories.extend(portage.util.grabfile(
713 os.path.join(path, 'profiles', 'categories')))
714 repoman_settings.categories = frozenset(
715 portage.util.stack_lists([categories], incremental=1))
716 categories = repoman_settings.categories
718 portdb.settings = repoman_settings
719 root_config = RootConfig(repoman_settings, trees[root], None)
720 # We really only need to cache the metadata that's necessary for visibility
721 # filtering. Anything else can be discarded to reduce memory consumption.
722 portdb._aux_cache_keys.clear()
723 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
725 reposplit = myreporoot.split(os.path.sep)
726 repolevel = len(reposplit)
728 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
729 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
730 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
731 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
732 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
733 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
734 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
736 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
738 # Make startdir relative to the canonical repodir, so that we can pass
739 # it to digestgen and it won't have to be canonicalized again.
743 startdir = normalize_path(mydir)
744 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
747 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.")
749 def repoman_getstatusoutput(cmd):
751 Implements an interface similar to getstatusoutput(), but with
752 customized unicode handling (see bug #310789) and without the shell.
754 args = portage.util.shlex_split(cmd)
755 encoding = _encodings['fs']
756 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
757 # Python 3.1 does not support bytes in Popen args.
758 args = [_unicode_encode(x,
759 encoding=encoding, errors='strict') for x in args]
760 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
761 stderr=subprocess.STDOUT)
762 output = portage._unicode_decode(proc.communicate()[0],
763 encoding=encoding, errors='strict')
764 if output and output[-1] == "\n":
765 # getstatusoutput strips one newline
767 return (proc.wait(), output)
769 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
771 Implements an interface similar to os.popen(), but with customized
772 unicode handling (see bug #310789) and without the shell.
775 __slots__ = ('_proc', '_stdout')
777 def __init__(self, cmd):
778 args = portage.util.shlex_split(cmd)
779 encoding = _encodings['fs']
780 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
781 # Python 3.1 does not support bytes in Popen args.
782 args = [_unicode_encode(x,
783 encoding=encoding, errors='strict') for x in args]
784 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
785 object.__setattr__(self, '_proc', proc)
786 object.__setattr__(self, '_stdout',
787 codecs.getreader(encoding)(proc.stdout, 'strict'))
789 def _get_target(self):
790 return object.__getattribute__(self, '_stdout')
792 __enter__ = _get_target
794 def __exit__(self, exc_type, exc_value, traceback):
795 proc = object.__getattribute__(self, '_proc')
799 class ProfileDesc(object):
800 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
801 def __init__(self, arch, status, sub_path, tree_path):
805 sub_path = normalize_path(sub_path.lstrip(os.sep))
806 self.sub_path = sub_path
807 self.tree_path = tree_path
809 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
811 self.abs_path = tree_path
816 return 'empty profile'
819 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
821 # get lists of valid keywords, licenses, and use
825 global_pmasklines = []
827 for path in portdb.porttrees:
829 liclist.update(os.listdir(os.path.join(path, "licenses")))
832 kwlist.update(portage.grabfile(os.path.join(path,
833 "profiles", "arch.list")))
835 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
841 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
843 expand_list = os.listdir(expand_desc_dir)
847 for fn in expand_list:
848 if not fn[-5:] == '.desc':
850 use_prefix = fn[:-5].lower() + '_'
851 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
854 uselist.add(use_prefix + x[0])
856 global_pmasklines.append(portage.util.grabfile_package(
857 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
859 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
861 desc_file = io.open(_unicode_encode(desc_path,
862 encoding=_encodings['fs'], errors='strict'),
863 mode='r', encoding=_encodings['repo.content'], errors='replace')
864 except EnvironmentError:
867 for i, x in enumerate(desc_file):
874 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
875 desc_path + " line %d" % (i + 1, ))
876 elif arch[0] not in kwlist:
877 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
878 desc_path + " line %d" % (i + 1, ))
879 elif arch[2] not in valid_profile_types:
880 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
881 desc_path + " line %d" % (i + 1, ))
882 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
883 if not os.path.isdir(profile_desc.abs_path):
885 "Invalid %s profile (%s) for arch %s in %s line %d",
886 arch[2], arch[1], arch[0], desc_path, i + 1)
889 os.path.join(profile_desc.abs_path, 'deprecated')):
891 profile_list.append(profile_desc)
894 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
895 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
897 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
898 global_pmaskdict = {}
899 for x in global_pmasklines:
900 global_pmaskdict.setdefault(x.cp, []).append(x)
901 del global_pmasklines
903 def has_global_mask(pkg):
904 mask_atoms = global_pmaskdict.get(pkg.cp)
908 if portage.dep.match_from_list(x, pkg_list):
912 # Ensure that profile sub_path attributes are unique. Process in reverse order
913 # so that profiles with duplicate sub_path from overlays will override
914 # profiles with the same sub_path from parent repos.
916 profile_list.reverse()
917 profile_sub_paths = set()
918 for prof in profile_list:
919 if prof.sub_path in profile_sub_paths:
921 profile_sub_paths.add(prof.sub_path)
922 profiles.setdefault(prof.arch, []).append(prof)
924 # Use an empty profile for checking dependencies of
925 # packages that have empty KEYWORDS.
926 prof = ProfileDesc('**', 'stable', '', '')
927 profiles.setdefault(prof.arch, []).append(prof)
929 for x in repoman_settings.archlist():
932 if x not in profiles:
933 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
934 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
935 print(red("up with the " + x + " team."))
938 liclist_deprecated = set()
939 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
940 liclist_deprecated.update(
941 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
944 logging.fatal("Couldn't find licenses?")
948 logging.fatal("Couldn't read KEYWORDS from arch.list")
952 logging.fatal("Couldn't find use.desc?")
957 # we are inside a category directory
958 catdir = reposplit[-1]
959 if catdir not in categories:
961 mydirlist = os.listdir(startdir)
963 if x == "CVS" or x.startswith("."):
965 if os.path.isdir(startdir + "/" + x):
966 scanlist.append(catdir + "/" + x)
967 repo_subdir = catdir + os.sep
970 if not os.path.isdir(startdir + "/" + x):
972 for y in os.listdir(startdir + "/" + x):
973 if y == "CVS" or y.startswith("."):
975 if os.path.isdir(startdir + "/" + x + "/" + y):
976 scanlist.append(x + "/" + y)
979 catdir = reposplit[-2]
980 if catdir not in categories:
982 scanlist.append(catdir + "/" + reposplit[-1])
983 repo_subdir = scanlist[-1] + os.sep
985 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
986 ' from the current working directory'
987 logging.critical(msg)
990 repo_subdir_len = len(repo_subdir)
993 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
995 def vcs_files_to_cps(vcs_file_iter):
997 Iterate over the given modified file paths returned from the vcs,
998 and return a frozenset containing category/pn strings for each
1005 if reposplit[-2] in categories and \
1006 next(vcs_file_iter, None) is not None:
1007 modified_cps.append("/".join(reposplit[-2:]))
1009 elif repolevel == 2:
1010 category = reposplit[-1]
1011 if category in categories:
1012 for filename in vcs_file_iter:
1013 f_split = filename.split(os.sep)
1015 if len(f_split) > 2:
1016 modified_cps.append(category + "/" + f_split[1])
1020 for filename in vcs_file_iter:
1021 f_split = filename.split(os.sep)
1022 # ['.', category, pn, ...]
1023 if len(f_split) > 3 and f_split[1] in categories:
1024 modified_cps.append("/".join(f_split[1:3]))
1026 return frozenset(modified_cps)
1028 def git_supports_gpg_sign():
1029 status, cmd_output = \
1030 repoman_getstatusoutput("git --version")
1031 cmd_output = cmd_output.split()
1033 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1034 if version is not None:
1035 version = [int(x) for x in version.groups()]
1036 if version[0] > 1 or \
1037 (version[0] == 1 and version[1] > 7) or \
1038 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1042 def dev_keywords(profiles):
1044 Create a set of KEYWORDS values that exist in 'dev'
1045 profiles. These are used
1046 to trigger a message notifying the user when they might
1047 want to add the --include-dev option.
1050 for arch, arch_profiles in profiles.items():
1051 for prof in arch_profiles:
1052 arch_set = type_arch_map.get(prof.status)
1053 if arch_set is None:
1055 type_arch_map[prof.status] = arch_set
1058 dev_keywords = type_arch_map.get('dev', set())
1059 dev_keywords.update(['~' + arch for arch in dev_keywords])
1060 return frozenset(dev_keywords)
1062 dev_keywords = dev_keywords(profiles)
1071 xmllint_capable = False
1072 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1074 def fetch_metadata_dtd():
1076 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1077 metadata_dtd_ctime_interval.
1079 @return: True if successful, otherwise False
1083 metadata_dtd_st = None
1084 current_time = int(time.time())
1086 metadata_dtd_st = os.stat(metadata_dtd)
1087 except EnvironmentError as e:
1088 if e.errno not in (errno.ENOENT, errno.ESTALE):
1092 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1093 if abs(current_time - metadata_dtd_st.st_ctime) \
1094 < metadata_dtd_ctime_interval:
1099 print(green("***") + " the local copy of metadata.dtd " + \
1100 "needs to be refetched, doing that now")
1102 parsed_url = urlparse(metadata_dtd_uri)
1103 setting = 'FETCHCOMMAND_' + parsed_url.scheme.upper()
1104 fcmd = repoman_settings.get(setting)
1106 fcmd = repoman_settings.get('FETCHCOMMAND')
1108 logging.error("FETCHCOMMAND is unset")
1111 destdir = repoman_settings["DISTDIR"]
1112 fd, metadata_dtd_tmp = tempfile.mkstemp(
1113 prefix='metadata.dtd.', dir=destdir)
1117 if not portage.getbinpkg.file_get(metadata_dtd_uri,
1119 filename=os.path.basename(metadata_dtd_tmp)):
1120 logging.error("failed to fetch metadata.dtd from '%s'" %
1125 portage.util.apply_secpass_permissions(metadata_dtd_tmp,
1126 gid=portage.data.portage_gid, mode=0o664, mask=0o2)
1127 except portage.exception.PortageException:
1130 os.rename(metadata_dtd_tmp, metadata_dtd)
1133 os.unlink(metadata_dtd_tmp)
1139 if options.mode == "manifest":
1141 elif not find_binary('xmllint'):
1142 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1143 if options.xml_parse or repolevel == 3:
1144 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1147 if not fetch_metadata_dtd():
1149 # this can be problematic if xmllint changes their output
1150 xmllint_capable = True
1152 if options.mode == 'commit' and vcs:
1153 utilities.detect_vcs_conflicts(options, vcs)
1155 if options.mode == "manifest":
1157 elif options.pretend:
1158 print(green("\nRepoMan does a once-over of the neighborhood..."))
1160 print(green("\nRepoMan scours the neighborhood..."))
1163 modified_ebuilds = set()
1164 modified_changelogs = set()
1170 mycvstree = cvstree.getentries("./", recursive=1)
1171 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1172 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1173 if options.if_modified == "y":
1174 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1177 with repoman_popen("svn status") as f:
1178 svnstatus = f.readlines()
1179 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1180 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1181 if options.if_modified == "y":
1182 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1185 with repoman_popen("git diff-index --name-only "
1186 "--relative --diff-filter=M HEAD") as f:
1187 mychanged = f.readlines()
1188 mychanged = ["./" + elem[:-1] for elem in mychanged]
1190 with repoman_popen("git diff-index --name-only "
1191 "--relative --diff-filter=A HEAD") as f:
1192 mynew = f.readlines()
1193 mynew = ["./" + elem[:-1] for elem in mynew]
1194 if options.if_modified == "y":
1195 with repoman_popen("git diff-index --name-only "
1196 "--relative --diff-filter=D HEAD") as f:
1197 myremoved = f.readlines()
1198 myremoved = ["./" + elem[:-1] for elem in myremoved]
1201 with repoman_popen("bzr status -S .") as f:
1202 bzrstatus = f.readlines()
1203 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1204 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1205 if options.if_modified == "y":
1206 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")]
1209 with repoman_popen("hg status --no-status --modified .") as f:
1210 mychanged = f.readlines()
1211 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1212 with repoman_popen("hg status --no-status --added .") as f:
1213 mynew = f.readlines()
1214 mynew = ["./" + elem.rstrip() for elem in mynew]
1215 if options.if_modified == "y":
1216 with repoman_popen("hg status --no-status --removed .") as f:
1217 myremoved = f.readlines()
1218 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1221 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1222 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1223 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1224 if os.path.basename(x) == "ChangeLog")
1226 def vcs_new_changed(relative_path):
1227 for x in chain(mychanged, mynew):
1228 if x == relative_path:
1232 have_pmasked = False
1233 have_dev_keywords = False
1236 # NOTE: match-all caches are not shared due to potential
1237 # differences between profiles in _get_implicit_iuse.
1239 arch_xmatch_caches = {}
1240 shared_xmatch_caches = {"cp-list":{}}
1242 include_arches = None
1243 if options.include_arches:
1244 include_arches = set()
1245 include_arches.update(*[x.split() for x in options.include_arches])
1247 # Disable the "ebuild.notadded" check when not in commit mode and
1248 # running `svn status` in every package dir will be too expensive.
1250 check_ebuild_notadded = not \
1251 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1253 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1254 thirdpartymirrors = {}
1255 for k, v in repoman_settings.thirdpartymirrors().items():
1257 if not v.endswith("/"):
1259 thirdpartymirrors[v] = k
1261 class _XMLParser(xml.etree.ElementTree.XMLParser):
1263 def __init__(self, data, **kwargs):
1264 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1265 self._portage_data = data
1266 if hasattr(self, 'parser'):
1267 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1268 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1269 self._base_StartDoctypeDeclHandler = \
1270 self.parser.StartDoctypeDeclHandler
1271 self.parser.StartDoctypeDeclHandler = \
1272 self._portage_StartDoctypeDeclHandler
1274 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1275 if self._base_XmlDeclHandler is not None:
1276 self._base_XmlDeclHandler(version, encoding, standalone)
1277 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1279 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1280 has_internal_subset):
1281 if self._base_StartDoctypeDeclHandler is not None:
1282 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1283 has_internal_subset)
1284 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1286 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1288 Implements doctype() as required to avoid deprecation warnings with
1291 def doctype(self, name, pubid, system):
1295 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1296 except (EnvironmentError, ParseError, PermissionDenied) as e:
1298 except FileNotFound:
1299 # TODO: Download as we do for metadata.dtd, but add a way to
1300 # disable for non-gentoo repoman users who may not have herds.
1303 effective_scanlist = scanlist
1304 if options.if_modified == "y":
1305 effective_scanlist = sorted(vcs_files_to_cps(
1306 chain(mychanged, mynew, myremoved)))
1308 for x in effective_scanlist:
1309 #ebuilds and digests added to cvs respectively.
1310 logging.info("checking package %s" % x)
1311 # save memory by discarding xmatch caches from previous package(s)
1312 arch_xmatch_caches.clear()
1314 catdir, pkgdir = x.split("/")
1315 checkdir = repodir + "/" + x
1316 checkdir_relative = ""
1318 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1320 checkdir_relative = os.path.join(catdir, checkdir_relative)
1321 checkdir_relative = os.path.join(".", checkdir_relative)
1322 generated_manifest = False
1324 if options.mode == "manifest" or \
1325 (options.mode != 'manifest-check' and options.digest == 'y') or \
1326 options.mode in ('commit', 'fix') and not options.pretend:
1327 auto_assumed = set()
1328 fetchlist_dict = portage.FetchlistDict(checkdir,
1329 repoman_settings, portdb)
1330 if options.mode == 'manifest' and options.force:
1331 portage._doebuild_manifest_exempt_depend += 1
1333 distdir = repoman_settings['DISTDIR']
1334 mf = repoman_settings.repositories.get_repo_for_location(
1335 os.path.dirname(os.path.dirname(checkdir)))
1336 mf = mf.load_manifest(checkdir, distdir,
1337 fetchlist_dict=fetchlist_dict)
1338 mf.create(requiredDistfiles=None,
1339 assumeDistHashesAlways=True)
1340 for distfiles in fetchlist_dict.values():
1341 for distfile in distfiles:
1342 if os.path.isfile(os.path.join(distdir, distfile)):
1343 mf.fhashdict['DIST'].pop(distfile, None)
1345 auto_assumed.add(distfile)
1348 portage._doebuild_manifest_exempt_depend -= 1
1350 repoman_settings["O"] = checkdir
1352 generated_manifest = digestgen(
1353 mysettings=repoman_settings, myportdb=portdb)
1354 except portage.exception.PermissionDenied as e:
1355 generated_manifest = False
1356 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1357 level=logging.ERROR, noiselevel=-1)
1359 if not generated_manifest:
1360 print("Unable to generate manifest.")
1363 if options.mode == "manifest":
1364 if not dofail and options.force and auto_assumed and \
1365 'assume-digests' in repoman_settings.features:
1366 # Show which digests were assumed despite the --force option
1367 # being given. This output will already have been shown by
1368 # digestgen() if assume-digests is not enabled, so only show
1369 # it here if assume-digests is enabled.
1370 pkgs = list(fetchlist_dict)
1372 portage.writemsg_stdout(" digest.assumed" + \
1373 portage.output.colorize("WARN",
1374 str(len(auto_assumed)).rjust(18)) + "\n")
1376 fetchmap = fetchlist_dict[cpv]
1377 pf = portage.catsplit(cpv)[1]
1378 for distfile in sorted(fetchmap):
1379 if distfile in auto_assumed:
1380 portage.writemsg_stdout(
1381 " %s::%s\n" % (pf, distfile))
1386 if not generated_manifest:
1387 repoman_settings['O'] = checkdir
1388 repoman_settings['PORTAGE_QUIET'] = '1'
1389 if not portage.digestcheck([], repoman_settings, strict=1):
1390 stats["manifest.bad"] += 1
1391 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1392 repoman_settings.pop('PORTAGE_QUIET', None)
1394 if options.mode == 'manifest-check':
1397 checkdirlist = os.listdir(checkdir)
1401 for y in checkdirlist:
1402 if (y in no_exec or y.endswith(".ebuild")) and \
1403 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1404 stats["file.executable"] += 1
1405 fails["file.executable"].append(os.path.join(checkdir, y))
1406 if y.endswith(".ebuild"):
1408 ebuildlist.append(pf)
1409 cpv = "%s/%s" % (catdir, pf)
1411 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1414 stats["ebuild.syntax"] += 1
1415 fails["ebuild.syntax"].append(os.path.join(x, y))
1419 stats["ebuild.output"] += 1
1420 fails["ebuild.output"].append(os.path.join(x, y))
1422 if not portage.eapi_is_supported(myaux["EAPI"]):
1424 stats["EAPI.unsupported"] += 1
1425 fails["EAPI.unsupported"].append(os.path.join(x, y))
1427 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1428 root_config=root_config, type_name="ebuild")
1432 if len(pkgs) != len(ebuildlist):
1433 # If we can't access all the metadata then it's totally unsafe to
1434 # commit since there's no way to generate a correct Manifest.
1435 # Do not try to do any more QA checks on this package since missing
1436 # metadata leads to false positives for several checks, and false
1437 # positives confuse users.
1441 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1442 ebuildlist = sorted(pkgs.values())
1443 ebuildlist = [pkg.pf for pkg in ebuildlist]
1445 for y in checkdirlist:
1446 index = repo_config.find_invalid_path_char(y)
1448 y_relative = os.path.join(checkdir_relative, y)
1449 if vcs is not None and not vcs_new_changed(y_relative):
1450 # If the file isn't in the VCS new or changed set, then
1451 # assume that it's an irrelevant temporary file (Manifest
1452 # entries are not generated for file names containing
1453 # prohibited characters). See bug #406877.
1456 stats["file.name"] += 1
1457 fails["file.name"].append("%s/%s: char '%s'" % \
1458 (checkdir, y, y[index]))
1460 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1465 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1466 encoding=_encodings['fs'], errors='strict'),
1467 mode='r', encoding=_encodings['repo.content'])
1470 except UnicodeDecodeError as ue:
1471 stats["file.UTF8"] += 1
1472 s = ue.object[:ue.start]
1476 s = s[s.rfind("\n") + 1:]
1477 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1482 if vcs in ("git", "hg") and check_ebuild_notadded:
1484 myf = repoman_popen("git ls-files --others %s" % \
1485 (portage._shell_quote(checkdir_relative),))
1487 myf = repoman_popen("hg status --no-status --unknown %s" % \
1488 (portage._shell_quote(checkdir_relative),))
1490 if l[:-1][-7:] == ".ebuild":
1491 stats["ebuild.notadded"] += 1
1492 fails["ebuild.notadded"].append(
1493 os.path.join(x, os.path.basename(l[:-1])))
1496 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1499 myf = open(checkdir + "/CVS/Entries", "r")
1501 myf = repoman_popen("svn status --depth=files --verbose " +
1502 portage._shell_quote(checkdir))
1504 myf = repoman_popen("bzr ls -v --kind=file " +
1505 portage._shell_quote(checkdir))
1506 myl = myf.readlines()
1512 splitl = l[1:].split("/")
1515 if splitl[0][-7:] == ".ebuild":
1516 eadded.append(splitl[0][:-7])
1521 # tree conflict, new in subversion 1.6
1524 if l[-7:] == ".ebuild":
1525 eadded.append(os.path.basename(l[:-7]))
1530 if l[-7:] == ".ebuild":
1531 eadded.append(os.path.basename(l[:-7]))
1533 myf = repoman_popen("svn status " +
1534 portage._shell_quote(checkdir))
1535 myl = myf.readlines()
1539 l = l.rstrip().split(' ')[-1]
1540 if l[-7:] == ".ebuild":
1541 eadded.append(os.path.basename(l[:-7]))
1544 stats["CVS/Entries.IO_error"] += 1
1545 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1550 mf = repoman_settings.repositories.get_repo_for_location(
1551 os.path.dirname(os.path.dirname(checkdir)))
1552 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1553 mydigests = mf.getTypeDigests("DIST")
1555 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1557 src_uri_error = False
1558 for mykey in fetchlist_dict:
1560 myfiles_all.extend(fetchlist_dict[mykey])
1561 except portage.exception.InvalidDependString as e:
1562 src_uri_error = True
1564 portdb.aux_get(mykey, ["SRC_URI"])
1566 # This will be reported as an "ebuild.syntax" error.
1569 stats["SRC_URI.syntax"] += 1
1570 fails["SRC_URI.syntax"].append(
1571 "%s.ebuild SRC_URI: %s" % (mykey, e))
1573 if not src_uri_error:
1574 # This test can produce false positives if SRC_URI could not
1575 # be parsed for one or more ebuilds. There's no point in
1576 # producing a false error here since the root cause will
1577 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1578 # or "ebuild.sytax".
1579 myfiles_all = set(myfiles_all)
1580 for entry in mydigests:
1581 if entry not in myfiles_all:
1582 stats["digest.unused"] += 1
1583 fails["digest.unused"].append(checkdir + "::" + entry)
1584 for entry in myfiles_all:
1585 if entry not in mydigests:
1586 stats["digest.missing"] += 1
1587 fails["digest.missing"].append(checkdir + "::" + entry)
1590 if os.path.exists(checkdir + "/files"):
1591 filesdirlist = os.listdir(checkdir + "/files")
1593 # recurse through files directory
1594 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1596 y = filesdirlist.pop(0)
1597 relative_path = os.path.join(x, "files", y)
1598 full_path = os.path.join(repodir, relative_path)
1600 mystat = os.stat(full_path)
1601 except OSError as oe:
1603 # don't worry about it. it likely was removed via fix above.
1607 if S_ISDIR(mystat.st_mode):
1608 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1609 if y == "CVS" or y == ".svn":
1611 for z in os.listdir(checkdir + "/files/" + y):
1612 if z == "CVS" or z == ".svn":
1614 filesdirlist.append(y + "/" + z)
1615 # Current policy is no files over 20 KiB, these are the checks. File size between
1616 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1617 elif mystat.st_size > 61440:
1618 stats["file.size.fatal"] += 1
1619 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1620 elif mystat.st_size > 20480:
1621 stats["file.size"] += 1
1622 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1624 index = repo_config.find_invalid_path_char(y)
1626 y_relative = os.path.join(checkdir_relative, "files", y)
1627 if vcs is not None and not vcs_new_changed(y_relative):
1628 # If the file isn't in the VCS new or changed set, then
1629 # assume that it's an irrelevant temporary file (Manifest
1630 # entries are not generated for file names containing
1631 # prohibited characters). See bug #406877.
1634 stats["file.name"] += 1
1635 fails["file.name"].append("%s/files/%s: char '%s'" % \
1636 (checkdir, y, y[index]))
1639 if check_changelog and "ChangeLog" not in checkdirlist:
1640 stats["changelog.missing"] += 1
1641 fails["changelog.missing"].append(x + "/ChangeLog")
1644 # metadata.xml file check
1645 if "metadata.xml" not in checkdirlist:
1646 stats["metadata.missing"] += 1
1647 fails["metadata.missing"].append(x + "/metadata.xml")
1648 # metadata.xml parse check
1650 metadata_bad = False
1652 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1654 # read metadata.xml into memory
1656 _metadata_xml = xml.etree.ElementTree.parse(
1657 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1658 encoding=_encodings['fs'], errors='strict'),
1660 except (ExpatError, SyntaxError, EnvironmentError) as e:
1662 stats["metadata.bad"] += 1
1663 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1666 if not hasattr(xml_parser, 'parser') or \
1667 sys.hexversion < 0x2070000 or \
1668 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1669 # doctype is not parsed with python 2.6 or 3.1
1672 if "XML_DECLARATION" not in xml_info:
1673 stats["metadata.bad"] += 1
1674 fails["metadata.bad"].append("%s/metadata.xml: "
1675 "xml declaration is missing on first line, "
1676 "should be '%s'" % (x, metadata_xml_declaration))
1678 xml_version, xml_encoding, xml_standalone = \
1679 xml_info["XML_DECLARATION"]
1680 if xml_encoding is None or \
1681 xml_encoding.upper() != metadata_xml_encoding:
1682 stats["metadata.bad"] += 1
1683 if xml_encoding is None:
1684 encoding_problem = "but it is undefined"
1686 encoding_problem = "not '%s'" % xml_encoding
1687 fails["metadata.bad"].append("%s/metadata.xml: "
1688 "xml declaration encoding should be '%s', %s" %
1689 (x, metadata_xml_encoding, encoding_problem))
1691 if "DOCTYPE" not in xml_info:
1693 stats["metadata.bad"] += 1
1694 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1695 "DOCTYPE is missing"))
1697 doctype_name, doctype_system, doctype_pubid = \
1699 if doctype_system != metadata_dtd_uri:
1700 stats["metadata.bad"] += 1
1701 if doctype_system is None:
1702 system_problem = "but it is undefined"
1704 system_problem = "not '%s'" % doctype_system
1705 fails["metadata.bad"].append("%s/metadata.xml: "
1706 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1707 (x, metadata_dtd_uri, system_problem))
1709 if doctype_name != metadata_doctype_name:
1710 stats["metadata.bad"] += 1
1711 fails["metadata.bad"].append("%s/metadata.xml: "
1712 "DOCTYPE: name should be '%s', not '%s'" %
1713 (x, metadata_doctype_name, doctype_name))
1715 # load USE flags from metadata.xml
1717 musedict = utilities.parse_metadata_use(_metadata_xml)
1718 except portage.exception.ParseError as e:
1720 stats["metadata.bad"] += 1
1721 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1723 for atom in chain(*musedict.values()):
1728 except InvalidAtom as e:
1729 stats["metadata.bad"] += 1
1730 fails["metadata.bad"].append(
1731 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1734 stats["metadata.bad"] += 1
1735 fails["metadata.bad"].append(
1736 ("%s/metadata.xml: Atom contains "
1737 "unexpected cat/pn: %s") % (x, atom))
1739 # Run other metadata.xml checkers
1741 utilities.check_metadata(_metadata_xml, herd_base)
1742 except (utilities.UnknownHerdsError, ) as e:
1744 stats["metadata.bad"] += 1
1745 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1748 #Only carry out if in package directory or check forced
1749 if xmllint_capable and not metadata_bad:
1750 # xmlint can produce garbage output even on success, so only dump
1751 # the ouput when it fails.
1752 st, out = repoman_getstatusoutput(
1753 "xmllint --nonet --noout --dtdvalid %s %s" % \
1754 (portage._shell_quote(metadata_dtd),
1755 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1757 print(red("!!!") + " metadata.xml is invalid:")
1758 for z in out.splitlines():
1759 print(red("!!! ") + z)
1760 stats["metadata.bad"] += 1
1761 fails["metadata.bad"].append(x + "/metadata.xml")
1764 muselist = frozenset(musedict)
1766 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1767 changelog_modified = changelog_path in modified_changelogs
1769 # detect unused local USE-descriptions
1770 used_useflags = set()
1772 for y in ebuildlist:
1773 relative_path = os.path.join(x, y + ".ebuild")
1774 full_path = os.path.join(repodir, relative_path)
1775 ebuild_path = y + ".ebuild"
1777 ebuild_path = os.path.join(pkgdir, ebuild_path)
1779 ebuild_path = os.path.join(catdir, ebuild_path)
1780 ebuild_path = os.path.join(".", ebuild_path)
1781 if check_changelog and not changelog_modified \
1782 and ebuild_path in new_ebuilds:
1783 stats['changelog.ebuildadded'] += 1
1784 fails['changelog.ebuildadded'].append(relative_path)
1786 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1787 #ebuild not added to vcs
1788 stats["ebuild.notadded"] += 1
1789 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1790 myesplit = portage.pkgsplit(y)
1791 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1792 or pv_toolong_re.search(myesplit[1]) \
1793 or pv_toolong_re.search(myesplit[2]):
1794 stats["ebuild.invalidname"] += 1
1795 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1797 elif myesplit[0] != pkgdir:
1798 print(pkgdir, myesplit[0])
1799 stats["ebuild.namenomatch"] += 1
1800 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1807 for k, msgs in pkg.invalid.items():
1810 fails[k].append("%s: %s" % (relative_path, msg))
1813 myaux = pkg._metadata
1814 eapi = myaux["EAPI"]
1815 inherited = pkg.inherited
1816 live_ebuild = live_eclasses.intersection(inherited)
1818 for k, v in myaux.items():
1819 if not isinstance(v, basestring):
1821 m = non_ascii_re.search(v)
1823 stats["variable.invalidchar"] += 1
1824 fails["variable.invalidchar"].append(
1825 ("%s: %s variable contains non-ASCII " + \
1826 "character at position %s") % \
1827 (relative_path, k, m.start() + 1))
1829 if not src_uri_error:
1830 # Check that URIs don't reference a server from thirdpartymirrors.
1831 for uri in portage.dep.use_reduce( \
1832 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1833 contains_mirror = False
1834 for mirror, mirror_alias in thirdpartymirrors.items():
1835 if uri.startswith(mirror):
1836 contains_mirror = True
1838 if not contains_mirror:
1841 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1842 stats["SRC_URI.mirror"] += 1
1843 fails["SRC_URI.mirror"].append(
1844 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1845 (relative_path, mirror, new_uri))
1847 if myaux.get("PROVIDE"):
1848 stats["virtual.oldstyle"] += 1
1849 fails["virtual.oldstyle"].append(relative_path)
1851 for pos, missing_var in enumerate(missingvars):
1852 if not myaux.get(missing_var):
1853 if catdir == "virtual" and \
1854 missing_var in ("HOMEPAGE", "LICENSE"):
1856 if live_ebuild and missing_var == "KEYWORDS":
1858 myqakey = missingvars[pos] + ".missing"
1860 fails[myqakey].append(x + "/" + y + ".ebuild")
1862 if catdir == "virtual":
1863 for var in ("HOMEPAGE", "LICENSE"):
1865 myqakey = var + ".virtual"
1867 fails[myqakey].append(relative_path)
1869 # 14 is the length of DESCRIPTION=""
1870 if len(myaux['DESCRIPTION']) > max_desc_len:
1871 stats['DESCRIPTION.toolong'] += 1
1872 fails['DESCRIPTION.toolong'].append(
1873 "%s: DESCRIPTION is %d characters (max %d)" % \
1874 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1876 keywords = myaux["KEYWORDS"].split()
1877 stable_keywords = []
1878 for keyword in keywords:
1879 if not keyword.startswith("~") and \
1880 not keyword.startswith("-"):
1881 stable_keywords.append(keyword)
1883 if ebuild_path in new_ebuilds and catdir != "virtual":
1884 stable_keywords.sort()
1885 stats["KEYWORDS.stable"] += 1
1886 fails["KEYWORDS.stable"].append(
1887 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1888 " ".join(stable_keywords))
1890 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1891 if not kw.startswith("-"))
1893 previous_keywords = slot_keywords.get(pkg.slot)
1894 if previous_keywords is None:
1895 slot_keywords[pkg.slot] = set()
1896 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1897 dropped_keywords = previous_keywords.difference(ebuild_archs)
1898 if dropped_keywords:
1899 stats["KEYWORDS.dropped"] += 1
1900 fails["KEYWORDS.dropped"].append(
1901 relative_path + ": %s" % \
1902 " ".join(sorted(dropped_keywords)))
1904 slot_keywords[pkg.slot].update(ebuild_archs)
1906 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1907 if "-*" in keywords:
1915 stats["KEYWORDS.stupid"] += 1
1916 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1919 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1920 not be allowed to be marked stable
1922 if live_ebuild and repo_config.name == "gentoo":
1923 bad_stable_keywords = []
1924 for keyword in keywords:
1925 if not keyword.startswith("~") and \
1926 not keyword.startswith("-"):
1927 bad_stable_keywords.append(keyword)
1929 if bad_stable_keywords:
1930 stats["LIVEVCS.stable"] += 1
1931 fails["LIVEVCS.stable"].append(
1932 x + "/" + y + ".ebuild with stable keywords:%s " % \
1933 bad_stable_keywords)
1934 del bad_stable_keywords
1936 if keywords and not has_global_mask(pkg):
1937 stats["LIVEVCS.unmasked"] += 1
1938 fails["LIVEVCS.unmasked"].append(relative_path)
1940 if options.ignore_arches:
1941 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1942 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1945 for keyword in keywords:
1946 if keyword[0] == "-":
1948 elif keyword[0] == "~":
1951 for expanded_arch in profiles:
1952 if expanded_arch == "**":
1954 arches.add((keyword, expanded_arch,
1955 (expanded_arch, "~" + expanded_arch)))
1957 arches.add((keyword, arch, (arch, keyword)))
1960 for expanded_arch in profiles:
1961 if expanded_arch == "**":
1963 arches.add((keyword, expanded_arch,
1966 arches.add((keyword, keyword, (keyword,)))
1968 # Use an empty profile for checking dependencies of
1969 # packages that have empty KEYWORDS.
1970 arches.add(('**', '**', ('**',)))
1972 unknown_pkgs = set()
1973 baddepsyntax = False
1974 badlicsyntax = False
1975 badprovsyntax = False
1976 catpkg = catdir + "/" + y
1978 inherited_java_eclass = "java-pkg-2" in inherited or \
1979 "java-pkg-opt-2" in inherited
1980 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1981 operator_tokens = set(["||", "(", ")"])
1982 type_list, badsyntax = [], []
1983 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
1984 mydepstr = myaux[mytype]
1986 buildtime = mytype in Package._buildtime_keys
1987 runtime = mytype in Package._runtime_keys
1989 if mytype.endswith("DEPEND"):
1990 token_class = portage.dep.Atom
1993 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1994 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1995 except portage.exception.InvalidDependString as e:
1997 badsyntax.append(str(e))
1999 if atoms and mytype.endswith("DEPEND"):
2001 "test?" in mydepstr.split():
2002 stats[mytype + '.suspect'] += 1
2003 fails[mytype + '.suspect'].append(relative_path + \
2004 ": 'test?' USE conditional in %s" % mytype)
2010 # Skip dependency.unknown for blockers, so that we
2011 # don't encourage people to remove necessary blockers,
2012 # as discussed in bug #382407.
2013 if atom.blocker is None and \
2014 not portdb.xmatch("match-all", atom) and \
2015 not atom.cp.startswith("virtual/"):
2016 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2018 is_blocker = atom.blocker
2020 if catdir != "virtual":
2021 if not is_blocker and \
2022 atom.cp in suspect_virtual:
2023 stats['virtual.suspect'] += 1
2024 fails['virtual.suspect'].append(
2026 ": %s: consider using '%s' instead of '%s'" %
2027 (mytype, suspect_virtual[atom.cp], atom))
2030 not is_blocker and \
2031 not inherited_java_eclass and \
2032 atom.cp == "virtual/jdk":
2033 stats['java.eclassesnotused'] += 1
2034 fails['java.eclassesnotused'].append(relative_path)
2035 elif buildtime and \
2036 not is_blocker and \
2037 not inherited_wxwidgets_eclass and \
2038 atom.cp == "x11-libs/wxGTK":
2039 stats['wxwidgets.eclassnotused'] += 1
2040 fails['wxwidgets.eclassnotused'].append(
2041 (relative_path + ": %ss on x11-libs/wxGTK"
2042 " without inheriting wxwidgets.eclass") % mytype)
2044 if not is_blocker and \
2045 atom.cp in suspect_rdepend:
2046 stats[mytype + '.suspect'] += 1
2047 fails[mytype + '.suspect'].append(
2048 relative_path + ": '%s'" % atom)
2050 if atom.operator == "~" and \
2051 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2052 qacat = 'dependency.badtilde'
2054 fails[qacat].append(
2055 (relative_path + ": %s uses the ~ operator"
2056 " with a non-zero revision:" + \
2057 " '%s'") % (mytype, atom))
2059 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2061 for m, b in zip(type_list, badsyntax):
2062 if m.endswith("DEPEND"):
2063 qacat = "dependency.syntax"
2065 qacat = m + ".syntax"
2067 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2069 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2070 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2071 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2072 badlicsyntax = badlicsyntax > 0
2073 badprovsyntax = badprovsyntax > 0
2075 # uselist checks - global
2078 for myflag in myaux["IUSE"].split():
2079 flag_name = myflag.lstrip("+-")
2080 used_useflags.add(flag_name)
2081 if myflag != flag_name:
2082 default_use.append(myflag)
2083 if flag_name not in uselist:
2084 myuse.append(flag_name)
2086 # uselist checks - metadata
2087 for mypos in range(len(myuse)-1, -1, -1):
2088 if myuse[mypos] and (myuse[mypos] in muselist):
2091 if default_use and not eapi_has_iuse_defaults(eapi):
2092 for myflag in default_use:
2093 stats['EAPI.incompatible'] += 1
2094 fails['EAPI.incompatible'].append(
2095 (relative_path + ": IUSE defaults" + \
2096 " not supported with EAPI='%s':" + \
2097 " '%s'") % (eapi, myflag))
2099 for mypos in range(len(myuse)):
2100 stats["IUSE.invalid"] += 1
2101 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2104 if not badlicsyntax:
2105 # Parse the LICENSE variable, remove USE conditions and
2107 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2108 # Check each entry to ensure that it exists in PORTDIR's
2109 # license directory.
2110 for lic in licenses:
2111 # Need to check for "||" manually as no portage
2112 # function will remove it without removing values.
2113 if lic not in liclist and lic != "||":
2114 stats["LICENSE.invalid"] += 1
2115 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2116 elif lic in liclist_deprecated:
2117 stats["LICENSE.deprecated"] += 1
2118 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2121 myuse = myaux["KEYWORDS"].split()
2123 if mykey not in ("-*", "*", "~*"):
2125 if myskey[:1] == "-":
2127 if myskey[:1] == "~":
2129 if myskey not in kwlist:
2130 stats["KEYWORDS.invalid"] += 1
2131 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2132 elif myskey not in profiles:
2133 stats["KEYWORDS.invalid"] += 1
2134 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2139 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2140 except portage.exception.InvalidDependString as e:
2141 stats["RESTRICT.syntax"] += 1
2142 fails["RESTRICT.syntax"].append(
2143 "%s: RESTRICT: %s" % (relative_path, e))
2146 myrestrict = set(myrestrict)
2147 mybadrestrict = myrestrict.difference(valid_restrict)
2149 stats["RESTRICT.invalid"] += len(mybadrestrict)
2150 for mybad in mybadrestrict:
2151 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2153 required_use = myaux["REQUIRED_USE"]
2155 if not eapi_has_required_use(eapi):
2156 stats['EAPI.incompatible'] += 1
2157 fails['EAPI.incompatible'].append(
2158 relative_path + ": REQUIRED_USE" + \
2159 " not supported with EAPI='%s'" % (eapi,))
2161 portage.dep.check_required_use(required_use, (),
2162 pkg.iuse.is_valid_flag, eapi=eapi)
2163 except portage.exception.InvalidDependString as e:
2164 stats["REQUIRED_USE.syntax"] += 1
2165 fails["REQUIRED_USE.syntax"].append(
2166 "%s: REQUIRED_USE: %s" % (relative_path, e))
2170 relative_path = os.path.join(x, y + ".ebuild")
2171 full_path = os.path.join(repodir, relative_path)
2172 if not vcs_preserves_mtime:
2173 if ebuild_path not in new_ebuilds and \
2174 ebuild_path not in modified_ebuilds:
2177 # All ebuilds should have utf_8 encoding.
2178 f = io.open(_unicode_encode(full_path,
2179 encoding=_encodings['fs'], errors='strict'),
2180 mode='r', encoding=_encodings['repo.content'])
2182 for check_name, e in run_checks(f, pkg):
2183 stats[check_name] += 1
2184 fails[check_name].append(relative_path + ': %s' % e)
2187 except UnicodeDecodeError:
2188 # A file.UTF8 failure will have already been recorded above.
2192 # The dep_check() calls are the most expensive QA test. If --force
2193 # is enabled, there's no point in wasting time on these since the
2194 # user is intent on forcing the commit anyway.
2197 relevant_profiles = []
2198 for keyword, arch, groups in arches:
2199 if arch not in profiles:
2200 # A missing profile will create an error further down
2201 # during the KEYWORDS verification.
2204 if include_arches is not None:
2205 if arch not in include_arches:
2208 relevant_profiles.extend((keyword, groups, prof)
2209 for prof in profiles[arch])
2212 return item[2].sub_path
2214 relevant_profiles.sort(key=sort_key)
2216 for keyword, groups, prof in relevant_profiles:
2218 if prof.status not in ("stable", "dev") or \
2219 prof.status == "dev" and not options.include_dev:
2222 dep_settings = arch_caches.get(prof.sub_path)
2223 if dep_settings is None:
2224 dep_settings = portage.config(
2225 config_profile_path=prof.abs_path,
2226 config_incrementals=repoman_incrementals,
2227 config_root=config_root,
2229 _unmatched_removal=options.unmatched_removal,
2231 dep_settings.categories = repoman_settings.categories
2232 if options.without_mask:
2233 dep_settings._mask_manager_obj = \
2234 copy.deepcopy(dep_settings._mask_manager)
2235 dep_settings._mask_manager._pmaskdict.clear()
2236 arch_caches[prof.sub_path] = dep_settings
2238 xmatch_cache_key = (prof.sub_path, tuple(groups))
2239 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2243 xcache = portdb.xcache
2244 xcache.update(shared_xmatch_caches)
2245 arch_xmatch_caches[xmatch_cache_key] = xcache
2247 trees[root]["porttree"].settings = dep_settings
2248 portdb.settings = dep_settings
2249 portdb.xcache = xcache
2251 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2252 # just in case, prevent config.reset() from nuking these.
2253 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2255 # This attribute is used in dbapi._match_use() to apply
2256 # use.stable.{mask,force} settings based on the stable
2257 # status of the parent package. This is required in order
2258 # for USE deps of unstable packages to be resolved correctly,
2259 # since otherwise use.stable.{mask,force} settings of
2260 # dependencies may conflict (see bug #456342).
2261 dep_settings._parent_stable = dep_settings._isStable(pkg)
2263 # Handle package.use*.{force,mask) calculation, for use
2265 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2266 pkg, stable=dep_settings._parent_stable)
2267 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2268 pkg, stable=dep_settings._parent_stable)
2270 if not baddepsyntax:
2271 ismasked = not ebuild_archs or \
2272 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2274 if not have_pmasked:
2275 have_pmasked = bool(dep_settings._getMaskAtom(
2276 pkg.cpv, pkg._metadata))
2277 if options.ignore_masked:
2279 #we are testing deps for a masked package; give it some lee-way
2281 matchmode = "minimum-all"
2284 matchmode = "minimum-visible"
2286 if not have_dev_keywords:
2287 have_dev_keywords = \
2288 bool(dev_keywords.intersection(keywords))
2290 if prof.status == "dev":
2291 suffix = suffix + "indev"
2293 for mytype in Package._dep_keys:
2295 mykey = "dependency.bad" + suffix
2296 myvalue = myaux[mytype]
2300 success, atoms = portage.dep_check(myvalue, portdb,
2301 dep_settings, use="all", mode=matchmode,
2307 # Don't bother with dependency.unknown for
2308 # cases in which *DEPEND.bad is triggered.
2310 # dep_check returns all blockers and they
2311 # aren't counted for *DEPEND.bad, so we
2313 if not atom.blocker:
2314 unknown_pkgs.discard(
2315 (mytype, atom.unevaluated_atom))
2317 if not prof.sub_path:
2318 # old-style virtuals currently aren't
2319 # resolvable with empty profile, since
2320 # 'virtuals' mappings are unavailable
2321 # (it would be expensive to search
2322 # for PROVIDE in all ebuilds)
2323 atoms = [atom for atom in atoms if not \
2324 (atom.cp.startswith('virtual/') and \
2325 not portdb.cp_list(atom.cp))]
2327 #we have some unsolvable deps
2328 #remove ! deps, which always show up as unsatisfiable
2329 atoms = [str(atom.unevaluated_atom) \
2330 for atom in atoms if not atom.blocker]
2332 #if we emptied out our list, continue:
2336 fails[mykey].append("%s: %s: %s(%s) %s" % \
2337 (relative_path, mytype, keyword,
2341 fails[mykey].append("%s: %s: %s(%s) %s" % \
2342 (relative_path, mytype, keyword,
2345 if not baddepsyntax and unknown_pkgs:
2347 for mytype, atom in unknown_pkgs:
2348 type_map.setdefault(mytype, set()).add(atom)
2349 for mytype, atoms in type_map.items():
2350 stats["dependency.unknown"] += 1
2351 fails["dependency.unknown"].append("%s: %s: %s" %
2352 (relative_path, mytype, ", ".join(sorted(atoms))))
2354 # check if there are unused local USE-descriptions in metadata.xml
2355 # (unless there are any invalids, to avoid noise)
2357 for myflag in muselist.difference(used_useflags):
2358 stats["metadata.warning"] += 1
2359 fails["metadata.warning"].append(
2360 "%s/metadata.xml: unused local USE-description: '%s'" % \
2363 if options.if_modified == "y" and len(effective_scanlist) < 1:
2364 logging.warn("--if-modified is enabled, but no modified packages were found!")
2366 if options.mode == "manifest":
2369 # dofail will be set to 1 if we have failed in at least one non-warning category
2371 # dowarn will be set to 1 if we tripped any warnings
2373 # dofull will be set if we should print a "repoman full" informational message
2374 dofull = options.mode != 'full'
2380 if x not in qawarnings:
2384 (dowarn and not (options.quiet or options.mode == "scan")):
2387 # Save QA output so that it can be conveniently displayed
2388 # in $EDITOR while the user creates a commit message.
2389 # Otherwise, the user would not be able to see this output
2390 # once the editor has taken over the screen.
2391 qa_output = io.StringIO()
2392 style_file = ConsoleStyleFile(sys.stdout)
2393 if options.mode == 'commit' and \
2394 (not commitmessage or not commitmessage.strip()):
2395 style_file.write_listener = qa_output
2396 console_writer = StyleWriter(file=style_file, maxcol=9999)
2397 console_writer.style_listener = style_file.new_styles
2399 f = formatter.AbstractFormatter(console_writer)
2401 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2404 del console_writer, f, style_file
2405 qa_output = qa_output.getvalue()
2406 qa_output = qa_output.splitlines(True)
2408 suggest_ignore_masked = False
2409 suggest_include_dev = False
2411 if have_pmasked and not (options.without_mask or options.ignore_masked):
2412 suggest_ignore_masked = True
2413 if have_dev_keywords and not options.include_dev:
2414 suggest_include_dev = True
2416 if suggest_ignore_masked or suggest_include_dev:
2418 if suggest_ignore_masked:
2419 print(bold("Note: use --without-mask to check " + \
2420 "KEYWORDS on dependencies of masked packages"))
2422 if suggest_include_dev:
2423 print(bold("Note: use --include-dev (-d) to check " + \
2424 "dependencies for 'dev' profiles"))
2427 if options.mode != 'commit':
2429 print(bold("Note: type \"repoman full\" for a complete listing."))
2430 if dowarn and not dofail:
2431 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.\"")
2433 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2435 print(bad("Please fix these important QA issues first."))
2436 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2439 if dofail and can_force and options.force and not options.pretend:
2440 print(green("RepoMan sez:") + \
2441 " \"You want to commit even with these QA issues?\n" + \
2442 " I'll take it this time, but I'm not happy.\"\n")
2444 if options.force and not can_force:
2445 print(bad("The --force option has been disabled due to extraordinary issues."))
2446 print(bad("Please fix these important QA issues first."))
2447 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2451 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2456 myvcstree = portage.cvstree.getentries("./", recursive=1)
2457 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2458 except SystemExit as e:
2459 raise # TODO propagate this
2461 err("Error retrieving CVS tree; exiting.")
2464 with repoman_popen("svn status --no-ignore") as f:
2465 svnstatus = f.readlines()
2466 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2467 except SystemExit as e:
2468 raise # TODO propagate this
2470 err("Error retrieving SVN info; exiting.")
2472 # get list of files not under version control or missing
2473 myf = repoman_popen("git ls-files --others")
2474 myunadded = ["./" + elem[:-1] for elem in myf]
2478 with repoman_popen("bzr status -S .") as f:
2479 bzrstatus = f.readlines()
2480 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2481 except SystemExit as e:
2482 raise # TODO propagate this
2484 err("Error retrieving bzr info; exiting.")
2486 with repoman_popen("hg status --no-status --unknown .") as f:
2487 myunadded = f.readlines()
2488 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2490 # Mercurial doesn't handle manually deleted files as removed from
2491 # the repository, so the user need to remove them before commit,
2492 # using "hg remove [FILES]"
2493 with repoman_popen("hg status --no-status --deleted .") as f:
2494 mydeleted = f.readlines()
2495 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2500 for x in range(len(myunadded)-1, -1, -1):
2501 xs = myunadded[x].split("/")
2502 if xs[-1] == "files":
2503 print("!!! files dir is not added! Please correct this.")
2505 elif xs[-1] == "Manifest":
2506 # It's a manifest... auto add
2507 myautoadd += [myunadded[x]]
2511 print(red("!!! The following files are in your local tree but are not added to the master"))
2512 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2519 if vcs == "hg" and mydeleted:
2520 print(red("!!! The following files are removed manually from your local tree but are not"))
2521 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2529 mycvstree = cvstree.getentries("./", recursive=1)
2530 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2531 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2532 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2533 bin_blob_pattern = re.compile("^-kb$")
2534 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2535 recursive=1, basedir="./"))
2538 with repoman_popen("svn status") as f:
2539 svnstatus = f.readlines()
2540 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2541 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2542 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2544 # Subversion expands keywords specified in svn:keywords properties.
2545 with repoman_popen("svn propget -R svn:keywords") as f:
2546 props = f.readlines()
2547 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2548 for prop in props if " - " in prop)
2551 with repoman_popen("git diff-index --name-only "
2552 "--relative --diff-filter=M HEAD") as f:
2553 mychanged = f.readlines()
2554 mychanged = ["./" + elem[:-1] for elem in mychanged]
2556 with repoman_popen("git diff-index --name-only "
2557 "--relative --diff-filter=A HEAD") as f:
2558 mynew = f.readlines()
2559 mynew = ["./" + elem[:-1] for elem in mynew]
2561 with repoman_popen("git diff-index --name-only "
2562 "--relative --diff-filter=D HEAD") as f:
2563 myremoved = f.readlines()
2564 myremoved = ["./" + elem[:-1] for elem in myremoved]
2567 with repoman_popen("bzr status -S .") as f:
2568 bzrstatus = f.readlines()
2569 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2570 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")]
2571 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2572 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")]
2573 # Bazaar expands nothing.
2576 with repoman_popen("hg status --no-status --modified .") as f:
2577 mychanged = f.readlines()
2578 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2580 with repoman_popen("hg status --no-status --added .") as f:
2581 mynew = f.readlines()
2582 mynew = ["./" + elem.rstrip() for elem in mynew]
2584 with repoman_popen("hg status --no-status --removed .") as f:
2585 myremoved = f.readlines()
2586 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2589 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2590 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2592 print("(Didn't find any changed files...)")
2596 # Manifests need to be regenerated after all other commits, so don't commit
2597 # them now even if they have changed.
2600 for f in mychanged + mynew:
2601 if "Manifest" == os.path.basename(f):
2605 myupdates.difference_update(myremoved)
2606 myupdates = list(myupdates)
2607 mymanifests = list(mymanifests)
2611 commitmessage = options.commitmsg
2612 if options.commitmsgfile:
2614 f = io.open(_unicode_encode(options.commitmsgfile,
2615 encoding=_encodings['fs'], errors='strict'),
2616 mode='r', encoding=_encodings['content'], errors='replace')
2617 commitmessage = f.read()
2620 except (IOError, OSError) as e:
2621 if e.errno == errno.ENOENT:
2622 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2625 # We've read the content so the file is no longer needed.
2626 commitmessagefile = None
2627 if not commitmessage or not commitmessage.strip():
2629 editor = os.environ.get("EDITOR")
2630 if editor and utilities.editor_is_executable(editor):
2631 commitmessage = utilities.get_commit_message_with_editor(
2632 editor, message=qa_output)
2634 commitmessage = utilities.get_commit_message_with_stdin()
2635 except KeyboardInterrupt:
2637 if not commitmessage or not commitmessage.strip():
2638 print("* no commit message? aborting commit.")
2640 commitmessage = commitmessage.rstrip()
2641 changelog_msg = commitmessage
2642 portage_version = getattr(portage, "VERSION", None)
2643 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2644 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2645 if portage_version is None:
2646 sys.stderr.write("Failed to insert portage version in message!\n")
2648 portage_version = "Unknown"
2652 report_options.append("--force")
2653 if options.ignore_arches:
2654 report_options.append("--ignore-arches")
2655 if include_arches is not None:
2656 report_options.append("--include-arches=\"%s\"" %
2657 " ".join(sorted(include_arches)))
2660 # Use new footer only for git (see bug #438364).
2661 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2663 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2665 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2667 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2669 unameout = platform.system() + " "
2670 if platform.system() in ["Darwin", "SunOS"]:
2671 unameout += platform.processor()
2673 unameout += platform.machine()
2674 commit_footer = "\n\n"
2676 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2677 commit_footer += "(Portage version: %s/%s/%s" % \
2678 (portage_version, vcs, unameout)
2680 commit_footer += ", RepoMan options: " + " ".join(report_options)
2682 commit_footer += ", signed Manifest commit with key %s" % \
2685 commit_footer += ", unsigned Manifest commit"
2686 commit_footer += ")"
2688 commitmessage += commit_footer
2690 if options.echangelog in ('y', 'force'):
2691 logging.info("checking for unmodified ChangeLog files")
2692 committer_name = utilities.get_committer_name(env=repoman_settings)
2693 for x in sorted(vcs_files_to_cps(
2694 chain(myupdates, mymanifests, myremoved))):
2695 catdir, pkgdir = x.split("/")
2696 checkdir = repodir + "/" + x
2697 checkdir_relative = ""
2699 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2701 checkdir_relative = os.path.join(catdir, checkdir_relative)
2702 checkdir_relative = os.path.join(".", checkdir_relative)
2704 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2705 changelog_modified = changelog_path in modified_changelogs
2706 if changelog_modified and options.echangelog != 'force':
2709 # get changes for this package
2710 cdrlen = len(checkdir_relative)
2711 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2712 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2713 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2715 # Skip ChangeLog generation if only the Manifest was modified,
2716 # as discussed in bug #398009.
2717 nontrivial_cl_files = set()
2718 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2719 nontrivial_cl_files.difference_update(['Manifest'])
2720 if not nontrivial_cl_files and options.echangelog != 'force':
2723 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2724 committer_name, changelog_msg,
2725 os.path.join(repodir, 'skel.ChangeLog'),
2727 new=clnew, removed=clremoved, changed=clchanged,
2728 pretend=options.pretend)
2729 if new_changelog is None:
2730 writemsg_level("!!! Updating the ChangeLog failed\n", \
2731 level=logging.ERROR, noiselevel=-1)
2734 # if the ChangeLog was just created, add it to vcs
2736 myautoadd.append(changelog_path)
2737 # myautoadd is appended to myupdates below
2739 myupdates.append(changelog_path)
2741 if options.ask and not options.pretend:
2742 # regenerate Manifest for modified ChangeLog (bug #420735)
2743 repoman_settings["O"] = checkdir
2744 digestgen(mysettings=repoman_settings, myportdb=portdb)
2747 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2748 add_cmd = [vcs, "add"]
2749 add_cmd += myautoadd
2751 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2754 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2755 # Python 3.1 produces the following TypeError if raw bytes are
2756 # passed to subprocess.call():
2757 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2758 # errread, errwrite)
2759 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2760 # raise child_exception
2761 # TypeError: expected an object with the buffer interface
2762 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2763 retcode = subprocess.call(add_cmd)
2764 if retcode != os.EX_OK:
2766 "Exiting on %s error code: %s\n" % (vcs, retcode))
2769 myupdates += myautoadd
2771 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2773 if vcs not in ('cvs', 'svn'):
2774 # With git, bzr and hg, there's never any keyword expansion, so
2775 # there's no need to regenerate manifests and all files will be
2776 # committed in one big commit at the end.
2778 elif not repo_config.thin_manifest:
2780 headerstring = "'\$(Header|Id).*\$'"
2782 svn_keywords = dict((k.lower(), k) for k in [
2785 "LastChangedRevision",
2796 for myfile in myupdates:
2798 # for CVS, no_expansion contains files that are excluded from expansion
2800 if myfile in no_expansion:
2803 # for SVN, expansion contains files that are included in expansion
2805 if myfile not in expansion:
2808 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2809 enabled_keywords = []
2810 for k in expansion[myfile]:
2811 keyword = svn_keywords.get(k.lower())
2812 if keyword is not None:
2813 enabled_keywords.append(keyword)
2815 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2817 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2818 portage._shell_quote(myfile))
2820 myheaders.append(myfile)
2822 print("%s have headers that will change." % green(str(len(myheaders))))
2823 print("* Files with headers will cause the manifests to be changed and committed separately.")
2825 logging.info("myupdates: %s", myupdates)
2826 logging.info("myheaders: %s", myheaders)
2828 if options.ask and userquery('Commit changes?', True) != 'Yes':
2829 print("* aborting commit.")
2830 sys.exit(128 + signal.SIGINT)
2832 # Handle the case where committed files have keywords which
2833 # will change and need a priming commit before the Manifest
2835 if (myupdates or myremoved) and myheaders:
2836 myfiles = myupdates + myremoved
2837 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2838 mymsg = os.fdopen(fd, "wb")
2839 mymsg.write(_unicode_encode(commitmessage))
2843 print(green("Using commit message:"))
2844 print(green("------------------------------------------------------------------------------"))
2845 print(commitmessage)
2846 print(green("------------------------------------------------------------------------------"))
2849 # Having a leading ./ prefix on file paths can trigger a bug in
2850 # the cvs server when committing files to multiple directories,
2851 # so strip the prefix.
2852 myfiles = [f.lstrip("./") for f in myfiles]
2855 commit_cmd.extend(vcs_global_opts)
2856 commit_cmd.append("commit")
2857 commit_cmd.extend(vcs_local_opts)
2858 commit_cmd.extend(["-F", commitmessagefile])
2859 commit_cmd.extend(myfiles)
2863 print("(%s)" % (" ".join(commit_cmd),))
2865 retval = spawn(commit_cmd, env=commit_env)
2866 if retval != os.EX_OK:
2867 writemsg_level(("!!! Exiting on %s (shell) " + \
2868 "error code: %s\n") % (vcs, retval),
2869 level=logging.ERROR, noiselevel=-1)
2873 os.unlink(commitmessagefile)
2877 # Setup the GPG commands
2878 def gpgsign(filename):
2879 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2881 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2882 " Is make.globals missing?")
2883 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2884 "PORTAGE_GPG_KEY" not in repoman_settings:
2885 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2886 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2887 if "PORTAGE_GPG_DIR" not in repoman_settings:
2888 repoman_settings["PORTAGE_GPG_DIR"] = \
2889 os.path.expanduser("~/.gnupg")
2890 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2891 % repoman_settings["PORTAGE_GPG_DIR"])
2893 repoman_settings["PORTAGE_GPG_DIR"] = \
2894 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2895 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2896 raise portage.exception.InvalidLocation(
2897 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2898 repoman_settings["PORTAGE_GPG_DIR"])
2899 gpgvars = {"FILE": filename}
2900 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2901 v = repoman_settings.get(k)
2904 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2906 print("(" + gpgcmd + ")")
2908 # Encode unicode manually for bug #310789.
2909 gpgcmd = portage.util.shlex_split(gpgcmd)
2910 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
2911 # Python 3.1 does not support bytes in Popen args.
2912 gpgcmd = [_unicode_encode(arg,
2913 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2914 rValue = subprocess.call(gpgcmd)
2915 if rValue == os.EX_OK:
2916 os.rename(filename + ".asc", filename)
2918 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2920 def need_signature(filename):
2922 with open(_unicode_encode(filename,
2923 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2924 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2925 except IOError as e:
2926 if e.errno in (errno.ENOENT, errno.ESTALE):
2930 # When files are removed and re-added, the cvs server will put /Attic/
2931 # inside the $Header path. This code detects the problem and corrects it
2932 # so that the Manifest will generate correctly. See bug #169500.
2933 # Use binary mode in order to avoid potential character encoding issues.
2934 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2935 attic_str = b'/Attic/'
2936 attic_replace = b'/'
2938 f = open(_unicode_encode(x,
2939 encoding=_encodings['fs'], errors='strict'),
2941 mylines = f.readlines()
2944 for i, line in enumerate(mylines):
2945 if cvs_header_re.match(line) is not None and \
2947 mylines[i] = line.replace(attic_str, attic_replace)
2950 portage.util.write_atomic(x, b''.join(mylines),
2954 print(green("RepoMan sez:"), "\"You're rather crazy... "
2955 "doing the entire repository.\"\n")
2957 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2959 for x in sorted(vcs_files_to_cps(
2960 chain(myupdates, myremoved, mymanifests))):
2961 repoman_settings["O"] = os.path.join(repodir, x)
2962 digestgen(mysettings=repoman_settings, myportdb=portdb)
2968 for x in sorted(vcs_files_to_cps(
2969 chain(myupdates, myremoved, mymanifests))):
2970 repoman_settings["O"] = os.path.join(repodir, x)
2971 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
2972 if not need_signature(manifest_path):
2974 gpgsign(manifest_path)
2975 except portage.exception.PortageException as e:
2976 portage.writemsg("!!! %s\n" % str(e))
2977 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2981 # It's not safe to use the git commit -a option since there might
2982 # be some modified files elsewhere in the working tree that the
2983 # user doesn't want to commit. Therefore, call git update-index
2984 # in order to ensure that the index is updated with the latest
2985 # versions of all new and modified files in the relevant portion
2986 # of the working tree.
2987 myfiles = mymanifests + myupdates
2989 update_index_cmd = ["git", "update-index"]
2990 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2992 print("(%s)" % (" ".join(update_index_cmd),))
2994 retval = spawn(update_index_cmd, env=os.environ)
2995 if retval != os.EX_OK:
2996 writemsg_level(("!!! Exiting on %s (shell) " + \
2997 "error code: %s\n") % (vcs, retval),
2998 level=logging.ERROR, noiselevel=-1)
3002 myfiles = mymanifests[:]
3003 # If there are no header (SVN/CVS keywords) changes in
3004 # the files, this Manifest commit must include the
3005 # other (yet uncommitted) files.
3007 myfiles += myupdates
3008 myfiles += myremoved
3011 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3012 mymsg = os.fdopen(fd, "wb")
3013 mymsg.write(_unicode_encode(commitmessage))
3017 if options.pretend and vcs is None:
3018 # substitute a bogus value for pretend output
3019 commit_cmd.append("cvs")
3021 commit_cmd.append(vcs)
3022 commit_cmd.extend(vcs_global_opts)
3023 commit_cmd.append("commit")
3024 commit_cmd.extend(vcs_local_opts)
3026 commit_cmd.extend(["--logfile", commitmessagefile])
3027 commit_cmd.extend(myfiles)
3029 commit_cmd.extend(["-F", commitmessagefile])
3030 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3034 print("(%s)" % (" ".join(commit_cmd),))
3036 retval = spawn(commit_cmd, env=commit_env)
3037 if retval != os.EX_OK:
3038 if repo_config.sign_commit and vcs == 'git' and \
3039 not git_supports_gpg_sign():
3040 # Inform user that newer git is needed (bug #403323).
3042 "Git >=1.7.9 is required for signed commits!")
3044 writemsg_level(("!!! Exiting on %s (shell) " + \
3045 "error code: %s\n") % (vcs, retval),
3046 level=logging.ERROR, noiselevel=-1)
3050 os.unlink(commitmessagefile)
3056 print("Commit complete.")
3058 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3059 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")