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 import portage.repository.config
69 from portage import cvstree, normalize_path
70 from portage import util
71 from portage.exception import (FileNotFound, InvalidAtom, MissingParameter,
72 ParseError, PermissionDenied)
73 from portage.dep import Atom
74 from portage.process import find_binary, spawn
75 from portage.output import bold, create_color_func, \
77 from portage.output import ConsoleStyleFile, StyleWriter
78 from portage.util import writemsg_level
79 from portage.package.ebuild.digestgen import digestgen
80 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
82 if sys.hexversion >= 0x3000000:
85 util.initialize_logger()
87 # 14 is the length of DESCRIPTION=""
89 allowed_filename_chars="a-zA-Z0-9._-+:"
90 pv_toolong_re = re.compile(r'[0-9]{19,}')
91 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})!?'
92 bad = create_color_func("BAD")
94 # A sane umask is needed for files that portage creates.
96 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
97 # behave incrementally.
98 repoman_incrementals = tuple(x for x in \
99 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
100 config_root = os.environ.get("PORTAGE_CONFIGROOT")
101 repoman_settings = portage.config(config_root=config_root, local_config=False)
103 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
104 repoman_settings.get('TERM') == 'dumb' or \
105 not sys.stdout.isatty():
109 print("repoman: " + txt)
115 def exithandler(signum=None, frame=None):
116 logging.fatal("Interrupted; exiting...")
120 sys.exit(128 + signum)
122 signal.signal(signal.SIGINT, exithandler)
124 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
125 """Repoman needs its own HelpFormatter for now, because the default ones
126 murder the help text."""
128 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
129 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
131 def format_description(self, description):
134 class RepomanOptionParser(optparse.OptionParser):
135 """Add the on_tail function, ruby has it, optionParser should too
138 def __init__(self, *args, **kwargs):
139 optparse.OptionParser.__init__(self, *args, **kwargs)
142 def on_tail(self, description):
143 self.tail += description
145 def format_help(self, formatter=None):
146 result = optparse.OptionParser.format_help(self, formatter)
151 def ParseArgs(argv, qahelp):
152 """This function uses a customized optionParser to parse command line arguments for repoman
154 argv - a sequence of command line arguments
155 qahelp - a dict of qa warning to help message
157 (opts, args), just like a call to parser.parse_args()
160 argv = portage._decode_argv(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 "repo.eapi.banned": "The ebuild uses an EAPI which is banned by the repository's metadata/layout.conf settings",
384 "repo.eapi.deprecated": "The ebuild uses an EAPI which is deprecated by the repository's metadata/layout.conf settings",
385 "virtual.oldstyle": "The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
386 "virtual.suspect": "Ebuild contains a package that usually should be pulled via virtual/, not directly.",
387 "usage.obsolete": "The ebuild makes use of an obsolete construct",
388 "upstream.workaround": "The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
391 qacats = list(qahelp)
396 "changelog.notadded",
397 "dependency.unknown",
402 "dependency.badmasked",
403 "dependency.badindev",
404 "dependency.badmaskedindev",
405 "dependency.badtilde",
406 "DESCRIPTION.toolong",
409 "LICENSE.deprecated",
424 "inherit.deprecated",
425 "java.eclassesnotused",
426 "wxwidgets.eclassnotused",
429 "repo.eapi.deprecated",
431 "upstream.workaround",
436 if portage.const._ENABLE_INHERIT_CHECK:
437 # This is experimental, so it's non-fatal.
438 qawarnings.add("inherit.missing")
440 non_ascii_re = re.compile(r'[^\x00-\x7f]')
442 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
443 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
444 allvars.update(Package.metadata_keys)
445 allvars = sorted(allvars)
447 for x in missingvars:
450 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
454 valid_restrict = frozenset(["binchecks", "bindist",
455 "fetch", "installsources", "mirror", "preserve-libs",
456 "primaryuri", "splitdebug", "strip", "test", "userpriv"])
458 live_eclasses = frozenset([
469 suspect_rdepend = frozenset([
470 "app-arch/cabextract",
471 "app-arch/rpm2targz",
476 "dev-perl/extutils-pkgconfig",
482 "dev-util/gtk-doc-am",
485 "dev-util/pkg-config-lite",
487 "dev-util/pkgconfig",
488 "dev-util/pkgconfig-openbsd",
492 "media-gfx/ebdftopcf",
494 "sys-devel/autoconf",
495 "sys-devel/automake",
502 "virtual/linux-sources",
509 "dev-util/pkg-config-lite":"virtual/pkgconfig",
510 "dev-util/pkgconf":"virtual/pkgconfig",
511 "dev-util/pkgconfig":"virtual/pkgconfig",
512 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
513 "dev-libs/libusb":"virtual/libusb",
514 "dev-libs/libusbx":"virtual/libusb",
515 "dev-libs/libusb-compat":"virtual/libusb",
518 metadata_xml_encoding = 'UTF-8'
519 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
520 (metadata_xml_encoding,)
521 metadata_doctype_name = 'pkgmetadata'
522 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
523 # force refetch if the local copy creation time is older than this
524 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
527 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
529 options, arguments = ParseArgs(sys.argv, qahelp)
532 print("Portage", portage.VERSION)
535 # Set this to False when an extraordinary issue (generally
536 # something other than a QA issue) makes it impossible to
537 # commit (like if Manifest generation fails).
540 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
544 myreporoot = os.path.basename(portdir_overlay)
545 myreporoot += mydir[len(portdir_overlay):]
548 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
553 vcses = utilities.FindVCS()
555 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
556 print(red('*** Please either clean up your workdir or specify --vcs option.'))
563 if options.if_modified == "y" and vcs is None:
564 logging.info("Not in a version controlled repository; "
565 "disabling --if-modified.")
566 options.if_modified = "n"
568 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
569 vcs_preserves_mtime = vcs in ('cvs',)
571 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
572 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
573 if vcs_global_opts is None:
574 if vcs in ('cvs', 'svn'):
575 vcs_global_opts = "-q"
578 vcs_global_opts = vcs_global_opts.split()
580 if options.mode == 'commit' and not options.pretend and not vcs:
581 logging.info("Not in a version controlled repository; enabling pretend mode.")
582 options.pretend = True
584 # Ensure that current repository is in the list of enabled repositories.
585 repodir = os.path.realpath(portdir_overlay)
587 repoman_settings.repositories.get_repo_for_location(repodir)
589 repo_name = portage.repository.config.RepoConfig._read_valid_repo_name(portdir_overlay)[0]
590 layout_conf_data = portage.repository.config.parse_layout_conf(portdir_overlay)[0]
591 if layout_conf_data['repo-name']:
592 repo_name = layout_conf_data['repo-name']
593 tmp_conf_file = io.StringIO(textwrap.dedent("""
596 """) % (repo_name, portdir_overlay))
597 # Ensure that the repository corresponding to $PWD overrides a
598 # repository of the same name referenced by the existing PORTDIR
599 # or PORTDIR_OVERLAY settings.
600 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
601 (repoman_settings.get('PORTDIR_OVERLAY', ''),
602 portage._shell_quote(portdir_overlay))
603 repositories = portage.repository.config.load_repository_config(repoman_settings, extra_files=[tmp_conf_file])
604 # We have to call the config constructor again so that attributes
605 # dependent on config.repositories are initialized correctly.
606 repoman_settings = portage.config(config_root=config_root, local_config=False, repositories=repositories)
608 root = repoman_settings['EROOT']
610 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
612 portdb = trees[root]['porttree'].dbapi
614 # Constrain dependency resolution to the master(s)
615 # that are specified in layout.conf.
616 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
617 portdb.porttrees = list(repo_config.eclass_db.porttrees)
618 portdir = portdb.porttrees[0]
619 commit_env = os.environ.copy()
620 # list() is for iteration on a copy.
621 for repo in list(repoman_settings.repositories):
622 # all paths are canonical
623 if repo.location not in repo_config.eclass_db.porttrees:
624 del repoman_settings.repositories[repo.name]
626 if repo_config.allow_provide_virtual:
627 qawarnings.add("virtual.oldstyle")
629 if repo_config.sign_commit:
631 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
632 # the commit arguments. If key_id is unspecified, then it must be
633 # configured by `git config user.signingkey key_id`.
634 vcs_local_opts.append("--gpg-sign")
635 if repoman_settings.get("PORTAGE_GPG_DIR"):
636 # Pass GNUPGHOME to git for bug #462362.
637 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
639 # In order to disable manifest signatures, repos may set
640 # "sign-manifests = false" in metadata/layout.conf. This
641 # can be used to prevent merge conflicts like those that
642 # thin-manifests is designed to prevent.
643 sign_manifests = "sign" in repoman_settings.features and \
644 repo_config.sign_manifest
646 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
647 options.mode in ("commit",) and not sign_manifests:
648 msg = ("The '%s' repository has manifest signatures enabled, "
649 "but FEATURES=sign is currently disabled. In order to avoid this "
650 "warning, enable FEATURES=sign in make.conf. Alternatively, "
651 "repositories can disable manifest signatures by setting "
652 "'sign-manifests = false' in metadata/layout.conf.") % \
654 for line in textwrap.wrap(msg, 60):
657 if sign_manifests and options.mode in ("commit",) and \
658 repoman_settings.get("PORTAGE_GPG_KEY") and \
659 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
660 repoman_settings["PORTAGE_GPG_KEY"]) is None:
661 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
662 repoman_settings["PORTAGE_GPG_KEY"])
665 manifest_hashes = repo_config.manifest_hashes
666 if manifest_hashes is None:
667 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
669 if options.mode in ("commit", "fix", "manifest"):
670 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
671 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
672 "metadata/layout.conf does not contain the '%s' hash which "
673 "is required by this portage version. You will have to "
674 "upgrade portage if you want to generate valid manifests for "
675 "this repository.") % \
676 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
677 for line in textwrap.wrap(msg, 70):
681 unsupported_hashes = manifest_hashes.difference(
682 portage.const.MANIFEST2_HASH_FUNCTIONS)
683 if unsupported_hashes:
684 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
685 "metadata/layout.conf contains one or more hash types '%s' "
686 "which are not supported by this portage version. You will "
687 "have to upgrade portage if you want to generate valid "
688 "manifests for this repository.") % \
689 (repo_config.name, " ".join(sorted(unsupported_hashes)))
690 for line in textwrap.wrap(msg, 70):
694 if options.echangelog is None and repo_config.update_changelog:
695 options.echangelog = 'y'
698 options.echangelog = 'n'
700 # The --echangelog option causes automatic ChangeLog generation,
701 # which invalidates changelog.ebuildadded and changelog.missing
703 # Note: Some don't use ChangeLogs in distributed SCMs.
704 # It will be generated on server side from scm log,
705 # before package moves to the rsync server.
706 # This is needed because they try to avoid merge collisions.
707 # Gentoo's Council decided to always use the ChangeLog file.
708 # TODO: shouldn't this just be switched on the repo, iso the VCS?
709 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
711 if 'digest' in repoman_settings.features and options.digest != 'n':
714 logging.debug("vcs: %s" % (vcs,))
715 logging.debug("repo config: %s" % (repo_config,))
716 logging.debug("options: %s" % (options,))
718 # It's confusing if these warnings are displayed without the user
719 # being told which profile they come from, so disable them.
720 env = os.environ.copy()
721 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
724 for path in repo_config.eclass_db.porttrees:
725 categories.extend(portage.util.grabfile(
726 os.path.join(path, 'profiles', 'categories')))
727 repoman_settings.categories = frozenset(
728 portage.util.stack_lists([categories], incremental=1))
729 categories = repoman_settings.categories
731 portdb.settings = repoman_settings
732 root_config = RootConfig(repoman_settings, trees[root], None)
733 # We really only need to cache the metadata that's necessary for visibility
734 # filtering. Anything else can be discarded to reduce memory consumption.
735 portdb._aux_cache_keys.clear()
736 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
738 reposplit = myreporoot.split(os.path.sep)
739 repolevel = len(reposplit)
741 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
742 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
743 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
744 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
745 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
746 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
747 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
749 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
751 # Make startdir relative to the canonical repodir, so that we can pass
752 # it to digestgen and it won't have to be canonicalized again.
756 startdir = normalize_path(mydir)
757 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
760 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.")
762 def repoman_getstatusoutput(cmd):
764 Implements an interface similar to getstatusoutput(), but with
765 customized unicode handling (see bug #310789) and without the shell.
767 args = portage.util.shlex_split(cmd)
769 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
770 not os.path.isabs(args[0]):
771 # Python 3.1 _execvp throws TypeError for non-absolute executable
772 # path passed as bytes (see http://bugs.python.org/issue8513).
773 fullname = find_binary(args[0])
775 raise portage.exception.CommandNotFound(args[0])
778 encoding = _encodings['fs']
779 args = [_unicode_encode(x,
780 encoding=encoding, errors='strict') for x in args]
781 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
782 stderr=subprocess.STDOUT)
783 output = portage._unicode_decode(proc.communicate()[0],
784 encoding=encoding, errors='strict')
785 if output and output[-1] == "\n":
786 # getstatusoutput strips one newline
788 return (proc.wait(), output)
790 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
792 Implements an interface similar to os.popen(), but with customized
793 unicode handling (see bug #310789) and without the shell.
796 __slots__ = ('_proc', '_stdout')
798 def __init__(self, cmd):
799 args = portage.util.shlex_split(cmd)
801 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
802 not os.path.isabs(args[0]):
803 # Python 3.1 _execvp throws TypeError for non-absolute executable
804 # path passed as bytes (see http://bugs.python.org/issue8513).
805 fullname = find_binary(args[0])
807 raise portage.exception.CommandNotFound(args[0])
810 encoding = _encodings['fs']
811 args = [_unicode_encode(x,
812 encoding=encoding, errors='strict') for x in args]
813 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
814 object.__setattr__(self, '_proc', proc)
815 object.__setattr__(self, '_stdout',
816 codecs.getreader(encoding)(proc.stdout, 'strict'))
818 def _get_target(self):
819 return object.__getattribute__(self, '_stdout')
821 __enter__ = _get_target
823 def __exit__(self, exc_type, exc_value, traceback):
824 proc = object.__getattribute__(self, '_proc')
828 class ProfileDesc(object):
829 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
830 def __init__(self, arch, status, sub_path, tree_path):
834 sub_path = normalize_path(sub_path.lstrip(os.sep))
835 self.sub_path = sub_path
836 self.tree_path = tree_path
838 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
840 self.abs_path = tree_path
845 return 'empty profile'
848 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
850 # get lists of valid keywords, licenses, and use
854 global_pmasklines = []
856 for path in portdb.porttrees:
858 liclist.update(os.listdir(os.path.join(path, "licenses")))
861 kwlist.update(portage.grabfile(os.path.join(path,
862 "profiles", "arch.list")))
864 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
870 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
872 expand_list = os.listdir(expand_desc_dir)
876 for fn in expand_list:
877 if not fn[-5:] == '.desc':
879 use_prefix = fn[:-5].lower() + '_'
880 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
883 uselist.add(use_prefix + x[0])
885 global_pmasklines.append(portage.util.grabfile_package(
886 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
888 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
890 desc_file = io.open(_unicode_encode(desc_path,
891 encoding=_encodings['fs'], errors='strict'),
892 mode='r', encoding=_encodings['repo.content'], errors='replace')
893 except EnvironmentError:
896 for i, x in enumerate(desc_file):
903 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
904 desc_path + " line %d" % (i + 1, ))
905 elif arch[0] not in kwlist:
906 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
907 desc_path + " line %d" % (i + 1, ))
908 elif arch[2] not in valid_profile_types:
909 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
910 desc_path + " line %d" % (i + 1, ))
911 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
912 if not os.path.isdir(profile_desc.abs_path):
914 "Invalid %s profile (%s) for arch %s in %s line %d",
915 arch[2], arch[1], arch[0], desc_path, i + 1)
918 os.path.join(profile_desc.abs_path, 'deprecated')):
920 profile_list.append(profile_desc)
923 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
924 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
926 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
927 global_pmaskdict = {}
928 for x in global_pmasklines:
929 global_pmaskdict.setdefault(x.cp, []).append(x)
930 del global_pmasklines
932 def has_global_mask(pkg):
933 mask_atoms = global_pmaskdict.get(pkg.cp)
937 if portage.dep.match_from_list(x, pkg_list):
941 # Ensure that profile sub_path attributes are unique. Process in reverse order
942 # so that profiles with duplicate sub_path from overlays will override
943 # profiles with the same sub_path from parent repos.
945 profile_list.reverse()
946 profile_sub_paths = set()
947 for prof in profile_list:
948 if prof.sub_path in profile_sub_paths:
950 profile_sub_paths.add(prof.sub_path)
951 profiles.setdefault(prof.arch, []).append(prof)
953 # Use an empty profile for checking dependencies of
954 # packages that have empty KEYWORDS.
955 prof = ProfileDesc('**', 'stable', '', '')
956 profiles.setdefault(prof.arch, []).append(prof)
958 for x in repoman_settings.archlist():
961 if x not in profiles:
962 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
963 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
964 print(red("up with the " + x + " team."))
967 liclist_deprecated = set()
968 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
969 liclist_deprecated.update(
970 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
973 logging.fatal("Couldn't find licenses?")
977 logging.fatal("Couldn't read KEYWORDS from arch.list")
981 logging.fatal("Couldn't find use.desc?")
986 # we are inside a category directory
987 catdir = reposplit[-1]
988 if catdir not in categories:
990 mydirlist = os.listdir(startdir)
992 if x == "CVS" or x.startswith("."):
994 if os.path.isdir(startdir + "/" + x):
995 scanlist.append(catdir + "/" + x)
996 repo_subdir = catdir + os.sep
999 if not os.path.isdir(startdir + "/" + x):
1001 for y in os.listdir(startdir + "/" + x):
1002 if y == "CVS" or y.startswith("."):
1004 if os.path.isdir(startdir + "/" + x + "/" + y):
1005 scanlist.append(x + "/" + y)
1007 elif repolevel == 3:
1008 catdir = reposplit[-2]
1009 if catdir not in categories:
1011 scanlist.append(catdir + "/" + reposplit[-1])
1012 repo_subdir = scanlist[-1] + os.sep
1014 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
1015 ' from the current working directory'
1016 logging.critical(msg)
1019 repo_subdir_len = len(repo_subdir)
1022 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
1024 def vcs_files_to_cps(vcs_file_iter):
1026 Iterate over the given modified file paths returned from the vcs,
1027 and return a frozenset containing category/pn strings for each
1034 if reposplit[-2] in categories and \
1035 next(vcs_file_iter, None) is not None:
1036 modified_cps.append("/".join(reposplit[-2:]))
1038 elif repolevel == 2:
1039 category = reposplit[-1]
1040 if category in categories:
1041 for filename in vcs_file_iter:
1042 f_split = filename.split(os.sep)
1044 if len(f_split) > 2:
1045 modified_cps.append(category + "/" + f_split[1])
1049 for filename in vcs_file_iter:
1050 f_split = filename.split(os.sep)
1051 # ['.', category, pn, ...]
1052 if len(f_split) > 3 and f_split[1] in categories:
1053 modified_cps.append("/".join(f_split[1:3]))
1055 return frozenset(modified_cps)
1057 def git_supports_gpg_sign():
1058 status, cmd_output = \
1059 repoman_getstatusoutput("git --version")
1060 cmd_output = cmd_output.split()
1062 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1063 if version is not None:
1064 version = [int(x) for x in version.groups()]
1065 if version[0] > 1 or \
1066 (version[0] == 1 and version[1] > 7) or \
1067 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1071 def dev_keywords(profiles):
1073 Create a set of KEYWORDS values that exist in 'dev'
1074 profiles. These are used
1075 to trigger a message notifying the user when they might
1076 want to add the --include-dev option.
1079 for arch, arch_profiles in profiles.items():
1080 for prof in arch_profiles:
1081 arch_set = type_arch_map.get(prof.status)
1082 if arch_set is None:
1084 type_arch_map[prof.status] = arch_set
1087 dev_keywords = type_arch_map.get('dev', set())
1088 dev_keywords.update(['~' + arch for arch in dev_keywords])
1089 return frozenset(dev_keywords)
1091 dev_keywords = dev_keywords(profiles)
1100 xmllint_capable = False
1101 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1103 def fetch_metadata_dtd():
1105 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1106 metadata_dtd_ctime_interval.
1108 @return: True if successful, otherwise False
1112 metadata_dtd_st = None
1113 current_time = int(time.time())
1115 metadata_dtd_st = os.stat(metadata_dtd)
1116 except EnvironmentError as e:
1117 if e.errno not in (errno.ENOENT, errno.ESTALE):
1121 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1122 if abs(current_time - metadata_dtd_st.st_ctime) \
1123 < metadata_dtd_ctime_interval:
1128 print(green("***") + " the local copy of metadata.dtd " + \
1129 "needs to be refetched, doing that now")
1131 parsed_url = urlparse(metadata_dtd_uri)
1132 setting = 'FETCHCOMMAND_' + parsed_url.scheme.upper()
1133 fcmd = repoman_settings.get(setting)
1135 fcmd = repoman_settings.get('FETCHCOMMAND')
1137 logging.error("FETCHCOMMAND is unset")
1140 destdir = repoman_settings["DISTDIR"]
1141 fd, metadata_dtd_tmp = tempfile.mkstemp(
1142 prefix='metadata.dtd.', dir=destdir)
1146 if not portage.getbinpkg.file_get(metadata_dtd_uri,
1148 filename=os.path.basename(metadata_dtd_tmp)):
1149 logging.error("failed to fetch metadata.dtd from '%s'" %
1154 portage.util.apply_secpass_permissions(metadata_dtd_tmp,
1155 gid=portage.data.portage_gid, mode=0o664, mask=0o2)
1156 except portage.exception.PortageException:
1159 os.rename(metadata_dtd_tmp, metadata_dtd)
1162 os.unlink(metadata_dtd_tmp)
1168 if options.mode == "manifest":
1170 elif not find_binary('xmllint'):
1171 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1172 if options.xml_parse or repolevel == 3:
1173 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1176 if not fetch_metadata_dtd():
1178 # this can be problematic if xmllint changes their output
1179 xmllint_capable = True
1181 if options.mode == 'commit' and vcs:
1182 utilities.detect_vcs_conflicts(options, vcs)
1184 if options.mode == "manifest":
1186 elif options.pretend:
1187 print(green("\nRepoMan does a once-over of the neighborhood..."))
1189 print(green("\nRepoMan scours the neighborhood..."))
1192 modified_ebuilds = set()
1193 modified_changelogs = set()
1199 mycvstree = cvstree.getentries("./", recursive=1)
1200 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1201 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1202 if options.if_modified == "y":
1203 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1206 with repoman_popen("svn status") as f:
1207 svnstatus = f.readlines()
1208 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1209 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1210 if options.if_modified == "y":
1211 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1214 with repoman_popen("git diff-index --name-only "
1215 "--relative --diff-filter=M HEAD") as f:
1216 mychanged = f.readlines()
1217 mychanged = ["./" + elem[:-1] for elem in mychanged]
1219 with repoman_popen("git diff-index --name-only "
1220 "--relative --diff-filter=A HEAD") as f:
1221 mynew = f.readlines()
1222 mynew = ["./" + elem[:-1] for elem in mynew]
1223 if options.if_modified == "y":
1224 with repoman_popen("git diff-index --name-only "
1225 "--relative --diff-filter=D HEAD") as f:
1226 myremoved = f.readlines()
1227 myremoved = ["./" + elem[:-1] for elem in myremoved]
1230 with repoman_popen("bzr status -S .") as f:
1231 bzrstatus = f.readlines()
1232 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1233 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1234 if options.if_modified == "y":
1235 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")]
1238 with repoman_popen("hg status --no-status --modified .") as f:
1239 mychanged = f.readlines()
1240 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1241 with repoman_popen("hg status --no-status --added .") as f:
1242 mynew = f.readlines()
1243 mynew = ["./" + elem.rstrip() for elem in mynew]
1244 if options.if_modified == "y":
1245 with repoman_popen("hg status --no-status --removed .") as f:
1246 myremoved = f.readlines()
1247 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1250 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1251 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1252 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1253 if os.path.basename(x) == "ChangeLog")
1255 def vcs_new_changed(relative_path):
1256 for x in chain(mychanged, mynew):
1257 if x == relative_path:
1261 have_pmasked = False
1262 have_dev_keywords = False
1265 # NOTE: match-all caches are not shared due to potential
1266 # differences between profiles in _get_implicit_iuse.
1268 arch_xmatch_caches = {}
1269 shared_xmatch_caches = {"cp-list":{}}
1271 include_arches = None
1272 if options.include_arches:
1273 include_arches = set()
1274 include_arches.update(*[x.split() for x in options.include_arches])
1276 # Disable the "ebuild.notadded" check when not in commit mode and
1277 # running `svn status` in every package dir will be too expensive.
1279 check_ebuild_notadded = not \
1280 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1282 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1283 thirdpartymirrors = {}
1284 for k, v in repoman_settings.thirdpartymirrors().items():
1286 if not v.endswith("/"):
1288 thirdpartymirrors[v] = k
1290 class _XMLParser(xml.etree.ElementTree.XMLParser):
1292 def __init__(self, data, **kwargs):
1293 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1294 self._portage_data = data
1295 if hasattr(self, 'parser'):
1296 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1297 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1298 self._base_StartDoctypeDeclHandler = \
1299 self.parser.StartDoctypeDeclHandler
1300 self.parser.StartDoctypeDeclHandler = \
1301 self._portage_StartDoctypeDeclHandler
1303 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1304 if self._base_XmlDeclHandler is not None:
1305 self._base_XmlDeclHandler(version, encoding, standalone)
1306 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1308 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1309 has_internal_subset):
1310 if self._base_StartDoctypeDeclHandler is not None:
1311 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1312 has_internal_subset)
1313 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1315 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1317 Implements doctype() as required to avoid deprecation warnings with
1320 def doctype(self, name, pubid, system):
1324 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1325 except (EnvironmentError, ParseError, PermissionDenied) as e:
1327 except FileNotFound:
1328 # TODO: Download as we do for metadata.dtd, but add a way to
1329 # disable for non-gentoo repoman users who may not have herds.
1332 effective_scanlist = scanlist
1333 if options.if_modified == "y":
1334 effective_scanlist = sorted(vcs_files_to_cps(
1335 chain(mychanged, mynew, myremoved)))
1337 for x in effective_scanlist:
1338 #ebuilds and digests added to cvs respectively.
1339 logging.info("checking package %s" % x)
1340 # save memory by discarding xmatch caches from previous package(s)
1341 arch_xmatch_caches.clear()
1343 catdir, pkgdir = x.split("/")
1344 checkdir = repodir + "/" + x
1345 checkdir_relative = ""
1347 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1349 checkdir_relative = os.path.join(catdir, checkdir_relative)
1350 checkdir_relative = os.path.join(".", checkdir_relative)
1351 generated_manifest = False
1353 if options.mode == "manifest" or \
1354 (options.mode != 'manifest-check' and options.digest == 'y') or \
1355 options.mode in ('commit', 'fix') and not options.pretend:
1356 auto_assumed = set()
1357 fetchlist_dict = portage.FetchlistDict(checkdir,
1358 repoman_settings, portdb)
1359 if options.mode == 'manifest' and options.force:
1360 portage._doebuild_manifest_exempt_depend += 1
1362 distdir = repoman_settings['DISTDIR']
1363 mf = repoman_settings.repositories.get_repo_for_location(
1364 os.path.dirname(os.path.dirname(checkdir)))
1365 mf = mf.load_manifest(checkdir, distdir,
1366 fetchlist_dict=fetchlist_dict)
1367 mf.create(requiredDistfiles=None,
1368 assumeDistHashesAlways=True)
1369 for distfiles in fetchlist_dict.values():
1370 for distfile in distfiles:
1371 if os.path.isfile(os.path.join(distdir, distfile)):
1372 mf.fhashdict['DIST'].pop(distfile, None)
1374 auto_assumed.add(distfile)
1377 portage._doebuild_manifest_exempt_depend -= 1
1379 repoman_settings["O"] = checkdir
1381 generated_manifest = digestgen(
1382 mysettings=repoman_settings, myportdb=portdb)
1383 except portage.exception.PermissionDenied as e:
1384 generated_manifest = False
1385 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1386 level=logging.ERROR, noiselevel=-1)
1388 if not generated_manifest:
1389 print("Unable to generate manifest.")
1392 if options.mode == "manifest":
1393 if not dofail and options.force and auto_assumed and \
1394 'assume-digests' in repoman_settings.features:
1395 # Show which digests were assumed despite the --force option
1396 # being given. This output will already have been shown by
1397 # digestgen() if assume-digests is not enabled, so only show
1398 # it here if assume-digests is enabled.
1399 pkgs = list(fetchlist_dict)
1401 portage.writemsg_stdout(" digest.assumed" + \
1402 portage.output.colorize("WARN",
1403 str(len(auto_assumed)).rjust(18)) + "\n")
1405 fetchmap = fetchlist_dict[cpv]
1406 pf = portage.catsplit(cpv)[1]
1407 for distfile in sorted(fetchmap):
1408 if distfile in auto_assumed:
1409 portage.writemsg_stdout(
1410 " %s::%s\n" % (pf, distfile))
1415 if not generated_manifest:
1416 repoman_settings['O'] = checkdir
1417 repoman_settings['PORTAGE_QUIET'] = '1'
1418 if not portage.digestcheck([], repoman_settings, strict=1):
1419 stats["manifest.bad"] += 1
1420 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1421 repoman_settings.pop('PORTAGE_QUIET', None)
1423 if options.mode == 'manifest-check':
1426 checkdirlist = os.listdir(checkdir)
1430 for y in checkdirlist:
1431 if (y in no_exec or y.endswith(".ebuild")) and \
1432 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1433 stats["file.executable"] += 1
1434 fails["file.executable"].append(os.path.join(checkdir, y))
1435 if y.endswith(".ebuild"):
1437 ebuildlist.append(pf)
1438 cpv = "%s/%s" % (catdir, pf)
1440 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1443 stats["ebuild.syntax"] += 1
1444 fails["ebuild.syntax"].append(os.path.join(x, y))
1448 stats["ebuild.output"] += 1
1449 fails["ebuild.output"].append(os.path.join(x, y))
1451 if not portage.eapi_is_supported(myaux["EAPI"]):
1453 stats["EAPI.unsupported"] += 1
1454 fails["EAPI.unsupported"].append(os.path.join(x, y))
1456 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1457 root_config=root_config, type_name="ebuild")
1461 if len(pkgs) != len(ebuildlist):
1462 # If we can't access all the metadata then it's totally unsafe to
1463 # commit since there's no way to generate a correct Manifest.
1464 # Do not try to do any more QA checks on this package since missing
1465 # metadata leads to false positives for several checks, and false
1466 # positives confuse users.
1470 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1471 ebuildlist = sorted(pkgs.values())
1472 ebuildlist = [pkg.pf for pkg in ebuildlist]
1474 for y in checkdirlist:
1475 index = repo_config.find_invalid_path_char(y)
1477 y_relative = os.path.join(checkdir_relative, y)
1478 if vcs is not None and not vcs_new_changed(y_relative):
1479 # If the file isn't in the VCS new or changed set, then
1480 # assume that it's an irrelevant temporary file (Manifest
1481 # entries are not generated for file names containing
1482 # prohibited characters). See bug #406877.
1485 stats["file.name"] += 1
1486 fails["file.name"].append("%s/%s: char '%s'" % \
1487 (checkdir, y, y[index]))
1489 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1494 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1495 encoding=_encodings['fs'], errors='strict'),
1496 mode='r', encoding=_encodings['repo.content'])
1499 except UnicodeDecodeError as ue:
1500 stats["file.UTF8"] += 1
1501 s = ue.object[:ue.start]
1505 s = s[s.rfind("\n") + 1:]
1506 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1511 if vcs in ("git", "hg") and check_ebuild_notadded:
1513 myf = repoman_popen("git ls-files --others %s" % \
1514 (portage._shell_quote(checkdir_relative),))
1516 myf = repoman_popen("hg status --no-status --unknown %s" % \
1517 (portage._shell_quote(checkdir_relative),))
1519 if l[:-1][-7:] == ".ebuild":
1520 stats["ebuild.notadded"] += 1
1521 fails["ebuild.notadded"].append(
1522 os.path.join(x, os.path.basename(l[:-1])))
1525 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1528 myf = open(checkdir + "/CVS/Entries", "r")
1530 myf = repoman_popen("svn status --depth=files --verbose " +
1531 portage._shell_quote(checkdir))
1533 myf = repoman_popen("bzr ls -v --kind=file " +
1534 portage._shell_quote(checkdir))
1535 myl = myf.readlines()
1541 splitl = l[1:].split("/")
1544 if splitl[0][-7:] == ".ebuild":
1545 eadded.append(splitl[0][:-7])
1550 # tree conflict, new in subversion 1.6
1553 if l[-7:] == ".ebuild":
1554 eadded.append(os.path.basename(l[:-7]))
1559 if l[-7:] == ".ebuild":
1560 eadded.append(os.path.basename(l[:-7]))
1562 myf = repoman_popen("svn status " +
1563 portage._shell_quote(checkdir))
1564 myl = myf.readlines()
1568 l = l.rstrip().split(' ')[-1]
1569 if l[-7:] == ".ebuild":
1570 eadded.append(os.path.basename(l[:-7]))
1573 stats["CVS/Entries.IO_error"] += 1
1574 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1579 mf = repoman_settings.repositories.get_repo_for_location(
1580 os.path.dirname(os.path.dirname(checkdir)))
1581 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1582 mydigests = mf.getTypeDigests("DIST")
1584 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1586 src_uri_error = False
1587 for mykey in fetchlist_dict:
1589 myfiles_all.extend(fetchlist_dict[mykey])
1590 except portage.exception.InvalidDependString as e:
1591 src_uri_error = True
1593 portdb.aux_get(mykey, ["SRC_URI"])
1595 # This will be reported as an "ebuild.syntax" error.
1598 stats["SRC_URI.syntax"] += 1
1599 fails["SRC_URI.syntax"].append(
1600 "%s.ebuild SRC_URI: %s" % (mykey, e))
1602 if not src_uri_error:
1603 # This test can produce false positives if SRC_URI could not
1604 # be parsed for one or more ebuilds. There's no point in
1605 # producing a false error here since the root cause will
1606 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1607 # or "ebuild.sytax".
1608 myfiles_all = set(myfiles_all)
1609 for entry in mydigests:
1610 if entry not in myfiles_all:
1611 stats["digest.unused"] += 1
1612 fails["digest.unused"].append(checkdir + "::" + entry)
1613 for entry in myfiles_all:
1614 if entry not in mydigests:
1615 stats["digest.missing"] += 1
1616 fails["digest.missing"].append(checkdir + "::" + entry)
1619 if os.path.exists(checkdir + "/files"):
1620 filesdirlist = os.listdir(checkdir + "/files")
1622 # recurse through files directory
1623 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1625 y = filesdirlist.pop(0)
1626 relative_path = os.path.join(x, "files", y)
1627 full_path = os.path.join(repodir, relative_path)
1629 mystat = os.stat(full_path)
1630 except OSError as oe:
1632 # don't worry about it. it likely was removed via fix above.
1636 if S_ISDIR(mystat.st_mode):
1637 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1638 if y == "CVS" or y == ".svn":
1640 for z in os.listdir(checkdir + "/files/" + y):
1641 if z == "CVS" or z == ".svn":
1643 filesdirlist.append(y + "/" + z)
1644 # Current policy is no files over 20 KiB, these are the checks. File size between
1645 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1646 elif mystat.st_size > 61440:
1647 stats["file.size.fatal"] += 1
1648 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1649 elif mystat.st_size > 20480:
1650 stats["file.size"] += 1
1651 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1653 index = repo_config.find_invalid_path_char(y)
1655 y_relative = os.path.join(checkdir_relative, "files", y)
1656 if vcs is not None and not vcs_new_changed(y_relative):
1657 # If the file isn't in the VCS new or changed set, then
1658 # assume that it's an irrelevant temporary file (Manifest
1659 # entries are not generated for file names containing
1660 # prohibited characters). See bug #406877.
1663 stats["file.name"] += 1
1664 fails["file.name"].append("%s/files/%s: char '%s'" % \
1665 (checkdir, y, y[index]))
1668 if check_changelog and "ChangeLog" not in checkdirlist:
1669 stats["changelog.missing"] += 1
1670 fails["changelog.missing"].append(x + "/ChangeLog")
1673 # metadata.xml file check
1674 if "metadata.xml" not in checkdirlist:
1675 stats["metadata.missing"] += 1
1676 fails["metadata.missing"].append(x + "/metadata.xml")
1677 # metadata.xml parse check
1679 metadata_bad = False
1681 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1683 # read metadata.xml into memory
1685 _metadata_xml = xml.etree.ElementTree.parse(
1686 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1687 encoding=_encodings['fs'], errors='strict'),
1689 except (ExpatError, SyntaxError, EnvironmentError) as e:
1691 stats["metadata.bad"] += 1
1692 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1695 if not hasattr(xml_parser, 'parser') or \
1696 sys.hexversion < 0x2070000 or \
1697 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1698 # doctype is not parsed with python 2.6 or 3.1
1701 if "XML_DECLARATION" not in xml_info:
1702 stats["metadata.bad"] += 1
1703 fails["metadata.bad"].append("%s/metadata.xml: "
1704 "xml declaration is missing on first line, "
1705 "should be '%s'" % (x, metadata_xml_declaration))
1707 xml_version, xml_encoding, xml_standalone = \
1708 xml_info["XML_DECLARATION"]
1709 if xml_encoding is None or \
1710 xml_encoding.upper() != metadata_xml_encoding:
1711 stats["metadata.bad"] += 1
1712 if xml_encoding is None:
1713 encoding_problem = "but it is undefined"
1715 encoding_problem = "not '%s'" % xml_encoding
1716 fails["metadata.bad"].append("%s/metadata.xml: "
1717 "xml declaration encoding should be '%s', %s" %
1718 (x, metadata_xml_encoding, encoding_problem))
1720 if "DOCTYPE" not in xml_info:
1722 stats["metadata.bad"] += 1
1723 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1724 "DOCTYPE is missing"))
1726 doctype_name, doctype_system, doctype_pubid = \
1728 if doctype_system != metadata_dtd_uri:
1729 stats["metadata.bad"] += 1
1730 if doctype_system is None:
1731 system_problem = "but it is undefined"
1733 system_problem = "not '%s'" % doctype_system
1734 fails["metadata.bad"].append("%s/metadata.xml: "
1735 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1736 (x, metadata_dtd_uri, system_problem))
1738 if doctype_name != metadata_doctype_name:
1739 stats["metadata.bad"] += 1
1740 fails["metadata.bad"].append("%s/metadata.xml: "
1741 "DOCTYPE: name should be '%s', not '%s'" %
1742 (x, metadata_doctype_name, doctype_name))
1744 # load USE flags from metadata.xml
1746 musedict = utilities.parse_metadata_use(_metadata_xml)
1747 except portage.exception.ParseError as e:
1749 stats["metadata.bad"] += 1
1750 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1752 for atom in chain(*musedict.values()):
1757 except InvalidAtom as e:
1758 stats["metadata.bad"] += 1
1759 fails["metadata.bad"].append(
1760 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1763 stats["metadata.bad"] += 1
1764 fails["metadata.bad"].append(
1765 ("%s/metadata.xml: Atom contains "
1766 "unexpected cat/pn: %s") % (x, atom))
1768 # Run other metadata.xml checkers
1770 utilities.check_metadata(_metadata_xml, herd_base)
1771 except (utilities.UnknownHerdsError, ) as e:
1773 stats["metadata.bad"] += 1
1774 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1777 #Only carry out if in package directory or check forced
1778 if xmllint_capable and not metadata_bad:
1779 # xmlint can produce garbage output even on success, so only dump
1780 # the ouput when it fails.
1781 st, out = repoman_getstatusoutput(
1782 "xmllint --nonet --noout --dtdvalid %s %s" % \
1783 (portage._shell_quote(metadata_dtd),
1784 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1786 print(red("!!!") + " metadata.xml is invalid:")
1787 for z in out.splitlines():
1788 print(red("!!! ") + z)
1789 stats["metadata.bad"] += 1
1790 fails["metadata.bad"].append(x + "/metadata.xml")
1793 muselist = frozenset(musedict)
1795 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1796 changelog_modified = changelog_path in modified_changelogs
1798 # detect unused local USE-descriptions
1799 used_useflags = set()
1801 for y in ebuildlist:
1802 relative_path = os.path.join(x, y + ".ebuild")
1803 full_path = os.path.join(repodir, relative_path)
1804 ebuild_path = y + ".ebuild"
1806 ebuild_path = os.path.join(pkgdir, ebuild_path)
1808 ebuild_path = os.path.join(catdir, ebuild_path)
1809 ebuild_path = os.path.join(".", ebuild_path)
1810 if check_changelog and not changelog_modified \
1811 and ebuild_path in new_ebuilds:
1812 stats['changelog.ebuildadded'] += 1
1813 fails['changelog.ebuildadded'].append(relative_path)
1815 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1816 #ebuild not added to vcs
1817 stats["ebuild.notadded"] += 1
1818 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1819 myesplit = portage.pkgsplit(y)
1820 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1821 or pv_toolong_re.search(myesplit[1]) \
1822 or pv_toolong_re.search(myesplit[2]):
1823 stats["ebuild.invalidname"] += 1
1824 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1826 elif myesplit[0] != pkgdir:
1827 print(pkgdir, myesplit[0])
1828 stats["ebuild.namenomatch"] += 1
1829 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1836 for k, msgs in pkg.invalid.items():
1839 fails[k].append("%s: %s" % (relative_path, msg))
1842 myaux = pkg._metadata
1843 eapi = myaux["EAPI"]
1844 inherited = pkg.inherited
1845 live_ebuild = live_eclasses.intersection(inherited)
1847 if repo_config.eapi_is_banned(eapi):
1848 stats["repo.eapi.banned"] += 1
1849 fails["repo.eapi.banned"].append(
1850 "%s: %s" % (relative_path, eapi))
1852 elif repo_config.eapi_is_deprecated(eapi):
1853 stats["repo.eapi.deprecated"] += 1
1854 fails["repo.eapi.deprecated"].append(
1855 "%s: %s" % (relative_path, eapi))
1857 for k, v in myaux.items():
1858 if not isinstance(v, basestring):
1860 m = non_ascii_re.search(v)
1862 stats["variable.invalidchar"] += 1
1863 fails["variable.invalidchar"].append(
1864 ("%s: %s variable contains non-ASCII " + \
1865 "character at position %s") % \
1866 (relative_path, k, m.start() + 1))
1868 if not src_uri_error:
1869 # Check that URIs don't reference a server from thirdpartymirrors.
1870 for uri in portage.dep.use_reduce( \
1871 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1872 contains_mirror = False
1873 for mirror, mirror_alias in thirdpartymirrors.items():
1874 if uri.startswith(mirror):
1875 contains_mirror = True
1877 if not contains_mirror:
1880 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1881 stats["SRC_URI.mirror"] += 1
1882 fails["SRC_URI.mirror"].append(
1883 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1884 (relative_path, mirror, new_uri))
1886 if myaux.get("PROVIDE"):
1887 stats["virtual.oldstyle"] += 1
1888 fails["virtual.oldstyle"].append(relative_path)
1890 for pos, missing_var in enumerate(missingvars):
1891 if not myaux.get(missing_var):
1892 if catdir == "virtual" and \
1893 missing_var in ("HOMEPAGE", "LICENSE"):
1895 if live_ebuild and missing_var == "KEYWORDS":
1897 myqakey = missingvars[pos] + ".missing"
1899 fails[myqakey].append(x + "/" + y + ".ebuild")
1901 if catdir == "virtual":
1902 for var in ("HOMEPAGE", "LICENSE"):
1904 myqakey = var + ".virtual"
1906 fails[myqakey].append(relative_path)
1908 # 14 is the length of DESCRIPTION=""
1909 if len(myaux['DESCRIPTION']) > max_desc_len:
1910 stats['DESCRIPTION.toolong'] += 1
1911 fails['DESCRIPTION.toolong'].append(
1912 "%s: DESCRIPTION is %d characters (max %d)" % \
1913 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1915 keywords = myaux["KEYWORDS"].split()
1916 stable_keywords = []
1917 for keyword in keywords:
1918 if not keyword.startswith("~") and \
1919 not keyword.startswith("-"):
1920 stable_keywords.append(keyword)
1922 if ebuild_path in new_ebuilds and catdir != "virtual":
1923 stable_keywords.sort()
1924 stats["KEYWORDS.stable"] += 1
1925 fails["KEYWORDS.stable"].append(
1926 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1927 " ".join(stable_keywords))
1929 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1930 if not kw.startswith("-"))
1932 previous_keywords = slot_keywords.get(pkg.slot)
1933 if previous_keywords is None:
1934 slot_keywords[pkg.slot] = set()
1935 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1936 dropped_keywords = previous_keywords.difference(ebuild_archs)
1937 if dropped_keywords:
1938 stats["KEYWORDS.dropped"] += 1
1939 fails["KEYWORDS.dropped"].append(
1940 relative_path + ": %s" % \
1941 " ".join(sorted(dropped_keywords)))
1943 slot_keywords[pkg.slot].update(ebuild_archs)
1945 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1946 if "-*" in keywords:
1954 stats["KEYWORDS.stupid"] += 1
1955 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1958 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1959 not be allowed to be marked stable
1961 if live_ebuild and repo_config.name == "gentoo":
1962 bad_stable_keywords = []
1963 for keyword in keywords:
1964 if not keyword.startswith("~") and \
1965 not keyword.startswith("-"):
1966 bad_stable_keywords.append(keyword)
1968 if bad_stable_keywords:
1969 stats["LIVEVCS.stable"] += 1
1970 fails["LIVEVCS.stable"].append(
1971 x + "/" + y + ".ebuild with stable keywords:%s " % \
1972 bad_stable_keywords)
1973 del bad_stable_keywords
1975 if keywords and not has_global_mask(pkg):
1976 stats["LIVEVCS.unmasked"] += 1
1977 fails["LIVEVCS.unmasked"].append(relative_path)
1979 if options.ignore_arches:
1980 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1981 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1984 for keyword in keywords:
1985 if keyword[0] == "-":
1987 elif keyword[0] == "~":
1990 for expanded_arch in profiles:
1991 if expanded_arch == "**":
1993 arches.add((keyword, expanded_arch,
1994 (expanded_arch, "~" + expanded_arch)))
1996 arches.add((keyword, arch, (arch, keyword)))
1999 for expanded_arch in profiles:
2000 if expanded_arch == "**":
2002 arches.add((keyword, expanded_arch,
2005 arches.add((keyword, keyword, (keyword,)))
2007 # Use an empty profile for checking dependencies of
2008 # packages that have empty KEYWORDS.
2009 arches.add(('**', '**', ('**',)))
2011 unknown_pkgs = set()
2012 baddepsyntax = False
2013 badlicsyntax = False
2014 badprovsyntax = False
2015 catpkg = catdir + "/" + y
2017 inherited_java_eclass = "java-pkg-2" in inherited or \
2018 "java-pkg-opt-2" in inherited
2019 inherited_wxwidgets_eclass = "wxwidgets" in inherited
2020 operator_tokens = set(["||", "(", ")"])
2021 type_list, badsyntax = [], []
2022 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
2023 mydepstr = myaux[mytype]
2025 buildtime = mytype in Package._buildtime_keys
2026 runtime = mytype in Package._runtime_keys
2028 if mytype.endswith("DEPEND"):
2029 token_class = portage.dep.Atom
2032 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
2033 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
2034 except portage.exception.InvalidDependString as e:
2036 badsyntax.append(str(e))
2038 if atoms and mytype.endswith("DEPEND"):
2040 "test?" in mydepstr.split():
2041 stats[mytype + '.suspect'] += 1
2042 fails[mytype + '.suspect'].append(relative_path + \
2043 ": 'test?' USE conditional in %s" % mytype)
2049 # Skip dependency.unknown for blockers, so that we
2050 # don't encourage people to remove necessary blockers,
2051 # as discussed in bug #382407.
2052 if atom.blocker is None and \
2053 not portdb.xmatch("match-all", atom) and \
2054 not atom.cp.startswith("virtual/"):
2055 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2057 is_blocker = atom.blocker
2059 if catdir != "virtual":
2060 if not is_blocker and \
2061 atom.cp in suspect_virtual:
2062 stats['virtual.suspect'] += 1
2063 fails['virtual.suspect'].append(
2065 ": %s: consider using '%s' instead of '%s'" %
2066 (mytype, suspect_virtual[atom.cp], atom))
2069 not is_blocker and \
2070 not inherited_java_eclass and \
2071 atom.cp == "virtual/jdk":
2072 stats['java.eclassesnotused'] += 1
2073 fails['java.eclassesnotused'].append(relative_path)
2074 elif buildtime and \
2075 not is_blocker and \
2076 not inherited_wxwidgets_eclass and \
2077 atom.cp == "x11-libs/wxGTK":
2078 stats['wxwidgets.eclassnotused'] += 1
2079 fails['wxwidgets.eclassnotused'].append(
2080 (relative_path + ": %ss on x11-libs/wxGTK"
2081 " without inheriting wxwidgets.eclass") % mytype)
2083 if not is_blocker and \
2084 atom.cp in suspect_rdepend:
2085 stats[mytype + '.suspect'] += 1
2086 fails[mytype + '.suspect'].append(
2087 relative_path + ": '%s'" % atom)
2089 if atom.operator == "~" and \
2090 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2091 qacat = 'dependency.badtilde'
2093 fails[qacat].append(
2094 (relative_path + ": %s uses the ~ operator"
2095 " with a non-zero revision:" + \
2096 " '%s'") % (mytype, atom))
2098 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2100 for m, b in zip(type_list, badsyntax):
2101 if m.endswith("DEPEND"):
2102 qacat = "dependency.syntax"
2104 qacat = m + ".syntax"
2106 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2108 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2109 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2110 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2111 badlicsyntax = badlicsyntax > 0
2112 badprovsyntax = badprovsyntax > 0
2114 # uselist checks - global
2117 for myflag in myaux["IUSE"].split():
2118 flag_name = myflag.lstrip("+-")
2119 used_useflags.add(flag_name)
2120 if myflag != flag_name:
2121 default_use.append(myflag)
2122 if flag_name not in uselist:
2123 myuse.append(flag_name)
2125 # uselist checks - metadata
2126 for mypos in range(len(myuse)-1, -1, -1):
2127 if myuse[mypos] and (myuse[mypos] in muselist):
2130 if default_use and not eapi_has_iuse_defaults(eapi):
2131 for myflag in default_use:
2132 stats['EAPI.incompatible'] += 1
2133 fails['EAPI.incompatible'].append(
2134 (relative_path + ": IUSE defaults" + \
2135 " not supported with EAPI='%s':" + \
2136 " '%s'") % (eapi, myflag))
2138 for mypos in range(len(myuse)):
2139 stats["IUSE.invalid"] += 1
2140 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2143 if not badlicsyntax:
2144 # Parse the LICENSE variable, remove USE conditions and
2146 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2147 # Check each entry to ensure that it exists in PORTDIR's
2148 # license directory.
2149 for lic in licenses:
2150 # Need to check for "||" manually as no portage
2151 # function will remove it without removing values.
2152 if lic not in liclist and lic != "||":
2153 stats["LICENSE.invalid"] += 1
2154 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2155 elif lic in liclist_deprecated:
2156 stats["LICENSE.deprecated"] += 1
2157 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2160 myuse = myaux["KEYWORDS"].split()
2162 if mykey not in ("-*", "*", "~*"):
2164 if myskey[:1] == "-":
2166 if myskey[:1] == "~":
2168 if myskey not in kwlist:
2169 stats["KEYWORDS.invalid"] += 1
2170 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2171 elif myskey not in profiles:
2172 stats["KEYWORDS.invalid"] += 1
2173 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2178 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2179 except portage.exception.InvalidDependString as e:
2180 stats["RESTRICT.syntax"] += 1
2181 fails["RESTRICT.syntax"].append(
2182 "%s: RESTRICT: %s" % (relative_path, e))
2185 myrestrict = set(myrestrict)
2186 mybadrestrict = myrestrict.difference(valid_restrict)
2188 stats["RESTRICT.invalid"] += len(mybadrestrict)
2189 for mybad in mybadrestrict:
2190 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2192 required_use = myaux["REQUIRED_USE"]
2194 if not eapi_has_required_use(eapi):
2195 stats['EAPI.incompatible'] += 1
2196 fails['EAPI.incompatible'].append(
2197 relative_path + ": REQUIRED_USE" + \
2198 " not supported with EAPI='%s'" % (eapi,))
2200 portage.dep.check_required_use(required_use, (),
2201 pkg.iuse.is_valid_flag, eapi=eapi)
2202 except portage.exception.InvalidDependString as e:
2203 stats["REQUIRED_USE.syntax"] += 1
2204 fails["REQUIRED_USE.syntax"].append(
2205 "%s: REQUIRED_USE: %s" % (relative_path, e))
2209 relative_path = os.path.join(x, y + ".ebuild")
2210 full_path = os.path.join(repodir, relative_path)
2211 if not vcs_preserves_mtime:
2212 if ebuild_path not in new_ebuilds and \
2213 ebuild_path not in modified_ebuilds:
2216 # All ebuilds should have utf_8 encoding.
2217 f = io.open(_unicode_encode(full_path,
2218 encoding=_encodings['fs'], errors='strict'),
2219 mode='r', encoding=_encodings['repo.content'])
2221 for check_name, e in run_checks(f, pkg):
2222 stats[check_name] += 1
2223 fails[check_name].append(relative_path + ': %s' % e)
2226 except UnicodeDecodeError:
2227 # A file.UTF8 failure will have already been recorded above.
2231 # The dep_check() calls are the most expensive QA test. If --force
2232 # is enabled, there's no point in wasting time on these since the
2233 # user is intent on forcing the commit anyway.
2236 relevant_profiles = []
2237 for keyword, arch, groups in arches:
2238 if arch not in profiles:
2239 # A missing profile will create an error further down
2240 # during the KEYWORDS verification.
2243 if include_arches is not None:
2244 if arch not in include_arches:
2247 relevant_profiles.extend((keyword, groups, prof)
2248 for prof in profiles[arch])
2251 return item[2].sub_path
2253 relevant_profiles.sort(key=sort_key)
2255 for keyword, groups, prof in relevant_profiles:
2257 if prof.status not in ("stable", "dev") or \
2258 prof.status == "dev" and not options.include_dev:
2261 dep_settings = arch_caches.get(prof.sub_path)
2262 if dep_settings is None:
2263 dep_settings = portage.config(
2264 config_profile_path=prof.abs_path,
2265 config_incrementals=repoman_incrementals,
2266 config_root=config_root,
2268 _unmatched_removal=options.unmatched_removal,
2269 env=env, repositories=repoman_settings.repositories)
2270 dep_settings.categories = repoman_settings.categories
2271 if options.without_mask:
2272 dep_settings._mask_manager_obj = \
2273 copy.deepcopy(dep_settings._mask_manager)
2274 dep_settings._mask_manager._pmaskdict.clear()
2275 arch_caches[prof.sub_path] = dep_settings
2277 xmatch_cache_key = (prof.sub_path, tuple(groups))
2278 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2282 xcache = portdb.xcache
2283 xcache.update(shared_xmatch_caches)
2284 arch_xmatch_caches[xmatch_cache_key] = xcache
2286 trees[root]["porttree"].settings = dep_settings
2287 portdb.settings = dep_settings
2288 portdb.xcache = xcache
2290 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2291 # just in case, prevent config.reset() from nuking these.
2292 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2294 # This attribute is used in dbapi._match_use() to apply
2295 # use.stable.{mask,force} settings based on the stable
2296 # status of the parent package. This is required in order
2297 # for USE deps of unstable packages to be resolved correctly,
2298 # since otherwise use.stable.{mask,force} settings of
2299 # dependencies may conflict (see bug #456342).
2300 dep_settings._parent_stable = dep_settings._isStable(pkg)
2302 # Handle package.use*.{force,mask) calculation, for use
2304 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2305 pkg, stable=dep_settings._parent_stable)
2306 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2307 pkg, stable=dep_settings._parent_stable)
2309 if not baddepsyntax:
2310 ismasked = not ebuild_archs or \
2311 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2313 if not have_pmasked:
2314 have_pmasked = bool(dep_settings._getMaskAtom(
2315 pkg.cpv, pkg._metadata))
2316 if options.ignore_masked:
2318 #we are testing deps for a masked package; give it some lee-way
2320 matchmode = "minimum-all"
2323 matchmode = "minimum-visible"
2325 if not have_dev_keywords:
2326 have_dev_keywords = \
2327 bool(dev_keywords.intersection(keywords))
2329 if prof.status == "dev":
2330 suffix = suffix + "indev"
2332 for mytype in Package._dep_keys:
2334 mykey = "dependency.bad" + suffix
2335 myvalue = myaux[mytype]
2339 success, atoms = portage.dep_check(myvalue, portdb,
2340 dep_settings, use="all", mode=matchmode,
2346 # Don't bother with dependency.unknown for
2347 # cases in which *DEPEND.bad is triggered.
2349 # dep_check returns all blockers and they
2350 # aren't counted for *DEPEND.bad, so we
2352 if not atom.blocker:
2353 unknown_pkgs.discard(
2354 (mytype, atom.unevaluated_atom))
2356 if not prof.sub_path:
2357 # old-style virtuals currently aren't
2358 # resolvable with empty profile, since
2359 # 'virtuals' mappings are unavailable
2360 # (it would be expensive to search
2361 # for PROVIDE in all ebuilds)
2362 atoms = [atom for atom in atoms if not \
2363 (atom.cp.startswith('virtual/') and \
2364 not portdb.cp_list(atom.cp))]
2366 #we have some unsolvable deps
2367 #remove ! deps, which always show up as unsatisfiable
2368 atoms = [str(atom.unevaluated_atom) \
2369 for atom in atoms if not atom.blocker]
2371 #if we emptied out our list, continue:
2375 fails[mykey].append("%s: %s: %s(%s) %s" % \
2376 (relative_path, mytype, keyword,
2380 fails[mykey].append("%s: %s: %s(%s) %s" % \
2381 (relative_path, mytype, keyword,
2384 if not baddepsyntax and unknown_pkgs:
2386 for mytype, atom in unknown_pkgs:
2387 type_map.setdefault(mytype, set()).add(atom)
2388 for mytype, atoms in type_map.items():
2389 stats["dependency.unknown"] += 1
2390 fails["dependency.unknown"].append("%s: %s: %s" %
2391 (relative_path, mytype, ", ".join(sorted(atoms))))
2393 # check if there are unused local USE-descriptions in metadata.xml
2394 # (unless there are any invalids, to avoid noise)
2396 for myflag in muselist.difference(used_useflags):
2397 stats["metadata.warning"] += 1
2398 fails["metadata.warning"].append(
2399 "%s/metadata.xml: unused local USE-description: '%s'" % \
2402 if options.if_modified == "y" and len(effective_scanlist) < 1:
2403 logging.warn("--if-modified is enabled, but no modified packages were found!")
2405 if options.mode == "manifest":
2408 # dofail will be set to 1 if we have failed in at least one non-warning category
2410 # dowarn will be set to 1 if we tripped any warnings
2412 # dofull will be set if we should print a "repoman full" informational message
2413 dofull = options.mode != 'full'
2419 if x not in qawarnings:
2423 (dowarn and not (options.quiet or options.mode == "scan")):
2426 # Save QA output so that it can be conveniently displayed
2427 # in $EDITOR while the user creates a commit message.
2428 # Otherwise, the user would not be able to see this output
2429 # once the editor has taken over the screen.
2430 qa_output = io.StringIO()
2431 style_file = ConsoleStyleFile(sys.stdout)
2432 if options.mode == 'commit' and \
2433 (not commitmessage or not commitmessage.strip()):
2434 style_file.write_listener = qa_output
2435 console_writer = StyleWriter(file=style_file, maxcol=9999)
2436 console_writer.style_listener = style_file.new_styles
2438 f = formatter.AbstractFormatter(console_writer)
2440 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2443 del console_writer, f, style_file
2444 qa_output = qa_output.getvalue()
2445 qa_output = qa_output.splitlines(True)
2447 suggest_ignore_masked = False
2448 suggest_include_dev = False
2450 if have_pmasked and not (options.without_mask or options.ignore_masked):
2451 suggest_ignore_masked = True
2452 if have_dev_keywords and not options.include_dev:
2453 suggest_include_dev = True
2455 if suggest_ignore_masked or suggest_include_dev:
2457 if suggest_ignore_masked:
2458 print(bold("Note: use --without-mask to check " + \
2459 "KEYWORDS on dependencies of masked packages"))
2461 if suggest_include_dev:
2462 print(bold("Note: use --include-dev (-d) to check " + \
2463 "dependencies for 'dev' profiles"))
2466 if options.mode != 'commit':
2468 print(bold("Note: type \"repoman full\" for a complete listing."))
2469 if dowarn and not dofail:
2470 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.\"")
2472 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2474 print(bad("Please fix these important QA issues first."))
2475 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2478 if dofail and can_force and options.force and not options.pretend:
2479 print(green("RepoMan sez:") + \
2480 " \"You want to commit even with these QA issues?\n" + \
2481 " I'll take it this time, but I'm not happy.\"\n")
2483 if options.force and not can_force:
2484 print(bad("The --force option has been disabled due to extraordinary issues."))
2485 print(bad("Please fix these important QA issues first."))
2486 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2490 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2495 myvcstree = portage.cvstree.getentries("./", recursive=1)
2496 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2497 except SystemExit as e:
2498 raise # TODO propagate this
2500 err("Error retrieving CVS tree; exiting.")
2503 with repoman_popen("svn status --no-ignore") as f:
2504 svnstatus = f.readlines()
2505 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2506 except SystemExit as e:
2507 raise # TODO propagate this
2509 err("Error retrieving SVN info; exiting.")
2511 # get list of files not under version control or missing
2512 myf = repoman_popen("git ls-files --others")
2513 myunadded = ["./" + elem[:-1] for elem in myf]
2517 with repoman_popen("bzr status -S .") as f:
2518 bzrstatus = f.readlines()
2519 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2520 except SystemExit as e:
2521 raise # TODO propagate this
2523 err("Error retrieving bzr info; exiting.")
2525 with repoman_popen("hg status --no-status --unknown .") as f:
2526 myunadded = f.readlines()
2527 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2529 # Mercurial doesn't handle manually deleted files as removed from
2530 # the repository, so the user need to remove them before commit,
2531 # using "hg remove [FILES]"
2532 with repoman_popen("hg status --no-status --deleted .") as f:
2533 mydeleted = f.readlines()
2534 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2539 for x in range(len(myunadded)-1, -1, -1):
2540 xs = myunadded[x].split("/")
2541 if xs[-1] == "files":
2542 print("!!! files dir is not added! Please correct this.")
2544 elif xs[-1] == "Manifest":
2545 # It's a manifest... auto add
2546 myautoadd += [myunadded[x]]
2550 print(red("!!! The following files are in your local tree but are not added to the master"))
2551 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2558 if vcs == "hg" and mydeleted:
2559 print(red("!!! The following files are removed manually from your local tree but are not"))
2560 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2568 mycvstree = cvstree.getentries("./", recursive=1)
2569 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2570 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2571 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2572 bin_blob_pattern = re.compile("^-kb$")
2573 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2574 recursive=1, basedir="./"))
2577 with repoman_popen("svn status") as f:
2578 svnstatus = f.readlines()
2579 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2580 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2581 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2583 # Subversion expands keywords specified in svn:keywords properties.
2584 with repoman_popen("svn propget -R svn:keywords") as f:
2585 props = f.readlines()
2586 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2587 for prop in props if " - " in prop)
2590 with repoman_popen("git diff-index --name-only "
2591 "--relative --diff-filter=M HEAD") as f:
2592 mychanged = f.readlines()
2593 mychanged = ["./" + elem[:-1] for elem in mychanged]
2595 with repoman_popen("git diff-index --name-only "
2596 "--relative --diff-filter=A HEAD") as f:
2597 mynew = f.readlines()
2598 mynew = ["./" + elem[:-1] for elem in mynew]
2600 with repoman_popen("git diff-index --name-only "
2601 "--relative --diff-filter=D HEAD") as f:
2602 myremoved = f.readlines()
2603 myremoved = ["./" + elem[:-1] for elem in myremoved]
2606 with repoman_popen("bzr status -S .") as f:
2607 bzrstatus = f.readlines()
2608 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2609 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")]
2610 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2611 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")]
2612 # Bazaar expands nothing.
2615 with repoman_popen("hg status --no-status --modified .") as f:
2616 mychanged = f.readlines()
2617 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2619 with repoman_popen("hg status --no-status --added .") as f:
2620 mynew = f.readlines()
2621 mynew = ["./" + elem.rstrip() for elem in mynew]
2623 with repoman_popen("hg status --no-status --removed .") as f:
2624 myremoved = f.readlines()
2625 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2628 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2629 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2631 print("(Didn't find any changed files...)")
2635 # Manifests need to be regenerated after all other commits, so don't commit
2636 # them now even if they have changed.
2639 for f in mychanged + mynew:
2640 if "Manifest" == os.path.basename(f):
2644 myupdates.difference_update(myremoved)
2645 myupdates = list(myupdates)
2646 mymanifests = list(mymanifests)
2650 commitmessage = options.commitmsg
2651 if options.commitmsgfile:
2653 f = io.open(_unicode_encode(options.commitmsgfile,
2654 encoding=_encodings['fs'], errors='strict'),
2655 mode='r', encoding=_encodings['content'], errors='replace')
2656 commitmessage = f.read()
2659 except (IOError, OSError) as e:
2660 if e.errno == errno.ENOENT:
2661 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2664 # We've read the content so the file is no longer needed.
2665 commitmessagefile = None
2666 if not commitmessage or not commitmessage.strip():
2668 editor = os.environ.get("EDITOR")
2669 if editor and utilities.editor_is_executable(editor):
2670 commitmessage = utilities.get_commit_message_with_editor(
2671 editor, message=qa_output)
2673 commitmessage = utilities.get_commit_message_with_stdin()
2674 except KeyboardInterrupt:
2676 if not commitmessage or not commitmessage.strip():
2677 print("* no commit message? aborting commit.")
2679 commitmessage = commitmessage.rstrip()
2680 changelog_msg = commitmessage
2681 portage_version = getattr(portage, "VERSION", None)
2682 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2683 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2684 if portage_version is None:
2685 sys.stderr.write("Failed to insert portage version in message!\n")
2687 portage_version = "Unknown"
2691 report_options.append("--force")
2692 if options.ignore_arches:
2693 report_options.append("--ignore-arches")
2694 if include_arches is not None:
2695 report_options.append("--include-arches=\"%s\"" %
2696 " ".join(sorted(include_arches)))
2699 # Use new footer only for git (see bug #438364).
2700 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2702 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2704 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2706 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2708 unameout = platform.system() + " "
2709 if platform.system() in ["Darwin", "SunOS"]:
2710 unameout += platform.processor()
2712 unameout += platform.machine()
2713 commit_footer = "\n\n"
2715 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2716 commit_footer += "(Portage version: %s/%s/%s" % \
2717 (portage_version, vcs, unameout)
2719 commit_footer += ", RepoMan options: " + " ".join(report_options)
2721 commit_footer += ", signed Manifest commit with key %s" % \
2724 commit_footer += ", unsigned Manifest commit"
2725 commit_footer += ")"
2727 commitmessage += commit_footer
2729 if options.echangelog in ('y', 'force'):
2730 logging.info("checking for unmodified ChangeLog files")
2731 committer_name = utilities.get_committer_name(env=repoman_settings)
2732 for x in sorted(vcs_files_to_cps(
2733 chain(myupdates, mymanifests, myremoved))):
2734 catdir, pkgdir = x.split("/")
2735 checkdir = repodir + "/" + x
2736 checkdir_relative = ""
2738 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2740 checkdir_relative = os.path.join(catdir, checkdir_relative)
2741 checkdir_relative = os.path.join(".", checkdir_relative)
2743 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2744 changelog_modified = changelog_path in modified_changelogs
2745 if changelog_modified and options.echangelog != 'force':
2748 # get changes for this package
2749 cdrlen = len(checkdir_relative)
2750 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2751 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2752 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2754 # Skip ChangeLog generation if only the Manifest was modified,
2755 # as discussed in bug #398009.
2756 nontrivial_cl_files = set()
2757 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2758 nontrivial_cl_files.difference_update(['Manifest'])
2759 if not nontrivial_cl_files and options.echangelog != 'force':
2762 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2763 committer_name, changelog_msg,
2764 os.path.join(repodir, 'skel.ChangeLog'),
2766 new=clnew, removed=clremoved, changed=clchanged,
2767 pretend=options.pretend)
2768 if new_changelog is None:
2769 writemsg_level("!!! Updating the ChangeLog failed\n", \
2770 level=logging.ERROR, noiselevel=-1)
2773 # if the ChangeLog was just created, add it to vcs
2775 myautoadd.append(changelog_path)
2776 # myautoadd is appended to myupdates below
2778 myupdates.append(changelog_path)
2780 if options.ask and not options.pretend:
2781 # regenerate Manifest for modified ChangeLog (bug #420735)
2782 repoman_settings["O"] = checkdir
2783 digestgen(mysettings=repoman_settings, myportdb=portdb)
2786 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2787 add_cmd = [vcs, "add"]
2788 add_cmd += myautoadd
2790 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2794 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2795 not os.path.isabs(add_cmd[0]):
2796 # Python 3.1 _execvp throws TypeError for non-absolute executable
2797 # path passed as bytes (see http://bugs.python.org/issue8513).
2798 fullname = find_binary(add_cmd[0])
2799 if fullname is None:
2800 raise portage.exception.CommandNotFound(add_cmd[0])
2801 add_cmd[0] = fullname
2803 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2804 retcode = subprocess.call(add_cmd)
2805 if retcode != os.EX_OK:
2807 "Exiting on %s error code: %s\n" % (vcs, retcode))
2810 myupdates += myautoadd
2812 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2814 if vcs not in ('cvs', 'svn'):
2815 # With git, bzr and hg, there's never any keyword expansion, so
2816 # there's no need to regenerate manifests and all files will be
2817 # committed in one big commit at the end.
2819 elif not repo_config.thin_manifest:
2821 headerstring = "'\$(Header|Id).*\$'"
2823 svn_keywords = dict((k.lower(), k) for k in [
2826 "LastChangedRevision",
2837 for myfile in myupdates:
2839 # for CVS, no_expansion contains files that are excluded from expansion
2841 if myfile in no_expansion:
2844 # for SVN, expansion contains files that are included in expansion
2846 if myfile not in expansion:
2849 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2850 enabled_keywords = []
2851 for k in expansion[myfile]:
2852 keyword = svn_keywords.get(k.lower())
2853 if keyword is not None:
2854 enabled_keywords.append(keyword)
2856 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2858 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2859 portage._shell_quote(myfile))
2861 myheaders.append(myfile)
2863 print("%s have headers that will change." % green(str(len(myheaders))))
2864 print("* Files with headers will cause the manifests to be changed and committed separately.")
2866 logging.info("myupdates: %s", myupdates)
2867 logging.info("myheaders: %s", myheaders)
2869 if options.ask and userquery('Commit changes?', True) != 'Yes':
2870 print("* aborting commit.")
2871 sys.exit(128 + signal.SIGINT)
2873 # Handle the case where committed files have keywords which
2874 # will change and need a priming commit before the Manifest
2876 if (myupdates or myremoved) and myheaders:
2877 myfiles = myupdates + myremoved
2878 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2879 mymsg = os.fdopen(fd, "wb")
2880 mymsg.write(_unicode_encode(commitmessage))
2884 print(green("Using commit message:"))
2885 print(green("------------------------------------------------------------------------------"))
2886 print(commitmessage)
2887 print(green("------------------------------------------------------------------------------"))
2890 # Having a leading ./ prefix on file paths can trigger a bug in
2891 # the cvs server when committing files to multiple directories,
2892 # so strip the prefix.
2893 myfiles = [f.lstrip("./") for f in myfiles]
2896 commit_cmd.extend(vcs_global_opts)
2897 commit_cmd.append("commit")
2898 commit_cmd.extend(vcs_local_opts)
2899 commit_cmd.extend(["-F", commitmessagefile])
2900 commit_cmd.extend(myfiles)
2904 print("(%s)" % (" ".join(commit_cmd),))
2906 retval = spawn(commit_cmd, env=commit_env)
2907 if retval != os.EX_OK:
2908 writemsg_level(("!!! Exiting on %s (shell) " + \
2909 "error code: %s\n") % (vcs, retval),
2910 level=logging.ERROR, noiselevel=-1)
2914 os.unlink(commitmessagefile)
2918 # Setup the GPG commands
2919 def gpgsign(filename):
2920 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2922 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2923 " Is make.globals missing?")
2924 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2925 "PORTAGE_GPG_KEY" not in repoman_settings:
2926 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2927 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2928 if "PORTAGE_GPG_DIR" not in repoman_settings:
2929 repoman_settings["PORTAGE_GPG_DIR"] = \
2930 os.path.expanduser("~/.gnupg")
2931 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2932 % repoman_settings["PORTAGE_GPG_DIR"])
2934 repoman_settings["PORTAGE_GPG_DIR"] = \
2935 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2936 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2937 raise portage.exception.InvalidLocation(
2938 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2939 repoman_settings["PORTAGE_GPG_DIR"])
2940 gpgvars = {"FILE": filename}
2941 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2942 v = repoman_settings.get(k)
2945 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2947 print("(" + gpgcmd + ")")
2949 # Encode unicode manually for bug #310789.
2950 gpgcmd = portage.util.shlex_split(gpgcmd)
2952 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2953 not os.path.isabs(gpgcmd[0]):
2954 # Python 3.1 _execvp throws TypeError for non-absolute executable
2955 # path passed as bytes (see http://bugs.python.org/issue8513).
2956 fullname = find_binary(gpgcmd[0])
2957 if fullname is None:
2958 raise portage.exception.CommandNotFound(gpgcmd[0])
2959 gpgcmd[0] = fullname
2961 gpgcmd = [_unicode_encode(arg,
2962 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2963 rValue = subprocess.call(gpgcmd)
2964 if rValue == os.EX_OK:
2965 os.rename(filename + ".asc", filename)
2967 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2969 def need_signature(filename):
2971 with open(_unicode_encode(filename,
2972 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2973 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2974 except IOError as e:
2975 if e.errno in (errno.ENOENT, errno.ESTALE):
2979 # When files are removed and re-added, the cvs server will put /Attic/
2980 # inside the $Header path. This code detects the problem and corrects it
2981 # so that the Manifest will generate correctly. See bug #169500.
2982 # Use binary mode in order to avoid potential character encoding issues.
2983 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2984 attic_str = b'/Attic/'
2985 attic_replace = b'/'
2987 f = open(_unicode_encode(x,
2988 encoding=_encodings['fs'], errors='strict'),
2990 mylines = f.readlines()
2993 for i, line in enumerate(mylines):
2994 if cvs_header_re.match(line) is not None and \
2996 mylines[i] = line.replace(attic_str, attic_replace)
2999 portage.util.write_atomic(x, b''.join(mylines),
3003 print(green("RepoMan sez:"), "\"You're rather crazy... "
3004 "doing the entire repository.\"\n")
3006 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
3008 for x in sorted(vcs_files_to_cps(
3009 chain(myupdates, myremoved, mymanifests))):
3010 repoman_settings["O"] = os.path.join(repodir, x)
3011 digestgen(mysettings=repoman_settings, myportdb=portdb)
3017 for x in sorted(vcs_files_to_cps(
3018 chain(myupdates, myremoved, mymanifests))):
3019 repoman_settings["O"] = os.path.join(repodir, x)
3020 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
3021 if not need_signature(manifest_path):
3023 gpgsign(manifest_path)
3024 except portage.exception.PortageException as e:
3025 portage.writemsg("!!! %s\n" % str(e))
3026 portage.writemsg("!!! Disabled FEATURES='sign'\n")
3030 # It's not safe to use the git commit -a option since there might
3031 # be some modified files elsewhere in the working tree that the
3032 # user doesn't want to commit. Therefore, call git update-index
3033 # in order to ensure that the index is updated with the latest
3034 # versions of all new and modified files in the relevant portion
3035 # of the working tree.
3036 myfiles = mymanifests + myupdates
3038 update_index_cmd = ["git", "update-index"]
3039 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
3041 print("(%s)" % (" ".join(update_index_cmd),))
3043 retval = spawn(update_index_cmd, env=os.environ)
3044 if retval != os.EX_OK:
3045 writemsg_level(("!!! Exiting on %s (shell) " + \
3046 "error code: %s\n") % (vcs, retval),
3047 level=logging.ERROR, noiselevel=-1)
3051 myfiles = mymanifests[:]
3052 # If there are no header (SVN/CVS keywords) changes in
3053 # the files, this Manifest commit must include the
3054 # other (yet uncommitted) files.
3056 myfiles += myupdates
3057 myfiles += myremoved
3060 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3061 mymsg = os.fdopen(fd, "wb")
3062 mymsg.write(_unicode_encode(commitmessage))
3066 if options.pretend and vcs is None:
3067 # substitute a bogus value for pretend output
3068 commit_cmd.append("cvs")
3070 commit_cmd.append(vcs)
3071 commit_cmd.extend(vcs_global_opts)
3072 commit_cmd.append("commit")
3073 commit_cmd.extend(vcs_local_opts)
3075 commit_cmd.extend(["--logfile", commitmessagefile])
3076 commit_cmd.extend(myfiles)
3078 commit_cmd.extend(["-F", commitmessagefile])
3079 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3083 print("(%s)" % (" ".join(commit_cmd),))
3085 retval = spawn(commit_cmd, env=commit_env)
3086 if retval != os.EX_OK:
3087 if repo_config.sign_commit and vcs == 'git' and \
3088 not git_supports_gpg_sign():
3089 # Inform user that newer git is needed (bug #403323).
3091 "Git >=1.7.9 is required for signed commits!")
3093 writemsg_level(("!!! Exiting on %s (shell) " + \
3094 "error code: %s\n") % (vcs, retval),
3095 level=logging.ERROR, noiselevel=-1)
3099 os.unlink(commitmessagefile)
3105 print("Commit complete.")
3107 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3108 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")