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
26 from itertools import chain
27 from stat import S_ISDIR
30 from urllib.parse import urlparse
32 from urlparse import urlparse
34 from os import path as osp
35 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
36 sys.path.insert(0, pym_path)
38 portage._internal_caller = True
39 portage._disable_legacy_globals()
42 import xml.etree.ElementTree
43 from xml.parsers.expat import ExpatError
44 except (SystemExit, KeyboardInterrupt):
46 except (ImportError, SystemError, RuntimeError, Exception):
47 # broken or missing xml support
48 # http://bugs.python.org/issue14988
49 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
50 from portage.output import EOutput
56 from portage import os
57 from portage import _encodings
58 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.util._argparse import ArgumentParser
80 from portage.package.ebuild.digestgen import digestgen
81 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
83 if sys.hexversion >= 0x3000000:
86 util.initialize_logger()
88 # 14 is the length of DESCRIPTION=""
90 allowed_filename_chars="a-zA-Z0-9._-+:"
91 pv_toolong_re = re.compile(r'[0-9]{19,}')
92 GPG_KEY_ID_REGEX = r'(0x)?([0-9a-fA-F]{8}|[0-9a-fA-F]{16}|[0-9a-fA-F]{24}|[0-9a-fA-F]{32}|[0-9a-fA-F]{40})!?'
93 bad = create_color_func("BAD")
95 # A sane umask is needed for files that portage creates.
97 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
98 # behave incrementally.
99 repoman_incrementals = tuple(x for x in \
100 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
101 config_root = os.environ.get("PORTAGE_CONFIGROOT")
102 repoman_settings = portage.config(config_root=config_root, local_config=False)
104 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
105 repoman_settings.get('TERM') == 'dumb' or \
106 not sys.stdout.isatty():
110 print("repoman: " + txt)
116 def exithandler(signum=None, _frame=None):
117 logging.fatal("Interrupted; exiting...")
121 sys.exit(128 + signum)
123 signal.signal(signal.SIGINT, exithandler)
125 def ParseArgs(argv, qahelp):
126 """This function uses a customized optionParser to parse command line arguments for repoman
128 argv - a sequence of command line arguments
129 qahelp - a dict of qa warning to help message
131 (opts, args), just like a call to parser.parse_args()
134 argv = portage._decode_argv(argv)
137 'commit' : 'Run a scan then commit changes',
138 'ci' : 'Run a scan then commit changes',
139 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
140 'full' : 'Scan directory tree and print all issues (not a summary)',
141 'help' : 'Show this screen',
142 'manifest' : 'Generate a Manifest (fetches files if necessary)',
143 'manifest-check' : 'Check Manifests for missing or incorrect digests',
144 'scan' : 'Scan directory tree for QA issues'
147 mode_keys = list(modes)
150 parser = ArgumentParser(usage="repoman [options] [mode]",
151 description="Modes: %s" % " | ".join(mode_keys),
152 epilog="For more help consult the man page.")
154 parser.add_argument('-a', '--ask', dest='ask', action='store_true', default=False,
155 help='Request a confirmation before commiting')
157 parser.add_argument('-m', '--commitmsg', dest='commitmsg',
158 help='specify a commit message on the command line')
160 parser.add_argument('-M', '--commitmsgfile', dest='commitmsgfile',
161 help='specify a path to a file that contains a commit message')
163 parser.add_argument('--digest',
164 choices=('y', 'n'), metavar='<y|n>',
165 help='Automatically update Manifest digests for modified files')
167 parser.add_argument('-p', '--pretend', dest='pretend', default=False,
168 action='store_true', help='don\'t commit or fix anything; just show what would be done')
170 parser.add_argument('-q', '--quiet', dest="quiet", action="count", default=0,
171 help='do not print unnecessary messages')
174 '--echangelog', choices=('y', 'n', 'force'), metavar="<y|n|force>",
175 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
176 'regardless of modification if \'force\' is specified)')
178 parser.add_argument('--experimental-inherit', choices=('y', 'n'),
179 metavar="<y|n>", default='n',
180 help='Enable experimental inherit.missing checks which may misbehave'
181 ' when the internal eclass database becomes outdated')
183 parser.add_argument('-f', '--force', dest='force', default=False, action='store_true',
184 help='Commit with QA violations')
186 parser.add_argument('--vcs', dest='vcs',
187 help='Force using specific VCS instead of autodetection')
189 parser.add_argument('-v', '--verbose', dest="verbosity", action='count',
190 help='be very verbose in output', default=0)
192 parser.add_argument('-V', '--version', dest='version', action='store_true',
193 help='show version info')
195 parser.add_argument('-x', '--xmlparse', dest='xml_parse', action='store_true',
196 default=False, help='forces the metadata.xml parse check to be carried out')
199 '--if-modified', choices=('y', 'n'), default='n',
201 help='only check packages that have uncommitted modifications')
203 parser.add_argument('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
204 default=False, help='ignore arch-specific failures (where arch != host)')
206 parser.add_argument("--ignore-default-opts",
208 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
210 parser.add_argument('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
211 default=False, help='ignore masked packages (not allowed with commit mode)')
213 parser.add_argument('--include-arches', dest='include_arches',
214 metavar='ARCHES', action='append',
215 help='A space separated list of arches used to '
216 'filter the selection of profiles for dependency checks')
218 parser.add_argument('-d', '--include-dev', dest='include_dev', action='store_true',
219 default=False, help='include dev profiles in dependency checks')
221 parser.add_argument('-e', '--include-exp-profiles', choices=('y', 'n'),
222 default=False, help='include exp profiles in dependency checks',
225 parser.add_argument('--unmatched-removal', dest='unmatched_removal', action='store_true',
226 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
228 parser.add_argument('--without-mask', dest='without_mask', action='store_true',
229 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
231 parser.add_argument('--mode', dest='mode', choices=mode_keys,
232 help='specify which mode repoman will run in (default=full)')
234 opts, args = parser.parse_known_args(argv[1:])
236 if not opts.ignore_default_opts:
237 default_opts = portage.util.shlex_split(
238 repoman_settings.get("REPOMAN_DEFAULT_OPTS", ""))
240 opts, args = parser.parse_known_args(default_opts + sys.argv[1:])
242 if opts.mode == 'help':
243 parser.print_help(short=False)
251 parser.error("invalid mode: %s" % arg)
256 if opts.mode == 'ci':
257 opts.mode = 'commit' # backwards compat shortcut
259 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
260 for val in range(opts.verbosity):
261 logger = logging.getLogger()
262 logger.setLevel(logger.getEffectiveLevel() - 10)
264 for val in range(opts.quiet):
265 logger = logging.getLogger()
266 logger.setLevel(logger.getEffectiveLevel() + 10)
268 if opts.mode == 'commit' and not (opts.force or opts.pretend):
269 if opts.ignore_masked:
270 opts.ignore_masked = False
271 logging.warn('Commit mode automatically disables --ignore-masked')
272 if opts.without_mask:
273 opts.without_mask = False
274 logging.warn('Commit mode automatically disables --without-mask')
279 "CVS/Entries.IO_error": "Attempting to commit, and an IO error was encountered access the Entries file",
280 "ebuild.invalidname": "Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
281 "ebuild.namenomatch": "Ebuild files that do not have the same name as their parent directory",
282 "changelog.ebuildadded": "An ebuild was added but the ChangeLog was not modified",
283 "changelog.missing": "Missing ChangeLog files",
284 "ebuild.notadded": "Ebuilds that exist but have not been added to cvs",
285 "ebuild.patches": "PATCHES variable should be a bash array to ensure white space safety",
286 "changelog.notadded": "ChangeLogs that exist but have not been added to cvs",
287 "dependency.bad": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds)",
288 "dependency.badmasked": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds)",
289 "dependency.badindev": "User-visible ebuilds with unsatisfied dependencies (matched against *visible* ebuilds) in developing arch",
290 "dependency.badmaskedindev": "Masked ebuilds with unsatisfied dependencies (matched against *all* ebuilds) in developing arch",
291 "dependency.badtilde": "Uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
292 "dependency.syntax": "Syntax error in dependency string (usually an extra/missing space/parenthesis)",
293 "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)",
294 "file.executable": "Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
295 "file.size": "Files in the files directory must be under 20 KiB",
296 "file.size.fatal": "Files in the files directory must be under 60 KiB",
297 "file.name": "File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
298 "file.UTF8": "File is not UTF8 compliant",
299 "inherit.deprecated": "Ebuild inherits a deprecated eclass",
300 "inherit.missing": "Ebuild uses functions from an eclass but does not inherit it",
301 "inherit.unused": "Ebuild inherits an eclass but does not use it",
302 "java.eclassesnotused": "With virtual/jdk in DEPEND you must inherit a java eclass",
303 "wxwidgets.eclassnotused": "Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
304 "KEYWORDS.dropped": "Ebuilds that appear to have dropped KEYWORDS for some arch",
305 "KEYWORDS.missing": "Ebuilds that have a missing or empty KEYWORDS variable",
306 "KEYWORDS.stable": "Ebuilds that have been added directly with stable KEYWORDS",
307 "KEYWORDS.stupid": "Ebuilds that use KEYWORDS=-* instead of package.mask",
308 "LICENSE.missing": "Ebuilds that have a missing or empty LICENSE variable",
309 "LICENSE.virtual": "Virtuals that have a non-empty LICENSE variable",
310 "DESCRIPTION.missing": "Ebuilds that have a missing or empty DESCRIPTION variable",
311 "DESCRIPTION.toolong": "DESCRIPTION is over %d characters" % max_desc_len,
312 "EAPI.definition": "EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
313 "EAPI.deprecated": "Ebuilds that use features that are deprecated in the current EAPI",
314 "EAPI.incompatible": "Ebuilds that use features that are only available with a different EAPI",
315 "EAPI.unsupported": "Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
316 "SLOT.invalid": "Ebuilds that have a missing or invalid SLOT variable value",
317 "HOMEPAGE.missing": "Ebuilds that have a missing or empty HOMEPAGE variable",
318 "HOMEPAGE.virtual": "Virtuals that have a non-empty HOMEPAGE variable",
319 "PDEPEND.suspect": "PDEPEND contains a package that usually only belongs in DEPEND.",
320 "LICENSE.syntax": "Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
321 "PROVIDE.syntax": "Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
322 "PROPERTIES.syntax": "Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
323 "RESTRICT.syntax": "Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
324 "REQUIRED_USE.syntax": "Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
325 "SRC_URI.syntax": "Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
326 "SRC_URI.mirror": "A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
327 "ebuild.syntax": "Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
328 "ebuild.output": "A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
329 "ebuild.nesteddie": "Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
330 "variable.invalidchar": "A variable contains an invalid character that is not part of the ASCII character set",
331 "variable.readonly": "Assigning a readonly variable",
332 "variable.usedwithhelpers": "Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
333 "LIVEVCS.stable": "This ebuild is a live checkout from a VCS but has stable keywords.",
334 "LIVEVCS.unmasked": "This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
335 "IUSE.invalid": "This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
336 "IUSE.missing": "This ebuild has a USE conditional which references a flag that is not listed in IUSE",
337 "IUSE.rubydeprecated": "The ebuild has set a ruby interpreter in USE_RUBY, that is not available as a ruby target anymore",
338 "LICENSE.invalid": "This ebuild is listing a license that doesnt exist in portages license/ dir.",
339 "LICENSE.deprecated": "This ebuild is listing a deprecated license.",
340 "KEYWORDS.invalid": "This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
341 "RDEPEND.implicit": "RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
342 "RDEPEND.suspect": "RDEPEND contains a package that usually only belongs in DEPEND.",
343 "RESTRICT.invalid": "This ebuild contains invalid RESTRICT values.",
344 "digest.assumed": "Existing digest must be assumed correct (Package level only)",
345 "digest.missing": "Some files listed in SRC_URI aren't referenced in the Manifest",
346 "digest.unused": "Some files listed in the Manifest aren't referenced in SRC_URI",
347 "ebuild.majorsyn": "This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
348 "ebuild.minorsyn": "This ebuild has a minor syntax error that contravenes gentoo coding style",
349 "ebuild.badheader": "This ebuild has a malformed header",
350 "manifest.bad": "Manifest has missing or incorrect digests",
351 "metadata.missing": "Missing metadata.xml files",
352 "metadata.bad": "Bad metadata.xml files",
353 "metadata.warning": "Warnings in metadata.xml files",
354 "portage.internal": "The ebuild uses an internal Portage function or variable",
355 "repo.eapi.banned": "The ebuild uses an EAPI which is banned by the repository's metadata/layout.conf settings",
356 "repo.eapi.deprecated": "The ebuild uses an EAPI which is deprecated by the repository's metadata/layout.conf settings",
357 "virtual.oldstyle": "The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
358 "virtual.suspect": "Ebuild contains a package that usually should be pulled via virtual/, not directly.",
359 "usage.obsolete": "The ebuild makes use of an obsolete construct",
360 "upstream.workaround": "The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
363 qacats = list(qahelp)
368 "changelog.notadded",
369 "dependency.unknown",
374 "dependency.badmasked",
375 "dependency.badindev",
376 "dependency.badmaskedindev",
377 "dependency.badtilde",
378 "DESCRIPTION.toolong",
381 "LICENSE.deprecated",
396 "inherit.deprecated",
397 "java.eclassesnotused",
398 "wxwidgets.eclassnotused",
401 "repo.eapi.deprecated",
403 "upstream.workaround",
406 "IUSE.rubydeprecated",
409 non_ascii_re = re.compile(r'[^\x00-\x7f]')
411 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
412 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
413 allvars.update(Package.metadata_keys)
414 allvars = sorted(allvars)
416 for x in missingvars:
419 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
423 valid_restrict = frozenset(["binchecks", "bindist",
424 "fetch", "installsources", "mirror", "preserve-libs",
425 "primaryuri", "splitdebug", "strip", "test", "userpriv"])
427 live_eclasses = frozenset([
438 suspect_rdepend = frozenset([
439 "app-arch/cabextract",
440 "app-arch/rpm2targz",
445 "dev-perl/extutils-pkgconfig",
451 "dev-util/gtk-doc-am",
454 "dev-util/pkg-config-lite",
456 "dev-util/pkgconfig",
457 "dev-util/pkgconfig-openbsd",
461 "media-gfx/ebdftopcf",
463 "sys-devel/autoconf",
464 "sys-devel/automake",
471 "virtual/linux-sources",
478 "dev-util/pkg-config-lite":"virtual/pkgconfig",
479 "dev-util/pkgconf":"virtual/pkgconfig",
480 "dev-util/pkgconfig":"virtual/pkgconfig",
481 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
482 "dev-libs/libusb":"virtual/libusb",
483 "dev-libs/libusbx":"virtual/libusb",
484 "dev-libs/libusb-compat":"virtual/libusb",
487 ruby_deprecated = frozenset([
488 "ruby_targets_ree18",
491 metadata_xml_encoding = 'UTF-8'
492 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
493 (metadata_xml_encoding,)
494 metadata_doctype_name = 'pkgmetadata'
495 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
496 # force refetch if the local copy creation time is older than this
497 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
500 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
502 options, arguments = ParseArgs(sys.argv, qahelp)
505 print("Portage", portage.VERSION)
508 if options.experimental_inherit == 'y':
509 # This is experimental, so it's non-fatal.
510 qawarnings.add("inherit.missing")
511 repoman.checks._init(experimental_inherit=True)
513 # Set this to False when an extraordinary issue (generally
514 # something other than a QA issue) makes it impossible to
515 # commit (like if Manifest generation fails).
518 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
522 myreporoot = os.path.basename(portdir_overlay)
523 myreporoot += mydir[len(portdir_overlay):]
526 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
531 vcses = utilities.FindVCS()
533 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
534 print(red('*** Please either clean up your workdir or specify --vcs option.'))
541 if options.if_modified == "y" and vcs is None:
542 logging.info("Not in a version controlled repository; "
543 "disabling --if-modified.")
544 options.if_modified = "n"
546 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
547 vcs_preserves_mtime = vcs in ('cvs',)
549 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
550 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
551 if vcs_global_opts is None:
552 if vcs in ('cvs', 'svn'):
553 vcs_global_opts = "-q"
556 vcs_global_opts = vcs_global_opts.split()
558 if options.mode == 'commit' and not options.pretend and not vcs:
559 logging.info("Not in a version controlled repository; enabling pretend mode.")
560 options.pretend = True
562 # Ensure that current repository is in the list of enabled repositories.
563 repodir = os.path.realpath(portdir_overlay)
565 repoman_settings.repositories.get_repo_for_location(repodir)
567 repo_name = portage.repository.config.RepoConfig._read_valid_repo_name(portdir_overlay)[0]
568 layout_conf_data = portage.repository.config.parse_layout_conf(portdir_overlay)[0]
569 if layout_conf_data['repo-name']:
570 repo_name = layout_conf_data['repo-name']
571 tmp_conf_file = io.StringIO(textwrap.dedent("""
574 """) % (repo_name, portdir_overlay))
575 # Ensure that the repository corresponding to $PWD overrides a
576 # repository of the same name referenced by the existing PORTDIR
577 # or PORTDIR_OVERLAY settings.
578 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
579 (repoman_settings.get('PORTDIR_OVERLAY', ''),
580 portage._shell_quote(portdir_overlay))
581 repositories = portage.repository.config.load_repository_config(repoman_settings, extra_files=[tmp_conf_file])
582 # We have to call the config constructor again so that attributes
583 # dependent on config.repositories are initialized correctly.
584 repoman_settings = portage.config(config_root=config_root, local_config=False, repositories=repositories)
586 root = repoman_settings['EROOT']
588 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
590 portdb = trees[root]['porttree'].dbapi
592 # Constrain dependency resolution to the master(s)
593 # that are specified in layout.conf.
594 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
595 portdb.porttrees = list(repo_config.eclass_db.porttrees)
596 portdir = portdb.porttrees[0]
597 commit_env = os.environ.copy()
598 # list() is for iteration on a copy.
599 for repo in list(repoman_settings.repositories):
600 # all paths are canonical
601 if repo.location not in repo_config.eclass_db.porttrees:
602 del repoman_settings.repositories[repo.name]
604 if repo_config.allow_provide_virtual:
605 qawarnings.add("virtual.oldstyle")
607 if repo_config.sign_commit:
609 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
610 # the commit arguments. If key_id is unspecified, then it must be
611 # configured by `git config user.signingkey key_id`.
612 vcs_local_opts.append("--gpg-sign")
613 if repoman_settings.get("PORTAGE_GPG_DIR"):
614 # Pass GNUPGHOME to git for bug #462362.
615 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
617 # Pass GPG_TTY to git for bug #477728.
619 commit_env["GPG_TTY"] = os.ttyname(sys.stdin.fileno())
623 # In order to disable manifest signatures, repos may set
624 # "sign-manifests = false" in metadata/layout.conf. This
625 # can be used to prevent merge conflicts like those that
626 # thin-manifests is designed to prevent.
627 sign_manifests = "sign" in repoman_settings.features and \
628 repo_config.sign_manifest
630 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
631 options.mode in ("commit",) and not sign_manifests:
632 msg = ("The '%s' repository has manifest signatures enabled, "
633 "but FEATURES=sign is currently disabled. In order to avoid this "
634 "warning, enable FEATURES=sign in make.conf. Alternatively, "
635 "repositories can disable manifest signatures by setting "
636 "'sign-manifests = false' in metadata/layout.conf.") % \
638 for line in textwrap.wrap(msg, 60):
641 if sign_manifests and options.mode in ("commit",) and \
642 repoman_settings.get("PORTAGE_GPG_KEY") and \
643 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
644 repoman_settings["PORTAGE_GPG_KEY"]) is None:
645 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
646 repoman_settings["PORTAGE_GPG_KEY"])
649 manifest_hashes = repo_config.manifest_hashes
650 if manifest_hashes is None:
651 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
653 if options.mode in ("commit", "fix", "manifest"):
654 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
655 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
656 "metadata/layout.conf does not contain the '%s' hash which "
657 "is required by this portage version. You will have to "
658 "upgrade portage if you want to generate valid manifests for "
659 "this repository.") % \
660 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
661 for line in textwrap.wrap(msg, 70):
665 unsupported_hashes = manifest_hashes.difference(
666 portage.const.MANIFEST2_HASH_FUNCTIONS)
667 if unsupported_hashes:
668 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
669 "metadata/layout.conf contains one or more hash types '%s' "
670 "which are not supported by this portage version. You will "
671 "have to upgrade portage if you want to generate valid "
672 "manifests for this repository.") % \
673 (repo_config.name, " ".join(sorted(unsupported_hashes)))
674 for line in textwrap.wrap(msg, 70):
678 if options.echangelog is None and repo_config.update_changelog:
679 options.echangelog = 'y'
682 options.echangelog = 'n'
684 # The --echangelog option causes automatic ChangeLog generation,
685 # which invalidates changelog.ebuildadded and changelog.missing
687 # Note: Some don't use ChangeLogs in distributed SCMs.
688 # It will be generated on server side from scm log,
689 # before package moves to the rsync server.
690 # This is needed because they try to avoid merge collisions.
691 # Gentoo's Council decided to always use the ChangeLog file.
692 # TODO: shouldn't this just be switched on the repo, iso the VCS?
693 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
695 if 'digest' in repoman_settings.features and options.digest != 'n':
698 logging.debug("vcs: %s" % (vcs,))
699 logging.debug("repo config: %s" % (repo_config,))
700 logging.debug("options: %s" % (options,))
702 # It's confusing if these warnings are displayed without the user
703 # being told which profile they come from, so disable them.
704 env = os.environ.copy()
705 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
708 for path in repo_config.eclass_db.porttrees:
709 categories.extend(portage.util.grabfile(
710 os.path.join(path, 'profiles', 'categories')))
711 repoman_settings.categories = frozenset(
712 portage.util.stack_lists([categories], incremental=1))
713 categories = repoman_settings.categories
715 portdb.settings = repoman_settings
716 root_config = RootConfig(repoman_settings, trees[root], None)
717 # We really only need to cache the metadata that's necessary for visibility
718 # filtering. Anything else can be discarded to reduce memory consumption.
719 portdb._aux_cache_keys.clear()
720 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
722 reposplit = myreporoot.split(os.path.sep)
723 repolevel = len(reposplit)
725 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
726 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
727 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
728 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
729 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
730 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
731 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
733 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
735 # Make startdir relative to the canonical repodir, so that we can pass
736 # it to digestgen and it won't have to be canonicalized again.
740 startdir = normalize_path(mydir)
741 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
744 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.")
746 def repoman_getstatusoutput(cmd):
748 Implements an interface similar to getstatusoutput(), but with
749 customized unicode handling (see bug #310789) and without the shell.
751 args = portage.util.shlex_split(cmd)
753 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
754 not os.path.isabs(args[0]):
755 # Python 3.1 _execvp throws TypeError for non-absolute executable
756 # path passed as bytes (see http://bugs.python.org/issue8513).
757 fullname = find_binary(args[0])
759 raise portage.exception.CommandNotFound(args[0])
762 encoding = _encodings['fs']
763 args = [_unicode_encode(x,
764 encoding=encoding, errors='strict') for x in args]
765 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
766 stderr=subprocess.STDOUT)
767 output = portage._unicode_decode(proc.communicate()[0],
768 encoding=encoding, errors='strict')
769 if output and output[-1] == "\n":
770 # getstatusoutput strips one newline
772 return (proc.wait(), output)
774 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
776 Implements an interface similar to os.popen(), but with customized
777 unicode handling (see bug #310789) and without the shell.
780 __slots__ = ('_proc', '_stdout')
782 def __init__(self, cmd):
783 args = portage.util.shlex_split(cmd)
785 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
786 not os.path.isabs(args[0]):
787 # Python 3.1 _execvp throws TypeError for non-absolute executable
788 # path passed as bytes (see http://bugs.python.org/issue8513).
789 fullname = find_binary(args[0])
791 raise portage.exception.CommandNotFound(args[0])
794 encoding = _encodings['fs']
795 args = [_unicode_encode(x,
796 encoding=encoding, errors='strict') for x in args]
797 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
798 object.__setattr__(self, '_proc', proc)
799 object.__setattr__(self, '_stdout',
800 codecs.getreader(encoding)(proc.stdout, 'strict'))
802 def _get_target(self):
803 return object.__getattribute__(self, '_stdout')
805 __enter__ = _get_target
807 def __exit__(self, exc_type, exc_value, traceback):
808 proc = object.__getattribute__(self, '_proc')
812 class ProfileDesc(object):
813 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
814 def __init__(self, arch, status, sub_path, tree_path):
818 sub_path = normalize_path(sub_path.lstrip(os.sep))
819 self.sub_path = sub_path
820 self.tree_path = tree_path
822 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
824 self.abs_path = tree_path
829 return 'empty profile'
832 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
834 # get lists of valid keywords, licenses, and use
838 global_pmasklines = []
840 for path in portdb.porttrees:
842 liclist.update(os.listdir(os.path.join(path, "licenses")))
845 kwlist.update(portage.grabfile(os.path.join(path,
846 "profiles", "arch.list")))
848 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
854 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
856 expand_list = os.listdir(expand_desc_dir)
860 for fn in expand_list:
861 if not fn[-5:] == '.desc':
863 use_prefix = fn[:-5].lower() + '_'
864 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
867 uselist.add(use_prefix + x[0])
869 global_pmasklines.append(portage.util.grabfile_package(
870 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
872 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
874 desc_file = io.open(_unicode_encode(desc_path,
875 encoding=_encodings['fs'], errors='strict'),
876 mode='r', encoding=_encodings['repo.content'], errors='replace')
877 except EnvironmentError:
880 for i, x in enumerate(desc_file):
887 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
888 desc_path + " line %d" % (i + 1, ))
889 elif arch[0] not in kwlist:
890 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
891 desc_path + " line %d" % (i + 1, ))
892 elif arch[2] not in valid_profile_types:
893 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
894 desc_path + " line %d" % (i + 1, ))
895 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
896 if not os.path.isdir(profile_desc.abs_path):
898 "Invalid %s profile (%s) for arch %s in %s line %d",
899 arch[2], arch[1], arch[0], desc_path, i + 1)
902 os.path.join(profile_desc.abs_path, 'deprecated')):
904 profile_list.append(profile_desc)
907 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
908 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
910 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
911 global_pmaskdict = {}
912 for x in global_pmasklines:
913 global_pmaskdict.setdefault(x.cp, []).append(x)
914 del global_pmasklines
916 def has_global_mask(pkg):
917 mask_atoms = global_pmaskdict.get(pkg.cp)
921 if portage.dep.match_from_list(x, pkg_list):
925 # Ensure that profile sub_path attributes are unique. Process in reverse order
926 # so that profiles with duplicate sub_path from overlays will override
927 # profiles with the same sub_path from parent repos.
929 profile_list.reverse()
930 profile_sub_paths = set()
931 for prof in profile_list:
932 if prof.sub_path in profile_sub_paths:
934 profile_sub_paths.add(prof.sub_path)
935 profiles.setdefault(prof.arch, []).append(prof)
937 # Use an empty profile for checking dependencies of
938 # packages that have empty KEYWORDS.
939 prof = ProfileDesc('**', 'stable', '', '')
940 profiles.setdefault(prof.arch, []).append(prof)
942 for x in repoman_settings.archlist():
945 if x not in profiles:
946 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
947 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
948 print(red("up with the " + x + " team."))
951 liclist_deprecated = set()
952 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
953 liclist_deprecated.update(
954 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
957 logging.fatal("Couldn't find licenses?")
961 logging.fatal("Couldn't read KEYWORDS from arch.list")
965 logging.fatal("Couldn't find use.desc?")
970 # we are inside a category directory
971 catdir = reposplit[-1]
972 if catdir not in categories:
974 mydirlist = os.listdir(startdir)
976 if x == "CVS" or x.startswith("."):
978 if os.path.isdir(startdir + "/" + x):
979 scanlist.append(catdir + "/" + x)
980 repo_subdir = catdir + os.sep
983 if not os.path.isdir(startdir + "/" + x):
985 for y in os.listdir(startdir + "/" + x):
986 if y == "CVS" or y.startswith("."):
988 if os.path.isdir(startdir + "/" + x + "/" + y):
989 scanlist.append(x + "/" + y)
992 catdir = reposplit[-2]
993 if catdir not in categories:
995 scanlist.append(catdir + "/" + reposplit[-1])
996 repo_subdir = scanlist[-1] + os.sep
998 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
999 ' from the current working directory'
1000 logging.critical(msg)
1003 repo_subdir_len = len(repo_subdir)
1006 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
1008 def vcs_files_to_cps(vcs_file_iter):
1010 Iterate over the given modified file paths returned from the vcs,
1011 and return a frozenset containing category/pn strings for each
1018 if reposplit[-2] in categories and \
1019 next(vcs_file_iter, None) is not None:
1020 modified_cps.append("/".join(reposplit[-2:]))
1022 elif repolevel == 2:
1023 category = reposplit[-1]
1024 if category in categories:
1025 for filename in vcs_file_iter:
1026 f_split = filename.split(os.sep)
1028 if len(f_split) > 2:
1029 modified_cps.append(category + "/" + f_split[1])
1033 for filename in vcs_file_iter:
1034 f_split = filename.split(os.sep)
1035 # ['.', category, pn, ...]
1036 if len(f_split) > 3 and f_split[1] in categories:
1037 modified_cps.append("/".join(f_split[1:3]))
1039 return frozenset(modified_cps)
1041 def git_supports_gpg_sign():
1042 status, cmd_output = \
1043 repoman_getstatusoutput("git --version")
1044 cmd_output = cmd_output.split()
1046 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1047 if version is not None:
1048 version = [int(x) for x in version.groups()]
1049 if version[0] > 1 or \
1050 (version[0] == 1 and version[1] > 7) or \
1051 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1055 def dev_keywords(profiles):
1057 Create a set of KEYWORDS values that exist in 'dev'
1058 profiles. These are used
1059 to trigger a message notifying the user when they might
1060 want to add the --include-dev option.
1063 for arch, arch_profiles in profiles.items():
1064 for prof in arch_profiles:
1065 arch_set = type_arch_map.get(prof.status)
1066 if arch_set is None:
1068 type_arch_map[prof.status] = arch_set
1071 dev_keywords = type_arch_map.get('dev', set())
1072 dev_keywords.update(['~' + arch for arch in dev_keywords])
1073 return frozenset(dev_keywords)
1075 dev_keywords = dev_keywords(profiles)
1084 xmllint_capable = False
1085 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1087 def fetch_metadata_dtd():
1089 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1090 metadata_dtd_ctime_interval.
1092 @return: True if successful, otherwise False
1096 metadata_dtd_st = None
1097 current_time = int(time.time())
1099 metadata_dtd_st = os.stat(metadata_dtd)
1100 except EnvironmentError as e:
1101 if e.errno not in (errno.ENOENT, errno.ESTALE):
1105 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1106 if abs(current_time - metadata_dtd_st.st_ctime) \
1107 < metadata_dtd_ctime_interval:
1112 print(green("***") + " the local copy of metadata.dtd " + \
1113 "needs to be refetched, doing that now")
1115 parsed_url = urlparse(metadata_dtd_uri)
1116 setting = 'FETCHCOMMAND_' + parsed_url.scheme.upper()
1117 fcmd = repoman_settings.get(setting)
1119 fcmd = repoman_settings.get('FETCHCOMMAND')
1121 logging.error("FETCHCOMMAND is unset")
1124 destdir = repoman_settings["DISTDIR"]
1125 fd, metadata_dtd_tmp = tempfile.mkstemp(
1126 prefix='metadata.dtd.', dir=destdir)
1130 if not portage.getbinpkg.file_get(metadata_dtd_uri,
1132 filename=os.path.basename(metadata_dtd_tmp)):
1133 logging.error("failed to fetch metadata.dtd from '%s'" %
1138 portage.util.apply_secpass_permissions(metadata_dtd_tmp,
1139 gid=portage.data.portage_gid, mode=0o664, mask=0o2)
1140 except portage.exception.PortageException:
1143 os.rename(metadata_dtd_tmp, metadata_dtd)
1146 os.unlink(metadata_dtd_tmp)
1152 if options.mode == "manifest":
1154 elif not find_binary('xmllint'):
1155 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1156 if options.xml_parse or repolevel == 3:
1157 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1160 if not fetch_metadata_dtd():
1162 # this can be problematic if xmllint changes their output
1163 xmllint_capable = True
1165 if options.mode == 'commit' and vcs:
1166 utilities.detect_vcs_conflicts(options, vcs)
1168 if options.mode == "manifest":
1170 elif options.pretend:
1171 print(green("\nRepoMan does a once-over of the neighborhood..."))
1173 print(green("\nRepoMan scours the neighborhood..."))
1176 modified_ebuilds = set()
1177 modified_changelogs = set()
1183 mycvstree = cvstree.getentries("./", recursive=1)
1184 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1185 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1186 if options.if_modified == "y":
1187 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1190 with repoman_popen("svn status") as f:
1191 svnstatus = f.readlines()
1192 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1193 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1194 if options.if_modified == "y":
1195 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1198 with repoman_popen("git diff-index --name-only "
1199 "--relative --diff-filter=M HEAD") as f:
1200 mychanged = f.readlines()
1201 mychanged = ["./" + elem[:-1] for elem in mychanged]
1203 with repoman_popen("git diff-index --name-only "
1204 "--relative --diff-filter=A HEAD") as f:
1205 mynew = f.readlines()
1206 mynew = ["./" + elem[:-1] for elem in mynew]
1207 if options.if_modified == "y":
1208 with repoman_popen("git diff-index --name-only "
1209 "--relative --diff-filter=D HEAD") as f:
1210 myremoved = f.readlines()
1211 myremoved = ["./" + elem[:-1] for elem in myremoved]
1214 with repoman_popen("bzr status -S .") as f:
1215 bzrstatus = f.readlines()
1216 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1217 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1218 if options.if_modified == "y":
1219 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")]
1222 with repoman_popen("hg status --no-status --modified .") as f:
1223 mychanged = f.readlines()
1224 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1225 with repoman_popen("hg status --no-status --added .") as f:
1226 mynew = f.readlines()
1227 mynew = ["./" + elem.rstrip() for elem in mynew]
1228 if options.if_modified == "y":
1229 with repoman_popen("hg status --no-status --removed .") as f:
1230 myremoved = f.readlines()
1231 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1234 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1235 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1236 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1237 if os.path.basename(x) == "ChangeLog")
1239 def vcs_new_changed(relative_path):
1240 for x in chain(mychanged, mynew):
1241 if x == relative_path:
1245 have_pmasked = False
1246 have_dev_keywords = False
1249 # NOTE: match-all caches are not shared due to potential
1250 # differences between profiles in _get_implicit_iuse.
1252 arch_xmatch_caches = {}
1253 shared_xmatch_caches = {"cp-list":{}}
1255 include_arches = None
1256 if options.include_arches:
1257 include_arches = set()
1258 include_arches.update(*[x.split() for x in options.include_arches])
1260 # Disable the "ebuild.notadded" check when not in commit mode and
1261 # running `svn status` in every package dir will be too expensive.
1263 check_ebuild_notadded = not \
1264 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1266 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1267 thirdpartymirrors = {}
1268 for k, v in repoman_settings.thirdpartymirrors().items():
1270 if not v.endswith("/"):
1272 thirdpartymirrors[v] = k
1274 class _XMLParser(xml.etree.ElementTree.XMLParser):
1276 def __init__(self, data, **kwargs):
1277 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1278 self._portage_data = data
1279 if hasattr(self, 'parser'):
1280 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1281 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1282 self._base_StartDoctypeDeclHandler = \
1283 self.parser.StartDoctypeDeclHandler
1284 self.parser.StartDoctypeDeclHandler = \
1285 self._portage_StartDoctypeDeclHandler
1287 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1288 if self._base_XmlDeclHandler is not None:
1289 self._base_XmlDeclHandler(version, encoding, standalone)
1290 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1292 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1293 has_internal_subset):
1294 if self._base_StartDoctypeDeclHandler is not None:
1295 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1296 has_internal_subset)
1297 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1299 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1301 Implements doctype() as required to avoid deprecation warnings with
1304 def doctype(self, name, pubid, system):
1308 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1309 except (EnvironmentError, ParseError, PermissionDenied) as e:
1311 except FileNotFound:
1312 # TODO: Download as we do for metadata.dtd, but add a way to
1313 # disable for non-gentoo repoman users who may not have herds.
1316 effective_scanlist = scanlist
1317 if options.if_modified == "y":
1318 effective_scanlist = sorted(vcs_files_to_cps(
1319 chain(mychanged, mynew, myremoved)))
1321 for x in effective_scanlist:
1322 # ebuilds and digests added to cvs respectively.
1323 logging.info("checking package %s" % x)
1324 # save memory by discarding xmatch caches from previous package(s)
1325 arch_xmatch_caches.clear()
1327 catdir, pkgdir = x.split("/")
1328 checkdir = repodir + "/" + x
1329 checkdir_relative = ""
1331 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1333 checkdir_relative = os.path.join(catdir, checkdir_relative)
1334 checkdir_relative = os.path.join(".", checkdir_relative)
1335 generated_manifest = False
1337 if options.mode == "manifest" or \
1338 (options.mode != 'manifest-check' and options.digest == 'y') or \
1339 options.mode in ('commit', 'fix') and not options.pretend:
1340 auto_assumed = set()
1341 fetchlist_dict = portage.FetchlistDict(checkdir,
1342 repoman_settings, portdb)
1343 if options.mode == 'manifest' and options.force:
1344 portage._doebuild_manifest_exempt_depend += 1
1346 distdir = repoman_settings['DISTDIR']
1347 mf = repoman_settings.repositories.get_repo_for_location(
1348 os.path.dirname(os.path.dirname(checkdir)))
1349 mf = mf.load_manifest(checkdir, distdir,
1350 fetchlist_dict=fetchlist_dict)
1351 mf.create(requiredDistfiles=None,
1352 assumeDistHashesAlways=True)
1353 for distfiles in fetchlist_dict.values():
1354 for distfile in distfiles:
1355 if os.path.isfile(os.path.join(distdir, distfile)):
1356 mf.fhashdict['DIST'].pop(distfile, None)
1358 auto_assumed.add(distfile)
1361 portage._doebuild_manifest_exempt_depend -= 1
1363 repoman_settings["O"] = checkdir
1365 generated_manifest = digestgen(
1366 mysettings=repoman_settings, myportdb=portdb)
1367 except portage.exception.PermissionDenied as e:
1368 generated_manifest = False
1369 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1370 level=logging.ERROR, noiselevel=-1)
1372 if not generated_manifest:
1373 print("Unable to generate manifest.")
1376 if options.mode == "manifest":
1377 if not dofail and options.force and auto_assumed and \
1378 'assume-digests' in repoman_settings.features:
1379 # Show which digests were assumed despite the --force option
1380 # being given. This output will already have been shown by
1381 # digestgen() if assume-digests is not enabled, so only show
1382 # it here if assume-digests is enabled.
1383 pkgs = list(fetchlist_dict)
1385 portage.writemsg_stdout(" digest.assumed" + \
1386 portage.output.colorize("WARN",
1387 str(len(auto_assumed)).rjust(18)) + "\n")
1389 fetchmap = fetchlist_dict[cpv]
1390 pf = portage.catsplit(cpv)[1]
1391 for distfile in sorted(fetchmap):
1392 if distfile in auto_assumed:
1393 portage.writemsg_stdout(
1394 " %s::%s\n" % (pf, distfile))
1399 if not generated_manifest:
1400 repoman_settings['O'] = checkdir
1401 repoman_settings['PORTAGE_QUIET'] = '1'
1402 if not portage.digestcheck([], repoman_settings, strict=1):
1403 stats["manifest.bad"] += 1
1404 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1405 repoman_settings.pop('PORTAGE_QUIET', None)
1407 if options.mode == 'manifest-check':
1410 checkdirlist = os.listdir(checkdir)
1414 for y in checkdirlist:
1415 if (y in no_exec or y.endswith(".ebuild")) and \
1416 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1417 stats["file.executable"] += 1
1418 fails["file.executable"].append(os.path.join(checkdir, y))
1419 if y.endswith(".ebuild"):
1421 ebuildlist.append(pf)
1422 cpv = "%s/%s" % (catdir, pf)
1424 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1427 stats["ebuild.syntax"] += 1
1428 fails["ebuild.syntax"].append(os.path.join(x, y))
1432 stats["ebuild.output"] += 1
1433 fails["ebuild.output"].append(os.path.join(x, y))
1435 if not portage.eapi_is_supported(myaux["EAPI"]):
1437 stats["EAPI.unsupported"] += 1
1438 fails["EAPI.unsupported"].append(os.path.join(x, y))
1440 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1441 root_config=root_config, type_name="ebuild")
1445 if len(pkgs) != len(ebuildlist):
1446 # If we can't access all the metadata then it's totally unsafe to
1447 # commit since there's no way to generate a correct Manifest.
1448 # Do not try to do any more QA checks on this package since missing
1449 # metadata leads to false positives for several checks, and false
1450 # positives confuse users.
1454 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1455 ebuildlist = sorted(pkgs.values())
1456 ebuildlist = [pkg.pf for pkg in ebuildlist]
1458 for y in checkdirlist:
1459 index = repo_config.find_invalid_path_char(y)
1461 y_relative = os.path.join(checkdir_relative, y)
1462 if vcs is not None and not vcs_new_changed(y_relative):
1463 # If the file isn't in the VCS new or changed set, then
1464 # assume that it's an irrelevant temporary file (Manifest
1465 # entries are not generated for file names containing
1466 # prohibited characters). See bug #406877.
1469 stats["file.name"] += 1
1470 fails["file.name"].append("%s/%s: char '%s'" % \
1471 (checkdir, y, y[index]))
1473 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1478 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1479 encoding=_encodings['fs'], errors='strict'),
1480 mode='r', encoding=_encodings['repo.content'])
1483 except UnicodeDecodeError as ue:
1484 stats["file.UTF8"] += 1
1485 s = ue.object[:ue.start]
1489 s = s[s.rfind("\n") + 1:]
1490 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1495 if vcs in ("git", "hg") and check_ebuild_notadded:
1497 myf = repoman_popen("git ls-files --others %s" % \
1498 (portage._shell_quote(checkdir_relative),))
1500 myf = repoman_popen("hg status --no-status --unknown %s" % \
1501 (portage._shell_quote(checkdir_relative),))
1503 if l[:-1][-7:] == ".ebuild":
1504 stats["ebuild.notadded"] += 1
1505 fails["ebuild.notadded"].append(
1506 os.path.join(x, os.path.basename(l[:-1])))
1509 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1512 myf = open(checkdir + "/CVS/Entries", "r")
1514 myf = repoman_popen("svn status --depth=files --verbose " +
1515 portage._shell_quote(checkdir))
1517 myf = repoman_popen("bzr ls -v --kind=file " +
1518 portage._shell_quote(checkdir))
1519 myl = myf.readlines()
1525 splitl = l[1:].split("/")
1528 if splitl[0][-7:] == ".ebuild":
1529 eadded.append(splitl[0][:-7])
1534 # tree conflict, new in subversion 1.6
1537 if l[-7:] == ".ebuild":
1538 eadded.append(os.path.basename(l[:-7]))
1543 if l[-7:] == ".ebuild":
1544 eadded.append(os.path.basename(l[:-7]))
1546 myf = repoman_popen("svn status " +
1547 portage._shell_quote(checkdir))
1548 myl = myf.readlines()
1552 l = l.rstrip().split(' ')[-1]
1553 if l[-7:] == ".ebuild":
1554 eadded.append(os.path.basename(l[:-7]))
1557 stats["CVS/Entries.IO_error"] += 1
1558 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1563 mf = repoman_settings.repositories.get_repo_for_location(
1564 os.path.dirname(os.path.dirname(checkdir)))
1565 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1566 mydigests = mf.getTypeDigests("DIST")
1568 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1570 src_uri_error = False
1571 for mykey in fetchlist_dict:
1573 myfiles_all.extend(fetchlist_dict[mykey])
1574 except portage.exception.InvalidDependString as e:
1575 src_uri_error = True
1577 portdb.aux_get(mykey, ["SRC_URI"])
1579 # This will be reported as an "ebuild.syntax" error.
1582 stats["SRC_URI.syntax"] += 1
1583 fails["SRC_URI.syntax"].append(
1584 "%s.ebuild SRC_URI: %s" % (mykey, e))
1586 if not src_uri_error:
1587 # This test can produce false positives if SRC_URI could not
1588 # be parsed for one or more ebuilds. There's no point in
1589 # producing a false error here since the root cause will
1590 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1591 # or "ebuild.sytax".
1592 myfiles_all = set(myfiles_all)
1593 for entry in mydigests:
1594 if entry not in myfiles_all:
1595 stats["digest.unused"] += 1
1596 fails["digest.unused"].append(checkdir + "::" + entry)
1597 for entry in myfiles_all:
1598 if entry not in mydigests:
1599 stats["digest.missing"] += 1
1600 fails["digest.missing"].append(checkdir + "::" + entry)
1603 if os.path.exists(checkdir + "/files"):
1604 filesdirlist = os.listdir(checkdir + "/files")
1606 # recurse through files directory
1607 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1609 y = filesdirlist.pop(0)
1610 relative_path = os.path.join(x, "files", y)
1611 full_path = os.path.join(repodir, relative_path)
1613 mystat = os.stat(full_path)
1614 except OSError as oe:
1616 # don't worry about it. it likely was removed via fix above.
1620 if S_ISDIR(mystat.st_mode):
1621 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1622 if y == "CVS" or y == ".svn":
1624 for z in os.listdir(checkdir + "/files/" + y):
1625 if z == "CVS" or z == ".svn":
1627 filesdirlist.append(y + "/" + z)
1628 # Current policy is no files over 20 KiB, these are the checks. File size between
1629 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1630 elif mystat.st_size > 61440:
1631 stats["file.size.fatal"] += 1
1632 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1633 elif mystat.st_size > 20480:
1634 stats["file.size"] += 1
1635 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1637 index = repo_config.find_invalid_path_char(y)
1639 y_relative = os.path.join(checkdir_relative, "files", y)
1640 if vcs is not None and not vcs_new_changed(y_relative):
1641 # If the file isn't in the VCS new or changed set, then
1642 # assume that it's an irrelevant temporary file (Manifest
1643 # entries are not generated for file names containing
1644 # prohibited characters). See bug #406877.
1647 stats["file.name"] += 1
1648 fails["file.name"].append("%s/files/%s: char '%s'" % \
1649 (checkdir, y, y[index]))
1652 if check_changelog and "ChangeLog" not in checkdirlist:
1653 stats["changelog.missing"] += 1
1654 fails["changelog.missing"].append(x + "/ChangeLog")
1657 # metadata.xml file check
1658 if "metadata.xml" not in checkdirlist:
1659 stats["metadata.missing"] += 1
1660 fails["metadata.missing"].append(x + "/metadata.xml")
1661 # metadata.xml parse check
1663 metadata_bad = False
1665 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1667 # read metadata.xml into memory
1669 _metadata_xml = xml.etree.ElementTree.parse(
1670 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1671 encoding=_encodings['fs'], errors='strict'),
1673 except (ExpatError, SyntaxError, EnvironmentError) as e:
1675 stats["metadata.bad"] += 1
1676 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1679 if not hasattr(xml_parser, 'parser') or \
1680 sys.hexversion < 0x2070000 or \
1681 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1682 # doctype is not parsed with python 2.6 or 3.1
1685 if "XML_DECLARATION" not in xml_info:
1686 stats["metadata.bad"] += 1
1687 fails["metadata.bad"].append("%s/metadata.xml: "
1688 "xml declaration is missing on first line, "
1689 "should be '%s'" % (x, metadata_xml_declaration))
1691 xml_version, xml_encoding, xml_standalone = \
1692 xml_info["XML_DECLARATION"]
1693 if xml_encoding is None or \
1694 xml_encoding.upper() != metadata_xml_encoding:
1695 stats["metadata.bad"] += 1
1696 if xml_encoding is None:
1697 encoding_problem = "but it is undefined"
1699 encoding_problem = "not '%s'" % xml_encoding
1700 fails["metadata.bad"].append("%s/metadata.xml: "
1701 "xml declaration encoding should be '%s', %s" %
1702 (x, metadata_xml_encoding, encoding_problem))
1704 if "DOCTYPE" not in xml_info:
1706 stats["metadata.bad"] += 1
1707 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1708 "DOCTYPE is missing"))
1710 doctype_name, doctype_system, doctype_pubid = \
1712 if doctype_system != metadata_dtd_uri:
1713 stats["metadata.bad"] += 1
1714 if doctype_system is None:
1715 system_problem = "but it is undefined"
1717 system_problem = "not '%s'" % doctype_system
1718 fails["metadata.bad"].append("%s/metadata.xml: "
1719 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1720 (x, metadata_dtd_uri, system_problem))
1722 if doctype_name != metadata_doctype_name:
1723 stats["metadata.bad"] += 1
1724 fails["metadata.bad"].append("%s/metadata.xml: "
1725 "DOCTYPE: name should be '%s', not '%s'" %
1726 (x, metadata_doctype_name, doctype_name))
1728 # load USE flags from metadata.xml
1730 musedict = utilities.parse_metadata_use(_metadata_xml)
1731 except portage.exception.ParseError as e:
1733 stats["metadata.bad"] += 1
1734 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1736 for atom in chain(*musedict.values()):
1741 except InvalidAtom as e:
1742 stats["metadata.bad"] += 1
1743 fails["metadata.bad"].append(
1744 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1747 stats["metadata.bad"] += 1
1748 fails["metadata.bad"].append(
1749 ("%s/metadata.xml: Atom contains "
1750 "unexpected cat/pn: %s") % (x, atom))
1752 # Run other metadata.xml checkers
1754 utilities.check_metadata(_metadata_xml, herd_base)
1755 except (utilities.UnknownHerdsError, ) as e:
1757 stats["metadata.bad"] += 1
1758 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1761 # Only carry out if in package directory or check forced
1762 if xmllint_capable and not metadata_bad:
1763 # xmlint can produce garbage output even on success, so only dump
1764 # the ouput when it fails.
1765 st, out = repoman_getstatusoutput(
1766 "xmllint --nonet --noout --dtdvalid %s %s" % \
1767 (portage._shell_quote(metadata_dtd),
1768 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1770 print(red("!!!") + " metadata.xml is invalid:")
1771 for z in out.splitlines():
1772 print(red("!!! ") + z)
1773 stats["metadata.bad"] += 1
1774 fails["metadata.bad"].append(x + "/metadata.xml")
1777 muselist = frozenset(musedict)
1779 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1780 changelog_modified = changelog_path in modified_changelogs
1782 # detect unused local USE-descriptions
1783 used_useflags = set()
1785 for y in ebuildlist:
1786 relative_path = os.path.join(x, y + ".ebuild")
1787 full_path = os.path.join(repodir, relative_path)
1788 ebuild_path = y + ".ebuild"
1790 ebuild_path = os.path.join(pkgdir, ebuild_path)
1792 ebuild_path = os.path.join(catdir, ebuild_path)
1793 ebuild_path = os.path.join(".", ebuild_path)
1794 if check_changelog and not changelog_modified \
1795 and ebuild_path in new_ebuilds:
1796 stats['changelog.ebuildadded'] += 1
1797 fails['changelog.ebuildadded'].append(relative_path)
1799 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1800 # ebuild not added to vcs
1801 stats["ebuild.notadded"] += 1
1802 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1803 myesplit = portage.pkgsplit(y)
1804 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1805 or pv_toolong_re.search(myesplit[1]) \
1806 or pv_toolong_re.search(myesplit[2]):
1807 stats["ebuild.invalidname"] += 1
1808 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1810 elif myesplit[0] != pkgdir:
1811 print(pkgdir, myesplit[0])
1812 stats["ebuild.namenomatch"] += 1
1813 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1820 for k, msgs in pkg.invalid.items():
1823 fails[k].append("%s: %s" % (relative_path, msg))
1826 myaux = pkg._metadata
1827 eapi = myaux["EAPI"]
1828 inherited = pkg.inherited
1829 live_ebuild = live_eclasses.intersection(inherited)
1831 if repo_config.eapi_is_banned(eapi):
1832 stats["repo.eapi.banned"] += 1
1833 fails["repo.eapi.banned"].append(
1834 "%s: %s" % (relative_path, eapi))
1836 elif repo_config.eapi_is_deprecated(eapi):
1837 stats["repo.eapi.deprecated"] += 1
1838 fails["repo.eapi.deprecated"].append(
1839 "%s: %s" % (relative_path, eapi))
1841 for k, v in myaux.items():
1842 if not isinstance(v, basestring):
1844 m = non_ascii_re.search(v)
1846 stats["variable.invalidchar"] += 1
1847 fails["variable.invalidchar"].append(
1848 ("%s: %s variable contains non-ASCII " + \
1849 "character at position %s") % \
1850 (relative_path, k, m.start() + 1))
1852 if not src_uri_error:
1853 # Check that URIs don't reference a server from thirdpartymirrors.
1854 for uri in portage.dep.use_reduce( \
1855 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1856 contains_mirror = False
1857 for mirror, mirror_alias in thirdpartymirrors.items():
1858 if uri.startswith(mirror):
1859 contains_mirror = True
1861 if not contains_mirror:
1864 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1865 stats["SRC_URI.mirror"] += 1
1866 fails["SRC_URI.mirror"].append(
1867 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1868 (relative_path, mirror, new_uri))
1870 if myaux.get("PROVIDE"):
1871 stats["virtual.oldstyle"] += 1
1872 fails["virtual.oldstyle"].append(relative_path)
1874 for pos, missing_var in enumerate(missingvars):
1875 if not myaux.get(missing_var):
1876 if catdir == "virtual" and \
1877 missing_var in ("HOMEPAGE", "LICENSE"):
1879 if live_ebuild and missing_var == "KEYWORDS":
1881 myqakey = missingvars[pos] + ".missing"
1883 fails[myqakey].append(x + "/" + y + ".ebuild")
1885 if catdir == "virtual":
1886 for var in ("HOMEPAGE", "LICENSE"):
1888 myqakey = var + ".virtual"
1890 fails[myqakey].append(relative_path)
1892 # 14 is the length of DESCRIPTION=""
1893 if len(myaux['DESCRIPTION']) > max_desc_len:
1894 stats['DESCRIPTION.toolong'] += 1
1895 fails['DESCRIPTION.toolong'].append(
1896 "%s: DESCRIPTION is %d characters (max %d)" % \
1897 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1899 keywords = myaux["KEYWORDS"].split()
1900 stable_keywords = []
1901 for keyword in keywords:
1902 if not keyword.startswith("~") and \
1903 not keyword.startswith("-"):
1904 stable_keywords.append(keyword)
1906 if ebuild_path in new_ebuilds and catdir != "virtual":
1907 stable_keywords.sort()
1908 stats["KEYWORDS.stable"] += 1
1909 fails["KEYWORDS.stable"].append(
1910 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1911 " ".join(stable_keywords))
1913 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1914 if not kw.startswith("-"))
1916 previous_keywords = slot_keywords.get(pkg.slot)
1917 if previous_keywords is None:
1918 slot_keywords[pkg.slot] = set()
1919 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1920 dropped_keywords = previous_keywords.difference(ebuild_archs)
1921 if dropped_keywords:
1922 stats["KEYWORDS.dropped"] += 1
1923 fails["KEYWORDS.dropped"].append(
1924 relative_path + ": %s" % \
1925 " ".join(sorted(dropped_keywords)))
1927 slot_keywords[pkg.slot].update(ebuild_archs)
1929 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1930 if "-*" in keywords:
1938 stats["KEYWORDS.stupid"] += 1
1939 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1942 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1943 not be allowed to be marked stable
1945 if live_ebuild and repo_config.name == "gentoo":
1946 bad_stable_keywords = []
1947 for keyword in keywords:
1948 if not keyword.startswith("~") and \
1949 not keyword.startswith("-"):
1950 bad_stable_keywords.append(keyword)
1952 if bad_stable_keywords:
1953 stats["LIVEVCS.stable"] += 1
1954 fails["LIVEVCS.stable"].append(
1955 x + "/" + y + ".ebuild with stable keywords:%s " % \
1956 bad_stable_keywords)
1957 del bad_stable_keywords
1959 if keywords and not has_global_mask(pkg):
1960 stats["LIVEVCS.unmasked"] += 1
1961 fails["LIVEVCS.unmasked"].append(relative_path)
1963 if options.ignore_arches:
1964 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1965 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1968 for keyword in keywords:
1969 if keyword[0] == "-":
1971 elif keyword[0] == "~":
1974 for expanded_arch in profiles:
1975 if expanded_arch == "**":
1977 arches.add((keyword, expanded_arch,
1978 (expanded_arch, "~" + expanded_arch)))
1980 arches.add((keyword, arch, (arch, keyword)))
1983 for expanded_arch in profiles:
1984 if expanded_arch == "**":
1986 arches.add((keyword, expanded_arch,
1989 arches.add((keyword, keyword, (keyword,)))
1991 # Use an empty profile for checking dependencies of
1992 # packages that have empty KEYWORDS.
1993 arches.add(('**', '**', ('**',)))
1995 unknown_pkgs = set()
1996 baddepsyntax = False
1997 badlicsyntax = False
1998 badprovsyntax = False
1999 catpkg = catdir + "/" + y
2001 inherited_java_eclass = "java-pkg-2" in inherited or \
2002 "java-pkg-opt-2" in inherited
2003 inherited_wxwidgets_eclass = "wxwidgets" in inherited
2004 operator_tokens = set(["||", "(", ")"])
2005 type_list, badsyntax = [], []
2006 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
2007 mydepstr = myaux[mytype]
2009 buildtime = mytype in Package._buildtime_keys
2010 runtime = mytype in Package._runtime_keys
2012 if mytype.endswith("DEPEND"):
2013 token_class = portage.dep.Atom
2016 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
2017 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
2018 except portage.exception.InvalidDependString as e:
2020 badsyntax.append(str(e))
2022 if atoms and mytype.endswith("DEPEND"):
2024 "test?" in mydepstr.split():
2025 stats[mytype + '.suspect'] += 1
2026 fails[mytype + '.suspect'].append(relative_path + \
2027 ": 'test?' USE conditional in %s" % mytype)
2033 # Skip dependency.unknown for blockers, so that we
2034 # don't encourage people to remove necessary blockers,
2035 # as discussed in bug #382407.
2036 if atom.blocker is None and \
2037 not portdb.xmatch("match-all", atom) and \
2038 not atom.cp.startswith("virtual/"):
2039 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2041 is_blocker = atom.blocker
2043 if catdir != "virtual":
2044 if not is_blocker and \
2045 atom.cp in suspect_virtual:
2046 stats['virtual.suspect'] += 1
2047 fails['virtual.suspect'].append(
2049 ": %s: consider using '%s' instead of '%s'" %
2050 (mytype, suspect_virtual[atom.cp], atom))
2053 not is_blocker and \
2054 not inherited_java_eclass and \
2055 atom.cp == "virtual/jdk":
2056 stats['java.eclassesnotused'] += 1
2057 fails['java.eclassesnotused'].append(relative_path)
2058 elif buildtime and \
2059 not is_blocker and \
2060 not inherited_wxwidgets_eclass and \
2061 atom.cp == "x11-libs/wxGTK":
2062 stats['wxwidgets.eclassnotused'] += 1
2063 fails['wxwidgets.eclassnotused'].append(
2064 (relative_path + ": %ss on x11-libs/wxGTK"
2065 " without inheriting wxwidgets.eclass") % mytype)
2067 if not is_blocker and \
2068 atom.cp in suspect_rdepend:
2069 stats[mytype + '.suspect'] += 1
2070 fails[mytype + '.suspect'].append(
2071 relative_path + ": '%s'" % atom)
2073 if atom.operator == "~" and \
2074 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2075 qacat = 'dependency.badtilde'
2077 fails[qacat].append(
2078 (relative_path + ": %s uses the ~ operator"
2079 " with a non-zero revision:" + \
2080 " '%s'") % (mytype, atom))
2082 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2084 for m, b in zip(type_list, badsyntax):
2085 if m.endswith("DEPEND"):
2086 qacat = "dependency.syntax"
2088 qacat = m + ".syntax"
2090 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2092 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2093 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2094 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2095 badlicsyntax = badlicsyntax > 0
2096 badprovsyntax = badprovsyntax > 0
2098 # uselist checks - global
2101 for myflag in myaux["IUSE"].split():
2102 flag_name = myflag.lstrip("+-")
2103 used_useflags.add(flag_name)
2104 if myflag != flag_name:
2105 default_use.append(myflag)
2106 if flag_name not in uselist:
2107 myuse.append(flag_name)
2109 # uselist checks - metadata
2110 for mypos in range(len(myuse)-1, -1, -1):
2111 if myuse[mypos] and (myuse[mypos] in muselist):
2114 if default_use and not eapi_has_iuse_defaults(eapi):
2115 for myflag in default_use:
2116 stats['EAPI.incompatible'] += 1
2117 fails['EAPI.incompatible'].append(
2118 (relative_path + ": IUSE defaults" + \
2119 " not supported with EAPI='%s':" + \
2120 " '%s'") % (eapi, myflag))
2122 for mypos in range(len(myuse)):
2123 stats["IUSE.invalid"] += 1
2124 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2126 # Check for outdated RUBY targets
2127 if "ruby-ng" in inherited or "ruby-fakegem" in inherited or "ruby" in inherited:
2128 ruby_intersection = pkg.iuse.all.intersection(ruby_deprecated)
2129 if ruby_intersection:
2130 for myruby in ruby_intersection:
2131 stats["IUSE.rubydeprecated"] += 1
2132 fails["IUSE.rubydeprecated"].append(
2133 (relative_path + ": Deprecated ruby target: %s") % myruby)
2136 if not badlicsyntax:
2137 # Parse the LICENSE variable, remove USE conditions and
2139 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2140 # Check each entry to ensure that it exists in PORTDIR's
2141 # license directory.
2142 for lic in licenses:
2143 # Need to check for "||" manually as no portage
2144 # function will remove it without removing values.
2145 if lic not in liclist and lic != "||":
2146 stats["LICENSE.invalid"] += 1
2147 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2148 elif lic in liclist_deprecated:
2149 stats["LICENSE.deprecated"] += 1
2150 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2153 myuse = myaux["KEYWORDS"].split()
2155 if mykey not in ("-*", "*", "~*"):
2157 if myskey[:1] == "-":
2159 if myskey[:1] == "~":
2161 if myskey not in kwlist:
2162 stats["KEYWORDS.invalid"] += 1
2163 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2164 elif myskey not in profiles:
2165 stats["KEYWORDS.invalid"] += 1
2166 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2171 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2172 except portage.exception.InvalidDependString as e:
2173 stats["RESTRICT.syntax"] += 1
2174 fails["RESTRICT.syntax"].append(
2175 "%s: RESTRICT: %s" % (relative_path, e))
2178 myrestrict = set(myrestrict)
2179 mybadrestrict = myrestrict.difference(valid_restrict)
2181 stats["RESTRICT.invalid"] += len(mybadrestrict)
2182 for mybad in mybadrestrict:
2183 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2184 # REQUIRED_USE check
2185 required_use = myaux["REQUIRED_USE"]
2187 if not eapi_has_required_use(eapi):
2188 stats['EAPI.incompatible'] += 1
2189 fails['EAPI.incompatible'].append(
2190 relative_path + ": REQUIRED_USE" + \
2191 " not supported with EAPI='%s'" % (eapi,))
2193 portage.dep.check_required_use(required_use, (),
2194 pkg.iuse.is_valid_flag, eapi=eapi)
2195 except portage.exception.InvalidDependString as e:
2196 stats["REQUIRED_USE.syntax"] += 1
2197 fails["REQUIRED_USE.syntax"].append(
2198 "%s: REQUIRED_USE: %s" % (relative_path, e))
2202 relative_path = os.path.join(x, y + ".ebuild")
2203 full_path = os.path.join(repodir, relative_path)
2204 if not vcs_preserves_mtime:
2205 if ebuild_path not in new_ebuilds and \
2206 ebuild_path not in modified_ebuilds:
2209 # All ebuilds should have utf_8 encoding.
2210 f = io.open(_unicode_encode(full_path,
2211 encoding=_encodings['fs'], errors='strict'),
2212 mode='r', encoding=_encodings['repo.content'])
2214 for check_name, e in run_checks(f, pkg):
2215 stats[check_name] += 1
2216 fails[check_name].append(relative_path + ': %s' % e)
2219 except UnicodeDecodeError:
2220 # A file.UTF8 failure will have already been recorded above.
2224 # The dep_check() calls are the most expensive QA test. If --force
2225 # is enabled, there's no point in wasting time on these since the
2226 # user is intent on forcing the commit anyway.
2229 relevant_profiles = []
2230 for keyword, arch, groups in arches:
2231 if arch not in profiles:
2232 # A missing profile will create an error further down
2233 # during the KEYWORDS verification.
2236 if include_arches is not None:
2237 if arch not in include_arches:
2240 relevant_profiles.extend((keyword, groups, prof)
2241 for prof in profiles[arch])
2244 return item[2].sub_path
2246 relevant_profiles.sort(key=sort_key)
2248 for keyword, groups, prof in relevant_profiles:
2250 if not (prof.status == "stable" or \
2251 (prof.status == "dev" and options.include_dev) or \
2252 (prof.status == "exp" and options.include_exp_profiles == 'y')):
2255 dep_settings = arch_caches.get(prof.sub_path)
2256 if dep_settings is None:
2257 dep_settings = portage.config(
2258 config_profile_path=prof.abs_path,
2259 config_incrementals=repoman_incrementals,
2260 config_root=config_root,
2262 _unmatched_removal=options.unmatched_removal,
2263 env=env, repositories=repoman_settings.repositories)
2264 dep_settings.categories = repoman_settings.categories
2265 if options.without_mask:
2266 dep_settings._mask_manager_obj = \
2267 copy.deepcopy(dep_settings._mask_manager)
2268 dep_settings._mask_manager._pmaskdict.clear()
2269 arch_caches[prof.sub_path] = dep_settings
2271 xmatch_cache_key = (prof.sub_path, tuple(groups))
2272 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2276 xcache = portdb.xcache
2277 xcache.update(shared_xmatch_caches)
2278 arch_xmatch_caches[xmatch_cache_key] = xcache
2280 trees[root]["porttree"].settings = dep_settings
2281 portdb.settings = dep_settings
2282 portdb.xcache = xcache
2284 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2285 # just in case, prevent config.reset() from nuking these.
2286 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2288 # This attribute is used in dbapi._match_use() to apply
2289 # use.stable.{mask,force} settings based on the stable
2290 # status of the parent package. This is required in order
2291 # for USE deps of unstable packages to be resolved correctly,
2292 # since otherwise use.stable.{mask,force} settings of
2293 # dependencies may conflict (see bug #456342).
2294 dep_settings._parent_stable = dep_settings._isStable(pkg)
2296 # Handle package.use*.{force,mask) calculation, for use
2298 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2299 pkg, stable=dep_settings._parent_stable)
2300 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2301 pkg, stable=dep_settings._parent_stable)
2303 if not baddepsyntax:
2304 ismasked = not ebuild_archs or \
2305 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2307 if not have_pmasked:
2308 have_pmasked = bool(dep_settings._getMaskAtom(
2309 pkg.cpv, pkg._metadata))
2310 if options.ignore_masked:
2312 # we are testing deps for a masked package; give it some lee-way
2314 matchmode = "minimum-all"
2317 matchmode = "minimum-visible"
2319 if not have_dev_keywords:
2320 have_dev_keywords = \
2321 bool(dev_keywords.intersection(keywords))
2323 if prof.status == "dev":
2324 suffix = suffix + "indev"
2326 for mytype in Package._dep_keys:
2328 mykey = "dependency.bad" + suffix
2329 myvalue = myaux[mytype]
2333 success, atoms = portage.dep_check(myvalue, portdb,
2334 dep_settings, use="all", mode=matchmode,
2340 # Don't bother with dependency.unknown for
2341 # cases in which *DEPEND.bad is triggered.
2343 # dep_check returns all blockers and they
2344 # aren't counted for *DEPEND.bad, so we
2346 if not atom.blocker:
2347 unknown_pkgs.discard(
2348 (mytype, atom.unevaluated_atom))
2350 if not prof.sub_path:
2351 # old-style virtuals currently aren't
2352 # resolvable with empty profile, since
2353 # 'virtuals' mappings are unavailable
2354 # (it would be expensive to search
2355 # for PROVIDE in all ebuilds)
2356 atoms = [atom for atom in atoms if not \
2357 (atom.cp.startswith('virtual/') and \
2358 not portdb.cp_list(atom.cp))]
2360 # we have some unsolvable deps
2361 # remove ! deps, which always show up as unsatisfiable
2362 atoms = [str(atom.unevaluated_atom) \
2363 for atom in atoms if not atom.blocker]
2365 # if we emptied out our list, continue:
2369 fails[mykey].append("%s: %s: %s(%s) %s" % \
2370 (relative_path, mytype, keyword,
2374 fails[mykey].append("%s: %s: %s(%s) %s" % \
2375 (relative_path, mytype, keyword,
2378 if not baddepsyntax and unknown_pkgs:
2380 for mytype, atom in unknown_pkgs:
2381 type_map.setdefault(mytype, set()).add(atom)
2382 for mytype, atoms in type_map.items():
2383 stats["dependency.unknown"] += 1
2384 fails["dependency.unknown"].append("%s: %s: %s" %
2385 (relative_path, mytype, ", ".join(sorted(atoms))))
2387 # check if there are unused local USE-descriptions in metadata.xml
2388 # (unless there are any invalids, to avoid noise)
2390 for myflag in muselist.difference(used_useflags):
2391 stats["metadata.warning"] += 1
2392 fails["metadata.warning"].append(
2393 "%s/metadata.xml: unused local USE-description: '%s'" % \
2396 if options.if_modified == "y" and len(effective_scanlist) < 1:
2397 logging.warn("--if-modified is enabled, but no modified packages were found!")
2399 if options.mode == "manifest":
2402 # dofail will be set to 1 if we have failed in at least one non-warning category
2404 # dowarn will be set to 1 if we tripped any warnings
2406 # dofull will be set if we should print a "repoman full" informational message
2407 dofull = options.mode != 'full'
2413 if x not in qawarnings:
2417 (dowarn and not (options.quiet or options.mode == "scan")):
2420 # Save QA output so that it can be conveniently displayed
2421 # in $EDITOR while the user creates a commit message.
2422 # Otherwise, the user would not be able to see this output
2423 # once the editor has taken over the screen.
2424 qa_output = io.StringIO()
2425 style_file = ConsoleStyleFile(sys.stdout)
2426 if options.mode == 'commit' and \
2427 (not commitmessage or not commitmessage.strip()):
2428 style_file.write_listener = qa_output
2429 console_writer = StyleWriter(file=style_file, maxcol=9999)
2430 console_writer.style_listener = style_file.new_styles
2432 f = formatter.AbstractFormatter(console_writer)
2434 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2437 del console_writer, f, style_file
2438 qa_output = qa_output.getvalue()
2439 qa_output = qa_output.splitlines(True)
2441 suggest_ignore_masked = False
2442 suggest_include_dev = False
2444 if have_pmasked and not (options.without_mask or options.ignore_masked):
2445 suggest_ignore_masked = True
2446 if have_dev_keywords and not options.include_dev:
2447 suggest_include_dev = True
2449 if suggest_ignore_masked or suggest_include_dev:
2451 if suggest_ignore_masked:
2452 print(bold("Note: use --without-mask to check " + \
2453 "KEYWORDS on dependencies of masked packages"))
2455 if suggest_include_dev:
2456 print(bold("Note: use --include-dev (-d) to check " + \
2457 "dependencies for 'dev' profiles"))
2460 if options.mode != 'commit':
2462 print(bold("Note: type \"repoman full\" for a complete listing."))
2463 if dowarn and not dofail:
2464 print(green("RepoMan sez:"),"\"You're only giving me a partial QA payment?\n I'll take it this time, but I'm not happy.\"")
2466 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2468 print(bad("Please fix these important QA issues first."))
2469 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2472 if dofail and can_force and options.force and not options.pretend:
2473 print(green("RepoMan sez:") + \
2474 " \"You want to commit even with these QA issues?\n" + \
2475 " I'll take it this time, but I'm not happy.\"\n")
2477 if options.force and not can_force:
2478 print(bad("The --force option has been disabled due to extraordinary issues."))
2479 print(bad("Please fix these important QA issues first."))
2480 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2484 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2489 myvcstree = portage.cvstree.getentries("./", recursive=1)
2490 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2491 except SystemExit as e:
2492 raise # TODO propagate this
2494 err("Error retrieving CVS tree; exiting.")
2497 with repoman_popen("svn status --no-ignore") as f:
2498 svnstatus = f.readlines()
2499 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2500 except SystemExit as e:
2501 raise # TODO propagate this
2503 err("Error retrieving SVN info; exiting.")
2505 # get list of files not under version control or missing
2506 myf = repoman_popen("git ls-files --others")
2507 myunadded = ["./" + elem[:-1] for elem in myf]
2511 with repoman_popen("bzr status -S .") as f:
2512 bzrstatus = f.readlines()
2513 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2514 except SystemExit as e:
2515 raise # TODO propagate this
2517 err("Error retrieving bzr info; exiting.")
2519 with repoman_popen("hg status --no-status --unknown .") as f:
2520 myunadded = f.readlines()
2521 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2523 # Mercurial doesn't handle manually deleted files as removed from
2524 # the repository, so the user need to remove them before commit,
2525 # using "hg remove [FILES]"
2526 with repoman_popen("hg status --no-status --deleted .") as f:
2527 mydeleted = f.readlines()
2528 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2533 for x in range(len(myunadded)-1, -1, -1):
2534 xs = myunadded[x].split("/")
2535 if xs[-1] == "files":
2536 print("!!! files dir is not added! Please correct this.")
2538 elif xs[-1] == "Manifest":
2539 # It's a manifest... auto add
2540 myautoadd += [myunadded[x]]
2544 print(red("!!! The following files are in your local tree but are not added to the master"))
2545 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2552 if vcs == "hg" and mydeleted:
2553 print(red("!!! The following files are removed manually from your local tree but are not"))
2554 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2562 mycvstree = cvstree.getentries("./", recursive=1)
2563 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2564 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2565 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2566 bin_blob_pattern = re.compile("^-kb$")
2567 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2568 recursive=1, basedir="./"))
2571 with repoman_popen("svn status") as f:
2572 svnstatus = f.readlines()
2573 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2574 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2575 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2577 # Subversion expands keywords specified in svn:keywords properties.
2578 with repoman_popen("svn propget -R svn:keywords") as f:
2579 props = f.readlines()
2580 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2581 for prop in props if " - " in prop)
2584 with repoman_popen("git diff-index --name-only "
2585 "--relative --diff-filter=M HEAD") as f:
2586 mychanged = f.readlines()
2587 mychanged = ["./" + elem[:-1] for elem in mychanged]
2589 with repoman_popen("git diff-index --name-only "
2590 "--relative --diff-filter=A HEAD") as f:
2591 mynew = f.readlines()
2592 mynew = ["./" + elem[:-1] for elem in mynew]
2594 with repoman_popen("git diff-index --name-only "
2595 "--relative --diff-filter=D HEAD") as f:
2596 myremoved = f.readlines()
2597 myremoved = ["./" + elem[:-1] for elem in myremoved]
2600 with repoman_popen("bzr status -S .") as f:
2601 bzrstatus = f.readlines()
2602 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2603 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] in "NK" or elem[0:1] == "R")]
2604 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2605 myremoved = ["./" + elem.split()[-3:-2][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "K" or elem[0:1] == "R")]
2606 # Bazaar expands nothing.
2609 with repoman_popen("hg status --no-status --modified .") as f:
2610 mychanged = f.readlines()
2611 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2613 with repoman_popen("hg status --no-status --added .") as f:
2614 mynew = f.readlines()
2615 mynew = ["./" + elem.rstrip() for elem in mynew]
2617 with repoman_popen("hg status --no-status --removed .") as f:
2618 myremoved = f.readlines()
2619 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2622 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2623 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2625 print("(Didn't find any changed files...)")
2629 # Manifests need to be regenerated after all other commits, so don't commit
2630 # them now even if they have changed.
2633 for f in mychanged + mynew:
2634 if "Manifest" == os.path.basename(f):
2638 myupdates.difference_update(myremoved)
2639 myupdates = list(myupdates)
2640 mymanifests = list(mymanifests)
2644 commitmessage = options.commitmsg
2645 if options.commitmsgfile:
2647 f = io.open(_unicode_encode(options.commitmsgfile,
2648 encoding=_encodings['fs'], errors='strict'),
2649 mode='r', encoding=_encodings['content'], errors='replace')
2650 commitmessage = f.read()
2653 except (IOError, OSError) as e:
2654 if e.errno == errno.ENOENT:
2655 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2658 # We've read the content so the file is no longer needed.
2659 commitmessagefile = None
2660 if not commitmessage or not commitmessage.strip():
2662 editor = os.environ.get("EDITOR")
2663 if editor and utilities.editor_is_executable(editor):
2664 commitmessage = utilities.get_commit_message_with_editor(
2665 editor, message=qa_output)
2667 commitmessage = utilities.get_commit_message_with_stdin()
2668 except KeyboardInterrupt:
2670 if not commitmessage or not commitmessage.strip():
2671 print("* no commit message? aborting commit.")
2673 commitmessage = commitmessage.rstrip()
2674 changelog_msg = commitmessage
2675 portage_version = getattr(portage, "VERSION", None)
2676 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2677 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2678 if portage_version is None:
2679 sys.stderr.write("Failed to insert portage version in message!\n")
2681 portage_version = "Unknown"
2685 report_options.append("--force")
2686 if options.ignore_arches:
2687 report_options.append("--ignore-arches")
2688 if include_arches is not None:
2689 report_options.append("--include-arches=\"%s\"" %
2690 " ".join(sorted(include_arches)))
2693 # Use new footer only for git (see bug #438364).
2694 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2696 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2698 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2700 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2702 unameout = platform.system() + " "
2703 if platform.system() in ["Darwin", "SunOS"]:
2704 unameout += platform.processor()
2706 unameout += platform.machine()
2707 commit_footer = "\n\n"
2709 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2710 commit_footer += "(Portage version: %s/%s/%s" % \
2711 (portage_version, vcs, unameout)
2713 commit_footer += ", RepoMan options: " + " ".join(report_options)
2715 commit_footer += ", signed Manifest commit with key %s" % \
2718 commit_footer += ", unsigned Manifest commit"
2719 commit_footer += ")"
2721 commitmessage += commit_footer
2723 broken_changelog_manifests = []
2724 if options.echangelog in ('y', 'force'):
2725 logging.info("checking for unmodified ChangeLog files")
2726 committer_name = utilities.get_committer_name(env=repoman_settings)
2727 for x in sorted(vcs_files_to_cps(
2728 chain(myupdates, mymanifests, myremoved))):
2729 catdir, pkgdir = x.split("/")
2730 checkdir = repodir + "/" + x
2731 checkdir_relative = ""
2733 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2735 checkdir_relative = os.path.join(catdir, checkdir_relative)
2736 checkdir_relative = os.path.join(".", checkdir_relative)
2738 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2739 changelog_modified = changelog_path in modified_changelogs
2740 if changelog_modified and options.echangelog != 'force':
2743 # get changes for this package
2744 cdrlen = len(checkdir_relative)
2745 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2746 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2747 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2749 # Skip ChangeLog generation if only the Manifest was modified,
2750 # as discussed in bug #398009.
2751 nontrivial_cl_files = set()
2752 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2753 nontrivial_cl_files.difference_update(['Manifest'])
2754 if not nontrivial_cl_files and options.echangelog != 'force':
2757 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2758 committer_name, changelog_msg,
2759 os.path.join(repodir, 'skel.ChangeLog'),
2761 new=clnew, removed=clremoved, changed=clchanged,
2762 pretend=options.pretend)
2763 if new_changelog is None:
2764 writemsg_level("!!! Updating the ChangeLog failed\n", \
2765 level=logging.ERROR, noiselevel=-1)
2768 # if the ChangeLog was just created, add it to vcs
2770 myautoadd.append(changelog_path)
2771 # myautoadd is appended to myupdates below
2773 myupdates.append(changelog_path)
2775 if options.ask and not options.pretend:
2776 # regenerate Manifest for modified ChangeLog (bug #420735)
2777 repoman_settings["O"] = checkdir
2778 digestgen(mysettings=repoman_settings, myportdb=portdb)
2780 broken_changelog_manifests.append(x)
2783 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2784 add_cmd = [vcs, "add"]
2785 add_cmd += myautoadd
2787 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2791 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2792 not os.path.isabs(add_cmd[0]):
2793 # Python 3.1 _execvp throws TypeError for non-absolute executable
2794 # path passed as bytes (see http://bugs.python.org/issue8513).
2795 fullname = find_binary(add_cmd[0])
2796 if fullname is None:
2797 raise portage.exception.CommandNotFound(add_cmd[0])
2798 add_cmd[0] = fullname
2800 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2801 retcode = subprocess.call(add_cmd)
2802 if retcode != os.EX_OK:
2804 "Exiting on %s error code: %s\n" % (vcs, retcode))
2807 myupdates += myautoadd
2809 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2811 if vcs not in ('cvs', 'svn'):
2812 # With git, bzr and hg, there's never any keyword expansion, so
2813 # there's no need to regenerate manifests and all files will be
2814 # committed in one big commit at the end.
2816 elif not repo_config.thin_manifest:
2818 headerstring = "'\$(Header|Id).*\$'"
2820 svn_keywords = dict((k.lower(), k) for k in [
2823 "LastChangedRevision",
2834 for myfile in myupdates:
2836 # for CVS, no_expansion contains files that are excluded from expansion
2838 if myfile in no_expansion:
2841 # for SVN, expansion contains files that are included in expansion
2843 if myfile not in expansion:
2846 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2847 enabled_keywords = []
2848 for k in expansion[myfile]:
2849 keyword = svn_keywords.get(k.lower())
2850 if keyword is not None:
2851 enabled_keywords.append(keyword)
2853 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2855 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2856 portage._shell_quote(myfile))
2858 myheaders.append(myfile)
2860 print("%s have headers that will change." % green(str(len(myheaders))))
2861 print("* Files with headers will cause the manifests to be changed and committed separately.")
2863 logging.info("myupdates: %s", myupdates)
2864 logging.info("myheaders: %s", myheaders)
2866 if options.ask and userquery('Commit changes?', True) != 'Yes':
2867 print("* aborting commit.")
2868 sys.exit(128 + signal.SIGINT)
2870 # Handle the case where committed files have keywords which
2871 # will change and need a priming commit before the Manifest
2873 if (myupdates or myremoved) and myheaders:
2874 myfiles = myupdates + myremoved
2875 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2876 mymsg = os.fdopen(fd, "wb")
2877 mymsg.write(_unicode_encode(commitmessage))
2881 print(green("Using commit message:"))
2882 print(green("------------------------------------------------------------------------------"))
2883 print(commitmessage)
2884 print(green("------------------------------------------------------------------------------"))
2887 # Having a leading ./ prefix on file paths can trigger a bug in
2888 # the cvs server when committing files to multiple directories,
2889 # so strip the prefix.
2890 myfiles = [f.lstrip("./") for f in myfiles]
2893 commit_cmd.extend(vcs_global_opts)
2894 commit_cmd.append("commit")
2895 commit_cmd.extend(vcs_local_opts)
2896 commit_cmd.extend(["-F", commitmessagefile])
2897 commit_cmd.extend(myfiles)
2901 print("(%s)" % (" ".join(commit_cmd),))
2903 retval = spawn(commit_cmd, env=commit_env)
2904 if retval != os.EX_OK:
2905 writemsg_level(("!!! Exiting on %s (shell) " + \
2906 "error code: %s\n") % (vcs, retval),
2907 level=logging.ERROR, noiselevel=-1)
2911 os.unlink(commitmessagefile)
2915 # Setup the GPG commands
2916 def gpgsign(filename):
2917 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2919 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2920 " Is make.globals missing?")
2921 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2922 "PORTAGE_GPG_KEY" not in repoman_settings:
2923 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2924 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2925 if "PORTAGE_GPG_DIR" not in repoman_settings:
2926 repoman_settings["PORTAGE_GPG_DIR"] = \
2927 os.path.expanduser("~/.gnupg")
2928 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2929 % repoman_settings["PORTAGE_GPG_DIR"])
2931 repoman_settings["PORTAGE_GPG_DIR"] = \
2932 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2933 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2934 raise portage.exception.InvalidLocation(
2935 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2936 repoman_settings["PORTAGE_GPG_DIR"])
2937 gpgvars = {"FILE": filename}
2938 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2939 v = repoman_settings.get(k)
2942 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2944 print("(" + gpgcmd + ")")
2946 # Encode unicode manually for bug #310789.
2947 gpgcmd = portage.util.shlex_split(gpgcmd)
2949 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2950 not os.path.isabs(gpgcmd[0]):
2951 # Python 3.1 _execvp throws TypeError for non-absolute executable
2952 # path passed as bytes (see http://bugs.python.org/issue8513).
2953 fullname = find_binary(gpgcmd[0])
2954 if fullname is None:
2955 raise portage.exception.CommandNotFound(gpgcmd[0])
2956 gpgcmd[0] = fullname
2958 gpgcmd = [_unicode_encode(arg,
2959 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2960 rValue = subprocess.call(gpgcmd)
2961 if rValue == os.EX_OK:
2962 os.rename(filename + ".asc", filename)
2964 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2966 def need_signature(filename):
2968 with open(_unicode_encode(filename,
2969 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2970 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2971 except IOError as e:
2972 if e.errno in (errno.ENOENT, errno.ESTALE):
2976 # When files are removed and re-added, the cvs server will put /Attic/
2977 # inside the $Header path. This code detects the problem and corrects it
2978 # so that the Manifest will generate correctly. See bug #169500.
2979 # Use binary mode in order to avoid potential character encoding issues.
2980 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2981 attic_str = b'/Attic/'
2982 attic_replace = b'/'
2984 f = open(_unicode_encode(x,
2985 encoding=_encodings['fs'], errors='strict'),
2987 mylines = f.readlines()
2990 for i, line in enumerate(mylines):
2991 if cvs_header_re.match(line) is not None and \
2993 mylines[i] = line.replace(attic_str, attic_replace)
2996 portage.util.write_atomic(x, b''.join(mylines),
3000 print(green("RepoMan sez:"), "\"You're rather crazy... "
3001 "doing the entire repository.\"\n")
3003 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
3005 for x in sorted(vcs_files_to_cps(
3006 chain(myupdates, myremoved, mymanifests))):
3007 repoman_settings["O"] = os.path.join(repodir, x)
3008 digestgen(mysettings=repoman_settings, myportdb=portdb)
3010 elif broken_changelog_manifests:
3011 for x in broken_changelog_manifests:
3012 repoman_settings["O"] = os.path.join(repodir, x)
3013 digestgen(mysettings=repoman_settings, myportdb=portdb)
3019 for x in sorted(vcs_files_to_cps(
3020 chain(myupdates, myremoved, mymanifests))):
3021 repoman_settings["O"] = os.path.join(repodir, x)
3022 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
3023 if not need_signature(manifest_path):
3025 gpgsign(manifest_path)
3026 except portage.exception.PortageException as e:
3027 portage.writemsg("!!! %s\n" % str(e))
3028 portage.writemsg("!!! Disabled FEATURES='sign'\n")
3032 # It's not safe to use the git commit -a option since there might
3033 # be some modified files elsewhere in the working tree that the
3034 # user doesn't want to commit. Therefore, call git update-index
3035 # in order to ensure that the index is updated with the latest
3036 # versions of all new and modified files in the relevant portion
3037 # of the working tree.
3038 myfiles = mymanifests + myupdates
3040 update_index_cmd = ["git", "update-index"]
3041 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
3043 print("(%s)" % (" ".join(update_index_cmd),))
3045 retval = spawn(update_index_cmd, env=os.environ)
3046 if retval != os.EX_OK:
3047 writemsg_level(("!!! Exiting on %s (shell) " + \
3048 "error code: %s\n") % (vcs, retval),
3049 level=logging.ERROR, noiselevel=-1)
3053 myfiles = mymanifests[:]
3054 # If there are no header (SVN/CVS keywords) changes in
3055 # the files, this Manifest commit must include the
3056 # other (yet uncommitted) files.
3058 myfiles += myupdates
3059 myfiles += myremoved
3062 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3063 mymsg = os.fdopen(fd, "wb")
3064 mymsg.write(_unicode_encode(commitmessage))
3068 if options.pretend and vcs is None:
3069 # substitute a bogus value for pretend output
3070 commit_cmd.append("cvs")
3072 commit_cmd.append(vcs)
3073 commit_cmd.extend(vcs_global_opts)
3074 commit_cmd.append("commit")
3075 commit_cmd.extend(vcs_local_opts)
3077 commit_cmd.extend(["--logfile", commitmessagefile])
3078 commit_cmd.extend(myfiles)
3080 commit_cmd.extend(["-F", commitmessagefile])
3081 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3085 print("(%s)" % (" ".join(commit_cmd),))
3087 retval = spawn(commit_cmd, env=commit_env)
3088 if retval != os.EX_OK:
3089 if repo_config.sign_commit and vcs == 'git' and \
3090 not git_supports_gpg_sign():
3091 # Inform user that newer git is needed (bug #403323).
3093 "Git >=1.7.9 is required for signed commits!")
3095 writemsg_level(("!!! Exiting on %s (shell) " + \
3096 "error code: %s\n") % (vcs, retval),
3097 level=logging.ERROR, noiselevel=-1)
3101 os.unlink(commitmessagefile)
3107 print("Commit complete.")
3109 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3110 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")