2 # Copyright 1999-2014 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 = portage.const.LIVE_ECLASSES
429 suspect_rdepend = frozenset([
430 "app-arch/cabextract",
431 "app-arch/rpm2targz",
436 "dev-perl/extutils-pkgconfig",
442 "dev-util/gtk-doc-am",
445 "dev-util/pkg-config-lite",
447 "dev-util/pkgconfig",
448 "dev-util/pkgconfig-openbsd",
452 "media-gfx/ebdftopcf",
454 "sys-devel/autoconf",
455 "sys-devel/automake",
462 "virtual/linux-sources",
469 "dev-util/pkg-config-lite":"virtual/pkgconfig",
470 "dev-util/pkgconf":"virtual/pkgconfig",
471 "dev-util/pkgconfig":"virtual/pkgconfig",
472 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
473 "dev-libs/libusb":"virtual/libusb",
474 "dev-libs/libusbx":"virtual/libusb",
475 "dev-libs/libusb-compat":"virtual/libusb",
478 ruby_deprecated = frozenset([
479 "ruby_targets_ree18",
482 metadata_xml_encoding = 'UTF-8'
483 metadata_xml_declaration = '<?xml version="1.0" encoding="%s"?>' % \
484 (metadata_xml_encoding,)
485 metadata_doctype_name = 'pkgmetadata'
486 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
487 # force refetch if the local copy creation time is older than this
488 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
491 no_exec = frozenset(["Manifest", "ChangeLog", "metadata.xml"])
493 options, arguments = ParseArgs(sys.argv, qahelp)
496 print("Portage", portage.VERSION)
499 if options.experimental_inherit == 'y':
500 # This is experimental, so it's non-fatal.
501 qawarnings.add("inherit.missing")
502 repoman.checks._init(experimental_inherit=True)
504 # Set this to False when an extraordinary issue (generally
505 # something other than a QA issue) makes it impossible to
506 # commit (like if Manifest generation fails).
509 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
513 myreporoot = os.path.basename(portdir_overlay)
514 myreporoot += mydir[len(portdir_overlay):]
517 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
522 vcses = utilities.FindVCS()
524 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
525 print(red('*** Please either clean up your workdir or specify --vcs option.'))
532 if options.if_modified == "y" and vcs is None:
533 logging.info("Not in a version controlled repository; "
534 "disabling --if-modified.")
535 options.if_modified = "n"
537 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
538 vcs_preserves_mtime = vcs in ('cvs',)
540 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
541 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
542 if vcs_global_opts is None:
543 if vcs in ('cvs', 'svn'):
544 vcs_global_opts = "-q"
547 vcs_global_opts = vcs_global_opts.split()
549 if options.mode == 'commit' and not options.pretend and not vcs:
550 logging.info("Not in a version controlled repository; enabling pretend mode.")
551 options.pretend = True
553 # Ensure that current repository is in the list of enabled repositories.
554 repodir = os.path.realpath(portdir_overlay)
556 repoman_settings.repositories.get_repo_for_location(repodir)
558 repo_name = portage.repository.config.RepoConfig._read_valid_repo_name(portdir_overlay)[0]
559 layout_conf_data = portage.repository.config.parse_layout_conf(portdir_overlay)[0]
560 if layout_conf_data['repo-name']:
561 repo_name = layout_conf_data['repo-name']
562 tmp_conf_file = io.StringIO(textwrap.dedent("""
565 """) % (repo_name, portdir_overlay))
566 # Ensure that the repository corresponding to $PWD overrides a
567 # repository of the same name referenced by the existing PORTDIR
568 # or PORTDIR_OVERLAY settings.
569 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
570 (repoman_settings.get('PORTDIR_OVERLAY', ''),
571 portage._shell_quote(portdir_overlay))
572 repositories = portage.repository.config.load_repository_config(repoman_settings, extra_files=[tmp_conf_file])
573 # We have to call the config constructor again so that attributes
574 # dependent on config.repositories are initialized correctly.
575 repoman_settings = portage.config(config_root=config_root, local_config=False, repositories=repositories)
577 root = repoman_settings['EROOT']
579 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
581 portdb = trees[root]['porttree'].dbapi
583 # Constrain dependency resolution to the master(s)
584 # that are specified in layout.conf.
585 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
586 portdb.porttrees = list(repo_config.eclass_db.porttrees)
587 portdir = portdb.porttrees[0]
588 commit_env = os.environ.copy()
589 # list() is for iteration on a copy.
590 for repo in list(repoman_settings.repositories):
591 # all paths are canonical
592 if repo.location not in repo_config.eclass_db.porttrees:
593 del repoman_settings.repositories[repo.name]
595 if repo_config.allow_provide_virtual:
596 qawarnings.add("virtual.oldstyle")
598 if repo_config.sign_commit:
600 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
601 # the commit arguments. If key_id is unspecified, then it must be
602 # configured by `git config user.signingkey key_id`.
603 vcs_local_opts.append("--gpg-sign")
604 if repoman_settings.get("PORTAGE_GPG_DIR"):
605 # Pass GNUPGHOME to git for bug #462362.
606 commit_env["GNUPGHOME"] = repoman_settings["PORTAGE_GPG_DIR"]
608 # Pass GPG_TTY to git for bug #477728.
610 commit_env["GPG_TTY"] = os.ttyname(sys.stdin.fileno())
614 # In order to disable manifest signatures, repos may set
615 # "sign-manifests = false" in metadata/layout.conf. This
616 # can be used to prevent merge conflicts like those that
617 # thin-manifests is designed to prevent.
618 sign_manifests = "sign" in repoman_settings.features and \
619 repo_config.sign_manifest
621 if repo_config.sign_manifest and repo_config.name == "gentoo" and \
622 options.mode in ("commit",) and not sign_manifests:
623 msg = ("The '%s' repository has manifest signatures enabled, "
624 "but FEATURES=sign is currently disabled. In order to avoid this "
625 "warning, enable FEATURES=sign in make.conf. Alternatively, "
626 "repositories can disable manifest signatures by setting "
627 "'sign-manifests = false' in metadata/layout.conf.") % \
629 for line in textwrap.wrap(msg, 60):
632 if sign_manifests and options.mode in ("commit",) and \
633 repoman_settings.get("PORTAGE_GPG_KEY") and \
634 re.match(r'^%s$' % GPG_KEY_ID_REGEX,
635 repoman_settings["PORTAGE_GPG_KEY"]) is None:
636 logging.error("PORTAGE_GPG_KEY value is invalid: %s" %
637 repoman_settings["PORTAGE_GPG_KEY"])
640 manifest_hashes = repo_config.manifest_hashes
641 if manifest_hashes is None:
642 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
644 if options.mode in ("commit", "fix", "manifest"):
645 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
646 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
647 "metadata/layout.conf does not contain the '%s' hash which "
648 "is required by this portage version. You will have to "
649 "upgrade portage if you want to generate valid manifests for "
650 "this repository.") % \
651 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
652 for line in textwrap.wrap(msg, 70):
656 unsupported_hashes = manifest_hashes.difference(
657 portage.const.MANIFEST2_HASH_FUNCTIONS)
658 if unsupported_hashes:
659 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
660 "metadata/layout.conf contains one or more hash types '%s' "
661 "which are not supported by this portage version. You will "
662 "have to upgrade portage if you want to generate valid "
663 "manifests for this repository.") % \
664 (repo_config.name, " ".join(sorted(unsupported_hashes)))
665 for line in textwrap.wrap(msg, 70):
669 if options.echangelog is None and repo_config.update_changelog:
670 options.echangelog = 'y'
673 options.echangelog = 'n'
675 # The --echangelog option causes automatic ChangeLog generation,
676 # which invalidates changelog.ebuildadded and changelog.missing
678 # Note: Some don't use ChangeLogs in distributed SCMs.
679 # It will be generated on server side from scm log,
680 # before package moves to the rsync server.
681 # This is needed because they try to avoid merge collisions.
682 # Gentoo's Council decided to always use the ChangeLog file.
683 # TODO: shouldn't this just be switched on the repo, iso the VCS?
684 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
686 if 'digest' in repoman_settings.features and options.digest != 'n':
689 logging.debug("vcs: %s" % (vcs,))
690 logging.debug("repo config: %s" % (repo_config,))
691 logging.debug("options: %s" % (options,))
693 # It's confusing if these warnings are displayed without the user
694 # being told which profile they come from, so disable them.
695 env = os.environ.copy()
696 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
699 for path in repo_config.eclass_db.porttrees:
700 categories.extend(portage.util.grabfile(
701 os.path.join(path, 'profiles', 'categories')))
702 repoman_settings.categories = frozenset(
703 portage.util.stack_lists([categories], incremental=1))
704 categories = repoman_settings.categories
706 portdb.settings = repoman_settings
707 root_config = RootConfig(repoman_settings, trees[root], None)
708 # We really only need to cache the metadata that's necessary for visibility
709 # filtering. Anything else can be discarded to reduce memory consumption.
710 portdb._aux_cache_keys.clear()
711 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
713 reposplit = myreporoot.split(os.path.sep)
714 repolevel = len(reposplit)
716 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
717 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
718 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
719 if options.mode == 'commit' and repolevel not in [1, 2, 3]:
720 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
721 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
722 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
724 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
726 # Make startdir relative to the canonical repodir, so that we can pass
727 # it to digestgen and it won't have to be canonicalized again.
731 startdir = normalize_path(mydir)
732 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2 - repolevel + 3:])
735 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.")
737 def repoman_getstatusoutput(cmd):
739 Implements an interface similar to getstatusoutput(), but with
740 customized unicode handling (see bug #310789) and without the shell.
742 args = portage.util.shlex_split(cmd)
744 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
745 not os.path.isabs(args[0]):
746 # Python 3.1 _execvp throws TypeError for non-absolute executable
747 # path passed as bytes (see http://bugs.python.org/issue8513).
748 fullname = find_binary(args[0])
750 raise portage.exception.CommandNotFound(args[0])
753 encoding = _encodings['fs']
754 args = [_unicode_encode(x,
755 encoding=encoding, errors='strict') for x in args]
756 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
757 stderr=subprocess.STDOUT)
758 output = portage._unicode_decode(proc.communicate()[0],
759 encoding=encoding, errors='strict')
760 if output and output[-1] == "\n":
761 # getstatusoutput strips one newline
763 return (proc.wait(), output)
765 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
767 Implements an interface similar to os.popen(), but with customized
768 unicode handling (see bug #310789) and without the shell.
771 __slots__ = ('_proc', '_stdout')
773 def __init__(self, cmd):
774 args = portage.util.shlex_split(cmd)
776 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
777 not os.path.isabs(args[0]):
778 # Python 3.1 _execvp throws TypeError for non-absolute executable
779 # path passed as bytes (see http://bugs.python.org/issue8513).
780 fullname = find_binary(args[0])
782 raise portage.exception.CommandNotFound(args[0])
785 encoding = _encodings['fs']
786 args = [_unicode_encode(x,
787 encoding=encoding, errors='strict') for x in args]
788 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
789 object.__setattr__(self, '_proc', proc)
790 object.__setattr__(self, '_stdout',
791 codecs.getreader(encoding)(proc.stdout, 'strict'))
793 def _get_target(self):
794 return object.__getattribute__(self, '_stdout')
796 __enter__ = _get_target
798 def __exit__(self, exc_type, exc_value, traceback):
799 proc = object.__getattribute__(self, '_proc')
803 class ProfileDesc(object):
804 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
805 def __init__(self, arch, status, sub_path, tree_path):
809 sub_path = normalize_path(sub_path.lstrip(os.sep))
810 self.sub_path = sub_path
811 self.tree_path = tree_path
813 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
815 self.abs_path = tree_path
820 return 'empty profile'
823 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
825 # get lists of valid keywords, licenses, and use
829 global_pmasklines = []
831 for path in portdb.porttrees:
833 liclist.update(os.listdir(os.path.join(path, "licenses")))
836 kwlist.update(portage.grabfile(os.path.join(path,
837 "profiles", "arch.list")))
839 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
845 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
847 expand_list = os.listdir(expand_desc_dir)
851 for fn in expand_list:
852 if not fn[-5:] == '.desc':
854 use_prefix = fn[:-5].lower() + '_'
855 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
858 uselist.add(use_prefix + x[0])
860 global_pmasklines.append(portage.util.grabfile_package(
861 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
863 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
865 desc_file = io.open(_unicode_encode(desc_path,
866 encoding=_encodings['fs'], errors='strict'),
867 mode='r', encoding=_encodings['repo.content'], errors='replace')
868 except EnvironmentError:
871 for i, x in enumerate(desc_file):
878 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
879 desc_path + " line %d" % (i + 1, ))
880 elif arch[0] not in kwlist:
881 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
882 desc_path + " line %d" % (i + 1, ))
883 elif arch[2] not in valid_profile_types:
884 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
885 desc_path + " line %d" % (i + 1, ))
886 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
887 if not os.path.isdir(profile_desc.abs_path):
889 "Invalid %s profile (%s) for arch %s in %s line %d",
890 arch[2], arch[1], arch[0], desc_path, i + 1)
893 os.path.join(profile_desc.abs_path, 'deprecated')):
895 profile_list.append(profile_desc)
898 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
899 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
901 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
902 global_pmaskdict = {}
903 for x in global_pmasklines:
904 global_pmaskdict.setdefault(x.cp, []).append(x)
905 del global_pmasklines
907 def has_global_mask(pkg):
908 mask_atoms = global_pmaskdict.get(pkg.cp)
912 if portage.dep.match_from_list(x, pkg_list):
916 # Ensure that profile sub_path attributes are unique. Process in reverse order
917 # so that profiles with duplicate sub_path from overlays will override
918 # profiles with the same sub_path from parent repos.
920 profile_list.reverse()
921 profile_sub_paths = set()
922 for prof in profile_list:
923 if prof.sub_path in profile_sub_paths:
925 profile_sub_paths.add(prof.sub_path)
926 profiles.setdefault(prof.arch, []).append(prof)
928 # Use an empty profile for checking dependencies of
929 # packages that have empty KEYWORDS.
930 prof = ProfileDesc('**', 'stable', '', '')
931 profiles.setdefault(prof.arch, []).append(prof)
933 for x in repoman_settings.archlist():
936 if x not in profiles:
937 print(red("\"" + x + "\" doesn't have a valid profile listed in profiles.desc."))
938 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
939 print(red("up with the " + x + " team."))
942 liclist_deprecated = set()
943 if "DEPRECATED" in repoman_settings._license_manager._license_groups:
944 liclist_deprecated.update(
945 repoman_settings._license_manager.expandLicenseTokens(["@DEPRECATED"]))
948 logging.fatal("Couldn't find licenses?")
952 logging.fatal("Couldn't read KEYWORDS from arch.list")
956 logging.fatal("Couldn't find use.desc?")
961 # we are inside a category directory
962 catdir = reposplit[-1]
963 if catdir not in categories:
965 mydirlist = os.listdir(startdir)
967 if x == "CVS" or x.startswith("."):
969 if os.path.isdir(startdir + "/" + x):
970 scanlist.append(catdir + "/" + x)
971 repo_subdir = catdir + os.sep
974 if not os.path.isdir(startdir + "/" + x):
976 for y in os.listdir(startdir + "/" + x):
977 if y == "CVS" or y.startswith("."):
979 if os.path.isdir(startdir + "/" + x + "/" + y):
980 scanlist.append(x + "/" + y)
983 catdir = reposplit[-2]
984 if catdir not in categories:
986 scanlist.append(catdir + "/" + reposplit[-1])
987 repo_subdir = scanlist[-1] + os.sep
989 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
990 ' from the current working directory'
991 logging.critical(msg)
994 repo_subdir_len = len(repo_subdir)
997 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
999 def vcs_files_to_cps(vcs_file_iter):
1001 Iterate over the given modified file paths returned from the vcs,
1002 and return a frozenset containing category/pn strings for each
1009 if reposplit[-2] in categories and \
1010 next(vcs_file_iter, None) is not None:
1011 modified_cps.append("/".join(reposplit[-2:]))
1013 elif repolevel == 2:
1014 category = reposplit[-1]
1015 if category in categories:
1016 for filename in vcs_file_iter:
1017 f_split = filename.split(os.sep)
1019 if len(f_split) > 2:
1020 modified_cps.append(category + "/" + f_split[1])
1024 for filename in vcs_file_iter:
1025 f_split = filename.split(os.sep)
1026 # ['.', category, pn, ...]
1027 if len(f_split) > 3 and f_split[1] in categories:
1028 modified_cps.append("/".join(f_split[1:3]))
1030 return frozenset(modified_cps)
1032 def git_supports_gpg_sign():
1033 status, cmd_output = \
1034 repoman_getstatusoutput("git --version")
1035 cmd_output = cmd_output.split()
1037 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1038 if version is not None:
1039 version = [int(x) for x in version.groups()]
1040 if version[0] > 1 or \
1041 (version[0] == 1 and version[1] > 7) or \
1042 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1046 def dev_keywords(profiles):
1048 Create a set of KEYWORDS values that exist in 'dev'
1049 profiles. These are used
1050 to trigger a message notifying the user when they might
1051 want to add the --include-dev option.
1054 for arch, arch_profiles in profiles.items():
1055 for prof in arch_profiles:
1056 arch_set = type_arch_map.get(prof.status)
1057 if arch_set is None:
1059 type_arch_map[prof.status] = arch_set
1062 dev_keywords = type_arch_map.get('dev', set())
1063 dev_keywords.update(['~' + arch for arch in dev_keywords])
1064 return frozenset(dev_keywords)
1066 dev_keywords = dev_keywords(profiles)
1075 xmllint_capable = False
1076 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1078 def fetch_metadata_dtd():
1080 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1081 metadata_dtd_ctime_interval.
1083 @return: True if successful, otherwise False
1087 metadata_dtd_st = None
1088 current_time = int(time.time())
1090 metadata_dtd_st = os.stat(metadata_dtd)
1091 except EnvironmentError as e:
1092 if e.errno not in (errno.ENOENT, errno.ESTALE):
1096 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1097 if abs(current_time - metadata_dtd_st.st_ctime) \
1098 < metadata_dtd_ctime_interval:
1103 print(green("***") + " the local copy of metadata.dtd " + \
1104 "needs to be refetched, doing that now")
1106 parsed_url = urlparse(metadata_dtd_uri)
1107 setting = 'FETCHCOMMAND_' + parsed_url.scheme.upper()
1108 fcmd = repoman_settings.get(setting)
1110 fcmd = repoman_settings.get('FETCHCOMMAND')
1112 logging.error("FETCHCOMMAND is unset")
1115 destdir = repoman_settings["DISTDIR"]
1116 fd, metadata_dtd_tmp = tempfile.mkstemp(
1117 prefix='metadata.dtd.', dir=destdir)
1121 if not portage.getbinpkg.file_get(metadata_dtd_uri,
1123 filename=os.path.basename(metadata_dtd_tmp)):
1124 logging.error("failed to fetch metadata.dtd from '%s'" %
1129 portage.util.apply_secpass_permissions(metadata_dtd_tmp,
1130 gid=portage.data.portage_gid, mode=0o664, mask=0o2)
1131 except portage.exception.PortageException:
1134 os.rename(metadata_dtd_tmp, metadata_dtd)
1137 os.unlink(metadata_dtd_tmp)
1143 if options.mode == "manifest":
1145 elif not find_binary('xmllint'):
1146 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1147 if options.xml_parse or repolevel == 3:
1148 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1151 if not fetch_metadata_dtd():
1153 # this can be problematic if xmllint changes their output
1154 xmllint_capable = True
1156 if options.mode == 'commit' and vcs:
1157 utilities.detect_vcs_conflicts(options, vcs)
1159 if options.mode == "manifest":
1161 elif options.pretend:
1162 print(green("\nRepoMan does a once-over of the neighborhood..."))
1164 print(green("\nRepoMan scours the neighborhood..."))
1167 modified_ebuilds = set()
1168 modified_changelogs = set()
1174 mycvstree = cvstree.getentries("./", recursive=1)
1175 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1176 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1177 if options.if_modified == "y":
1178 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1181 with repoman_popen("svn status") as f:
1182 svnstatus = f.readlines()
1183 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR"]
1184 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
1185 if options.if_modified == "y":
1186 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1189 with repoman_popen("git diff-index --name-only "
1190 "--relative --diff-filter=M HEAD") as f:
1191 mychanged = f.readlines()
1192 mychanged = ["./" + elem[:-1] for elem in mychanged]
1194 with repoman_popen("git diff-index --name-only "
1195 "--relative --diff-filter=A HEAD") as f:
1196 mynew = f.readlines()
1197 mynew = ["./" + elem[:-1] for elem in mynew]
1198 if options.if_modified == "y":
1199 with repoman_popen("git diff-index --name-only "
1200 "--relative --diff-filter=D HEAD") as f:
1201 myremoved = f.readlines()
1202 myremoved = ["./" + elem[:-1] for elem in myremoved]
1205 with repoman_popen("bzr status -S .") as f:
1206 bzrstatus = f.readlines()
1207 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
1208 mynew = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and (elem[1:2] == "NK" or elem[0:1] == "R")]
1209 if options.if_modified == "y":
1210 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")]
1213 with repoman_popen("hg status --no-status --modified .") as f:
1214 mychanged = f.readlines()
1215 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1216 with repoman_popen("hg status --no-status --added .") as f:
1217 mynew = f.readlines()
1218 mynew = ["./" + elem.rstrip() for elem in mynew]
1219 if options.if_modified == "y":
1220 with repoman_popen("hg status --no-status --removed .") as f:
1221 myremoved = f.readlines()
1222 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1225 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1226 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1227 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1228 if os.path.basename(x) == "ChangeLog")
1230 def vcs_new_changed(relative_path):
1231 for x in chain(mychanged, mynew):
1232 if x == relative_path:
1236 have_pmasked = False
1237 have_dev_keywords = False
1240 # NOTE: match-all caches are not shared due to potential
1241 # differences between profiles in _get_implicit_iuse.
1243 arch_xmatch_caches = {}
1244 shared_xmatch_caches = {"cp-list":{}}
1246 include_arches = None
1247 if options.include_arches:
1248 include_arches = set()
1249 include_arches.update(*[x.split() for x in options.include_arches])
1251 # Disable the "ebuild.notadded" check when not in commit mode and
1252 # running `svn status` in every package dir will be too expensive.
1254 check_ebuild_notadded = not \
1255 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1257 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1258 thirdpartymirrors = {}
1259 for k, v in repoman_settings.thirdpartymirrors().items():
1261 if not v.endswith("/"):
1263 thirdpartymirrors[v] = k
1265 class _XMLParser(xml.etree.ElementTree.XMLParser):
1267 def __init__(self, data, **kwargs):
1268 xml.etree.ElementTree.XMLParser.__init__(self, **kwargs)
1269 self._portage_data = data
1270 if hasattr(self, 'parser'):
1271 self._base_XmlDeclHandler = self.parser.XmlDeclHandler
1272 self.parser.XmlDeclHandler = self._portage_XmlDeclHandler
1273 self._base_StartDoctypeDeclHandler = \
1274 self.parser.StartDoctypeDeclHandler
1275 self.parser.StartDoctypeDeclHandler = \
1276 self._portage_StartDoctypeDeclHandler
1278 def _portage_XmlDeclHandler(self, version, encoding, standalone):
1279 if self._base_XmlDeclHandler is not None:
1280 self._base_XmlDeclHandler(version, encoding, standalone)
1281 self._portage_data["XML_DECLARATION"] = (version, encoding, standalone)
1283 def _portage_StartDoctypeDeclHandler(self, doctypeName, systemId, publicId,
1284 has_internal_subset):
1285 if self._base_StartDoctypeDeclHandler is not None:
1286 self._base_StartDoctypeDeclHandler(doctypeName, systemId, publicId,
1287 has_internal_subset)
1288 self._portage_data["DOCTYPE"] = (doctypeName, systemId, publicId)
1290 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1292 Implements doctype() as required to avoid deprecation warnings with
1295 def doctype(self, name, pubid, system):
1299 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1300 except (EnvironmentError, ParseError, PermissionDenied) as e:
1302 except FileNotFound:
1303 # TODO: Download as we do for metadata.dtd, but add a way to
1304 # disable for non-gentoo repoman users who may not have herds.
1307 effective_scanlist = scanlist
1308 if options.if_modified == "y":
1309 effective_scanlist = sorted(vcs_files_to_cps(
1310 chain(mychanged, mynew, myremoved)))
1312 for x in effective_scanlist:
1313 # ebuilds and digests added to cvs respectively.
1314 logging.info("checking package %s" % x)
1315 # save memory by discarding xmatch caches from previous package(s)
1316 arch_xmatch_caches.clear()
1318 catdir, pkgdir = x.split("/")
1319 checkdir = repodir + "/" + x
1320 checkdir_relative = ""
1322 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1324 checkdir_relative = os.path.join(catdir, checkdir_relative)
1325 checkdir_relative = os.path.join(".", checkdir_relative)
1326 generated_manifest = False
1328 if options.mode == "manifest" or \
1329 (options.mode != 'manifest-check' and options.digest == 'y') or \
1330 options.mode in ('commit', 'fix') and not options.pretend:
1331 auto_assumed = set()
1332 fetchlist_dict = portage.FetchlistDict(checkdir,
1333 repoman_settings, portdb)
1334 if options.mode == 'manifest' and options.force:
1335 portage._doebuild_manifest_exempt_depend += 1
1337 distdir = repoman_settings['DISTDIR']
1338 mf = repoman_settings.repositories.get_repo_for_location(
1339 os.path.dirname(os.path.dirname(checkdir)))
1340 mf = mf.load_manifest(checkdir, distdir,
1341 fetchlist_dict=fetchlist_dict)
1342 mf.create(requiredDistfiles=None,
1343 assumeDistHashesAlways=True)
1344 for distfiles in fetchlist_dict.values():
1345 for distfile in distfiles:
1346 if os.path.isfile(os.path.join(distdir, distfile)):
1347 mf.fhashdict['DIST'].pop(distfile, None)
1349 auto_assumed.add(distfile)
1352 portage._doebuild_manifest_exempt_depend -= 1
1354 repoman_settings["O"] = checkdir
1356 generated_manifest = digestgen(
1357 mysettings=repoman_settings, myportdb=portdb)
1358 except portage.exception.PermissionDenied as e:
1359 generated_manifest = False
1360 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1361 level=logging.ERROR, noiselevel=-1)
1363 if not generated_manifest:
1364 print("Unable to generate manifest.")
1367 if options.mode == "manifest":
1368 if not dofail and options.force and auto_assumed and \
1369 'assume-digests' in repoman_settings.features:
1370 # Show which digests were assumed despite the --force option
1371 # being given. This output will already have been shown by
1372 # digestgen() if assume-digests is not enabled, so only show
1373 # it here if assume-digests is enabled.
1374 pkgs = list(fetchlist_dict)
1376 portage.writemsg_stdout(" digest.assumed" + \
1377 portage.output.colorize("WARN",
1378 str(len(auto_assumed)).rjust(18)) + "\n")
1380 fetchmap = fetchlist_dict[cpv]
1381 pf = portage.catsplit(cpv)[1]
1382 for distfile in sorted(fetchmap):
1383 if distfile in auto_assumed:
1384 portage.writemsg_stdout(
1385 " %s::%s\n" % (pf, distfile))
1390 if not generated_manifest:
1391 repoman_settings['O'] = checkdir
1392 repoman_settings['PORTAGE_QUIET'] = '1'
1393 if not portage.digestcheck([], repoman_settings, strict=1):
1394 stats["manifest.bad"] += 1
1395 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1396 repoman_settings.pop('PORTAGE_QUIET', None)
1398 if options.mode == 'manifest-check':
1401 checkdirlist = os.listdir(checkdir)
1405 for y in checkdirlist:
1406 if (y in no_exec or y.endswith(".ebuild")) and \
1407 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1408 stats["file.executable"] += 1
1409 fails["file.executable"].append(os.path.join(checkdir, y))
1410 if y.endswith(".ebuild"):
1412 ebuildlist.append(pf)
1413 cpv = "%s/%s" % (catdir, pf)
1415 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1418 stats["ebuild.syntax"] += 1
1419 fails["ebuild.syntax"].append(os.path.join(x, y))
1423 stats["ebuild.output"] += 1
1424 fails["ebuild.output"].append(os.path.join(x, y))
1426 if not portage.eapi_is_supported(myaux["EAPI"]):
1428 stats["EAPI.unsupported"] += 1
1429 fails["EAPI.unsupported"].append(os.path.join(x, y))
1431 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1432 root_config=root_config, type_name="ebuild")
1436 if len(pkgs) != len(ebuildlist):
1437 # If we can't access all the metadata then it's totally unsafe to
1438 # commit since there's no way to generate a correct Manifest.
1439 # Do not try to do any more QA checks on this package since missing
1440 # metadata leads to false positives for several checks, and false
1441 # positives confuse users.
1445 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1446 ebuildlist = sorted(pkgs.values())
1447 ebuildlist = [pkg.pf for pkg in ebuildlist]
1449 for y in checkdirlist:
1450 index = repo_config.find_invalid_path_char(y)
1452 y_relative = os.path.join(checkdir_relative, y)
1453 if vcs is not None and not vcs_new_changed(y_relative):
1454 # If the file isn't in the VCS new or changed set, then
1455 # assume that it's an irrelevant temporary file (Manifest
1456 # entries are not generated for file names containing
1457 # prohibited characters). See bug #406877.
1460 stats["file.name"] += 1
1461 fails["file.name"].append("%s/%s: char '%s'" % \
1462 (checkdir, y, y[index]))
1464 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1469 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1470 encoding=_encodings['fs'], errors='strict'),
1471 mode='r', encoding=_encodings['repo.content'])
1474 except UnicodeDecodeError as ue:
1475 stats["file.UTF8"] += 1
1476 s = ue.object[:ue.start]
1480 s = s[s.rfind("\n") + 1:]
1481 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1486 if vcs in ("git", "hg") and check_ebuild_notadded:
1488 myf = repoman_popen("git ls-files --others %s" % \
1489 (portage._shell_quote(checkdir_relative),))
1491 myf = repoman_popen("hg status --no-status --unknown %s" % \
1492 (portage._shell_quote(checkdir_relative),))
1494 if l[:-1][-7:] == ".ebuild":
1495 stats["ebuild.notadded"] += 1
1496 fails["ebuild.notadded"].append(
1497 os.path.join(x, os.path.basename(l[:-1])))
1500 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1503 myf = open(checkdir + "/CVS/Entries", "r")
1505 myf = repoman_popen("svn status --depth=files --verbose " +
1506 portage._shell_quote(checkdir))
1508 myf = repoman_popen("bzr ls -v --kind=file " +
1509 portage._shell_quote(checkdir))
1510 myl = myf.readlines()
1516 splitl = l[1:].split("/")
1519 if splitl[0][-7:] == ".ebuild":
1520 eadded.append(splitl[0][:-7])
1525 # tree conflict, new in subversion 1.6
1528 if l[-7:] == ".ebuild":
1529 eadded.append(os.path.basename(l[:-7]))
1534 if l[-7:] == ".ebuild":
1535 eadded.append(os.path.basename(l[:-7]))
1537 myf = repoman_popen("svn status " +
1538 portage._shell_quote(checkdir))
1539 myl = myf.readlines()
1543 l = l.rstrip().split(' ')[-1]
1544 if l[-7:] == ".ebuild":
1545 eadded.append(os.path.basename(l[:-7]))
1548 stats["CVS/Entries.IO_error"] += 1
1549 fails["CVS/Entries.IO_error"].append(checkdir + "/CVS/Entries")
1554 mf = repoman_settings.repositories.get_repo_for_location(
1555 os.path.dirname(os.path.dirname(checkdir)))
1556 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1557 mydigests = mf.getTypeDigests("DIST")
1559 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1561 src_uri_error = False
1562 for mykey in fetchlist_dict:
1564 myfiles_all.extend(fetchlist_dict[mykey])
1565 except portage.exception.InvalidDependString as e:
1566 src_uri_error = True
1568 portdb.aux_get(mykey, ["SRC_URI"])
1570 # This will be reported as an "ebuild.syntax" error.
1573 stats["SRC_URI.syntax"] += 1
1574 fails["SRC_URI.syntax"].append(
1575 "%s.ebuild SRC_URI: %s" % (mykey, e))
1577 if not src_uri_error:
1578 # This test can produce false positives if SRC_URI could not
1579 # be parsed for one or more ebuilds. There's no point in
1580 # producing a false error here since the root cause will
1581 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1582 # or "ebuild.sytax".
1583 myfiles_all = set(myfiles_all)
1584 for entry in mydigests:
1585 if entry not in myfiles_all:
1586 stats["digest.unused"] += 1
1587 fails["digest.unused"].append(checkdir + "::" + entry)
1588 for entry in myfiles_all:
1589 if entry not in mydigests:
1590 stats["digest.missing"] += 1
1591 fails["digest.missing"].append(checkdir + "::" + entry)
1594 if os.path.exists(checkdir + "/files"):
1595 filesdirlist = os.listdir(checkdir + "/files")
1597 # recurse through files directory
1598 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1600 y = filesdirlist.pop(0)
1601 relative_path = os.path.join(x, "files", y)
1602 full_path = os.path.join(repodir, relative_path)
1604 mystat = os.stat(full_path)
1605 except OSError as oe:
1607 # don't worry about it. it likely was removed via fix above.
1611 if S_ISDIR(mystat.st_mode):
1612 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1613 if y == "CVS" or y == ".svn":
1615 for z in os.listdir(checkdir + "/files/" + y):
1616 if z == "CVS" or z == ".svn":
1618 filesdirlist.append(y + "/" + z)
1619 # Current policy is no files over 20 KiB, these are the checks. File size between
1620 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1621 elif mystat.st_size > 61440:
1622 stats["file.size.fatal"] += 1
1623 fails["file.size.fatal"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1624 elif mystat.st_size > 20480:
1625 stats["file.size"] += 1
1626 fails["file.size"].append("(" + str(mystat.st_size//1024) + " KiB) " + x + "/files/" + y)
1628 index = repo_config.find_invalid_path_char(y)
1630 y_relative = os.path.join(checkdir_relative, "files", y)
1631 if vcs is not None and not vcs_new_changed(y_relative):
1632 # If the file isn't in the VCS new or changed set, then
1633 # assume that it's an irrelevant temporary file (Manifest
1634 # entries are not generated for file names containing
1635 # prohibited characters). See bug #406877.
1638 stats["file.name"] += 1
1639 fails["file.name"].append("%s/files/%s: char '%s'" % \
1640 (checkdir, y, y[index]))
1643 if check_changelog and "ChangeLog" not in checkdirlist:
1644 stats["changelog.missing"] += 1
1645 fails["changelog.missing"].append(x + "/ChangeLog")
1648 # metadata.xml file check
1649 if "metadata.xml" not in checkdirlist:
1650 stats["metadata.missing"] += 1
1651 fails["metadata.missing"].append(x + "/metadata.xml")
1652 # metadata.xml parse check
1654 metadata_bad = False
1656 xml_parser = _XMLParser(xml_info, target=_MetadataTreeBuilder())
1658 # read metadata.xml into memory
1660 _metadata_xml = xml.etree.ElementTree.parse(
1661 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1662 encoding=_encodings['fs'], errors='strict'),
1664 except (ExpatError, SyntaxError, EnvironmentError) as e:
1666 stats["metadata.bad"] += 1
1667 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1670 if not hasattr(xml_parser, 'parser') or \
1671 sys.hexversion < 0x2070000 or \
1672 (sys.hexversion > 0x3000000 and sys.hexversion < 0x3020000):
1673 # doctype is not parsed with python 2.6 or 3.1
1676 if "XML_DECLARATION" not in xml_info:
1677 stats["metadata.bad"] += 1
1678 fails["metadata.bad"].append("%s/metadata.xml: "
1679 "xml declaration is missing on first line, "
1680 "should be '%s'" % (x, metadata_xml_declaration))
1682 xml_version, xml_encoding, xml_standalone = \
1683 xml_info["XML_DECLARATION"]
1684 if xml_encoding is None or \
1685 xml_encoding.upper() != metadata_xml_encoding:
1686 stats["metadata.bad"] += 1
1687 if xml_encoding is None:
1688 encoding_problem = "but it is undefined"
1690 encoding_problem = "not '%s'" % xml_encoding
1691 fails["metadata.bad"].append("%s/metadata.xml: "
1692 "xml declaration encoding should be '%s', %s" %
1693 (x, metadata_xml_encoding, encoding_problem))
1695 if "DOCTYPE" not in xml_info:
1697 stats["metadata.bad"] += 1
1698 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x,
1699 "DOCTYPE is missing"))
1701 doctype_name, doctype_system, doctype_pubid = \
1703 if doctype_system != metadata_dtd_uri:
1704 stats["metadata.bad"] += 1
1705 if doctype_system is None:
1706 system_problem = "but it is undefined"
1708 system_problem = "not '%s'" % doctype_system
1709 fails["metadata.bad"].append("%s/metadata.xml: "
1710 "DOCTYPE: SYSTEM should refer to '%s', %s" %
1711 (x, metadata_dtd_uri, system_problem))
1713 if doctype_name != metadata_doctype_name:
1714 stats["metadata.bad"] += 1
1715 fails["metadata.bad"].append("%s/metadata.xml: "
1716 "DOCTYPE: name should be '%s', not '%s'" %
1717 (x, metadata_doctype_name, doctype_name))
1719 # load USE flags from metadata.xml
1721 musedict = utilities.parse_metadata_use(_metadata_xml)
1722 except portage.exception.ParseError as e:
1724 stats["metadata.bad"] += 1
1725 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1727 for atom in chain(*musedict.values()):
1732 except InvalidAtom as e:
1733 stats["metadata.bad"] += 1
1734 fails["metadata.bad"].append(
1735 "%s/metadata.xml: Invalid atom: %s" % (x, e))
1738 stats["metadata.bad"] += 1
1739 fails["metadata.bad"].append(
1740 ("%s/metadata.xml: Atom contains "
1741 "unexpected cat/pn: %s") % (x, atom))
1743 # Run other metadata.xml checkers
1745 utilities.check_metadata(_metadata_xml, herd_base)
1746 except (utilities.UnknownHerdsError, ) as e:
1748 stats["metadata.bad"] += 1
1749 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1752 # Only carry out if in package directory or check forced
1753 if xmllint_capable and not metadata_bad:
1754 # xmlint can produce garbage output even on success, so only dump
1755 # the ouput when it fails.
1756 st, out = repoman_getstatusoutput(
1757 "xmllint --nonet --noout --dtdvalid %s %s" % \
1758 (portage._shell_quote(metadata_dtd),
1759 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1761 print(red("!!!") + " metadata.xml is invalid:")
1762 for z in out.splitlines():
1763 print(red("!!! ") + z)
1764 stats["metadata.bad"] += 1
1765 fails["metadata.bad"].append(x + "/metadata.xml")
1768 muselist = frozenset(musedict)
1770 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1771 changelog_modified = changelog_path in modified_changelogs
1773 # detect unused local USE-descriptions
1774 used_useflags = set()
1776 for y in ebuildlist:
1777 relative_path = os.path.join(x, y + ".ebuild")
1778 full_path = os.path.join(repodir, relative_path)
1779 ebuild_path = y + ".ebuild"
1781 ebuild_path = os.path.join(pkgdir, ebuild_path)
1783 ebuild_path = os.path.join(catdir, ebuild_path)
1784 ebuild_path = os.path.join(".", ebuild_path)
1785 if check_changelog and not changelog_modified \
1786 and ebuild_path in new_ebuilds:
1787 stats['changelog.ebuildadded'] += 1
1788 fails['changelog.ebuildadded'].append(relative_path)
1790 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1791 # ebuild not added to vcs
1792 stats["ebuild.notadded"] += 1
1793 fails["ebuild.notadded"].append(x + "/" + y + ".ebuild")
1794 myesplit = portage.pkgsplit(y)
1795 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1796 or pv_toolong_re.search(myesplit[1]) \
1797 or pv_toolong_re.search(myesplit[2]):
1798 stats["ebuild.invalidname"] += 1
1799 fails["ebuild.invalidname"].append(x + "/" + y + ".ebuild")
1801 elif myesplit[0] != pkgdir:
1802 print(pkgdir, myesplit[0])
1803 stats["ebuild.namenomatch"] += 1
1804 fails["ebuild.namenomatch"].append(x + "/" + y + ".ebuild")
1811 for k, msgs in pkg.invalid.items():
1814 fails[k].append("%s: %s" % (relative_path, msg))
1817 myaux = pkg._metadata
1818 eapi = myaux["EAPI"]
1819 inherited = pkg.inherited
1820 live_ebuild = live_eclasses.intersection(inherited)
1822 if repo_config.eapi_is_banned(eapi):
1823 stats["repo.eapi.banned"] += 1
1824 fails["repo.eapi.banned"].append(
1825 "%s: %s" % (relative_path, eapi))
1827 elif repo_config.eapi_is_deprecated(eapi):
1828 stats["repo.eapi.deprecated"] += 1
1829 fails["repo.eapi.deprecated"].append(
1830 "%s: %s" % (relative_path, eapi))
1832 for k, v in myaux.items():
1833 if not isinstance(v, basestring):
1835 m = non_ascii_re.search(v)
1837 stats["variable.invalidchar"] += 1
1838 fails["variable.invalidchar"].append(
1839 ("%s: %s variable contains non-ASCII " + \
1840 "character at position %s") % \
1841 (relative_path, k, m.start() + 1))
1843 if not src_uri_error:
1844 # Check that URIs don't reference a server from thirdpartymirrors.
1845 for uri in portage.dep.use_reduce( \
1846 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1847 contains_mirror = False
1848 for mirror, mirror_alias in thirdpartymirrors.items():
1849 if uri.startswith(mirror):
1850 contains_mirror = True
1852 if not contains_mirror:
1855 new_uri = "mirror://%s/%s" % (mirror_alias, uri[len(mirror):])
1856 stats["SRC_URI.mirror"] += 1
1857 fails["SRC_URI.mirror"].append(
1858 "%s: '%s' found in thirdpartymirrors, use '%s'" % \
1859 (relative_path, mirror, new_uri))
1861 if myaux.get("PROVIDE"):
1862 stats["virtual.oldstyle"] += 1
1863 fails["virtual.oldstyle"].append(relative_path)
1865 for pos, missing_var in enumerate(missingvars):
1866 if not myaux.get(missing_var):
1867 if catdir == "virtual" and \
1868 missing_var in ("HOMEPAGE", "LICENSE"):
1870 if live_ebuild and missing_var == "KEYWORDS":
1872 myqakey = missingvars[pos] + ".missing"
1874 fails[myqakey].append(x + "/" + y + ".ebuild")
1876 if catdir == "virtual":
1877 for var in ("HOMEPAGE", "LICENSE"):
1879 myqakey = var + ".virtual"
1881 fails[myqakey].append(relative_path)
1883 # 14 is the length of DESCRIPTION=""
1884 if len(myaux['DESCRIPTION']) > max_desc_len:
1885 stats['DESCRIPTION.toolong'] += 1
1886 fails['DESCRIPTION.toolong'].append(
1887 "%s: DESCRIPTION is %d characters (max %d)" % \
1888 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1890 keywords = myaux["KEYWORDS"].split()
1891 stable_keywords = []
1892 for keyword in keywords:
1893 if not keyword.startswith("~") and \
1894 not keyword.startswith("-"):
1895 stable_keywords.append(keyword)
1897 if ebuild_path in new_ebuilds and catdir != "virtual":
1898 stable_keywords.sort()
1899 stats["KEYWORDS.stable"] += 1
1900 fails["KEYWORDS.stable"].append(
1901 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1902 " ".join(stable_keywords))
1904 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1905 if not kw.startswith("-"))
1907 previous_keywords = slot_keywords.get(pkg.slot)
1908 if previous_keywords is None:
1909 slot_keywords[pkg.slot] = set()
1910 elif ebuild_archs and "*" not in ebuild_archs and not live_ebuild:
1911 dropped_keywords = previous_keywords.difference(ebuild_archs)
1912 if dropped_keywords:
1913 stats["KEYWORDS.dropped"] += 1
1914 fails["KEYWORDS.dropped"].append(
1915 relative_path + ": %s" % \
1916 " ".join(sorted(dropped_keywords)))
1918 slot_keywords[pkg.slot].update(ebuild_archs)
1920 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1921 if "-*" in keywords:
1929 stats["KEYWORDS.stupid"] += 1
1930 fails["KEYWORDS.stupid"].append(x + "/" + y + ".ebuild")
1933 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1934 not be allowed to be marked stable
1936 if live_ebuild and repo_config.name == "gentoo":
1937 bad_stable_keywords = []
1938 for keyword in keywords:
1939 if not keyword.startswith("~") and \
1940 not keyword.startswith("-"):
1941 bad_stable_keywords.append(keyword)
1943 if bad_stable_keywords:
1944 stats["LIVEVCS.stable"] += 1
1945 fails["LIVEVCS.stable"].append(
1946 x + "/" + y + ".ebuild with stable keywords:%s " % \
1947 bad_stable_keywords)
1948 del bad_stable_keywords
1950 if keywords and not has_global_mask(pkg):
1951 stats["LIVEVCS.unmasked"] += 1
1952 fails["LIVEVCS.unmasked"].append(relative_path)
1954 if options.ignore_arches:
1955 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1956 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1959 for keyword in keywords:
1960 if keyword[0] == "-":
1962 elif keyword[0] == "~":
1965 for expanded_arch in profiles:
1966 if expanded_arch == "**":
1968 arches.add((keyword, expanded_arch,
1969 (expanded_arch, "~" + expanded_arch)))
1971 arches.add((keyword, arch, (arch, keyword)))
1974 for expanded_arch in profiles:
1975 if expanded_arch == "**":
1977 arches.add((keyword, expanded_arch,
1980 arches.add((keyword, keyword, (keyword,)))
1982 # Use an empty profile for checking dependencies of
1983 # packages that have empty KEYWORDS.
1984 arches.add(('**', '**', ('**',)))
1986 unknown_pkgs = set()
1987 baddepsyntax = False
1988 badlicsyntax = False
1989 badprovsyntax = False
1990 catpkg = catdir + "/" + y
1992 inherited_java_eclass = "java-pkg-2" in inherited or \
1993 "java-pkg-opt-2" in inherited
1994 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1995 operator_tokens = set(["||", "(", ")"])
1996 type_list, badsyntax = [], []
1997 for mytype in Package._dep_keys + ("LICENSE", "PROPERTIES", "PROVIDE"):
1998 mydepstr = myaux[mytype]
2000 buildtime = mytype in Package._buildtime_keys
2001 runtime = mytype in Package._runtime_keys
2003 if mytype.endswith("DEPEND"):
2004 token_class = portage.dep.Atom
2007 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
2008 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
2009 except portage.exception.InvalidDependString as e:
2011 badsyntax.append(str(e))
2013 if atoms and mytype.endswith("DEPEND"):
2015 "test?" in mydepstr.split():
2016 stats[mytype + '.suspect'] += 1
2017 fails[mytype + '.suspect'].append(relative_path + \
2018 ": 'test?' USE conditional in %s" % mytype)
2024 # Skip dependency.unknown for blockers, so that we
2025 # don't encourage people to remove necessary blockers,
2026 # as discussed in bug #382407.
2027 if atom.blocker is None and \
2028 not portdb.xmatch("match-all", atom) and \
2029 not atom.cp.startswith("virtual/"):
2030 unknown_pkgs.add((mytype, atom.unevaluated_atom))
2032 is_blocker = atom.blocker
2034 if catdir != "virtual":
2035 if not is_blocker and \
2036 atom.cp in suspect_virtual:
2037 stats['virtual.suspect'] += 1
2038 fails['virtual.suspect'].append(
2040 ": %s: consider using '%s' instead of '%s'" %
2041 (mytype, suspect_virtual[atom.cp], atom))
2044 not is_blocker and \
2045 not inherited_java_eclass and \
2046 atom.cp == "virtual/jdk":
2047 stats['java.eclassesnotused'] += 1
2048 fails['java.eclassesnotused'].append(relative_path)
2049 elif buildtime and \
2050 not is_blocker and \
2051 not inherited_wxwidgets_eclass and \
2052 atom.cp == "x11-libs/wxGTK":
2053 stats['wxwidgets.eclassnotused'] += 1
2054 fails['wxwidgets.eclassnotused'].append(
2055 (relative_path + ": %ss on x11-libs/wxGTK"
2056 " without inheriting wxwidgets.eclass") % mytype)
2058 if not is_blocker and \
2059 atom.cp in suspect_rdepend:
2060 stats[mytype + '.suspect'] += 1
2061 fails[mytype + '.suspect'].append(
2062 relative_path + ": '%s'" % atom)
2064 if atom.operator == "~" and \
2065 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
2066 qacat = 'dependency.badtilde'
2068 fails[qacat].append(
2069 (relative_path + ": %s uses the ~ operator"
2070 " with a non-zero revision:" + \
2071 " '%s'") % (mytype, atom))
2073 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
2075 for m, b in zip(type_list, badsyntax):
2076 if m.endswith("DEPEND"):
2077 qacat = "dependency.syntax"
2079 qacat = m + ".syntax"
2081 fails[qacat].append("%s: %s: %s" % (relative_path, m, b))
2083 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
2084 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
2085 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
2086 badlicsyntax = badlicsyntax > 0
2087 badprovsyntax = badprovsyntax > 0
2089 # uselist checks - global
2092 for myflag in myaux["IUSE"].split():
2093 flag_name = myflag.lstrip("+-")
2094 used_useflags.add(flag_name)
2095 if myflag != flag_name:
2096 default_use.append(myflag)
2097 if flag_name not in uselist:
2098 myuse.append(flag_name)
2100 # uselist checks - metadata
2101 for mypos in range(len(myuse)-1, -1, -1):
2102 if myuse[mypos] and (myuse[mypos] in muselist):
2105 if default_use and not eapi_has_iuse_defaults(eapi):
2106 for myflag in default_use:
2107 stats['EAPI.incompatible'] += 1
2108 fails['EAPI.incompatible'].append(
2109 (relative_path + ": IUSE defaults" + \
2110 " not supported with EAPI='%s':" + \
2111 " '%s'") % (eapi, myflag))
2113 for mypos in range(len(myuse)):
2114 stats["IUSE.invalid"] += 1
2115 fails["IUSE.invalid"].append(x + "/" + y + ".ebuild: %s" % myuse[mypos])
2117 # Check for outdated RUBY targets
2118 if "ruby-ng" in inherited or "ruby-fakegem" in inherited or "ruby" in inherited:
2119 ruby_intersection = pkg.iuse.all.intersection(ruby_deprecated)
2120 if ruby_intersection:
2121 for myruby in ruby_intersection:
2122 stats["IUSE.rubydeprecated"] += 1
2123 fails["IUSE.rubydeprecated"].append(
2124 (relative_path + ": Deprecated ruby target: %s") % myruby)
2127 if not badlicsyntax:
2128 # Parse the LICENSE variable, remove USE conditions and
2130 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2131 # Check each entry to ensure that it exists in PORTDIR's
2132 # license directory.
2133 for lic in licenses:
2134 # Need to check for "||" manually as no portage
2135 # function will remove it without removing values.
2136 if lic not in liclist and lic != "||":
2137 stats["LICENSE.invalid"] += 1
2138 fails["LICENSE.invalid"].append(x + "/" + y + ".ebuild: %s" % lic)
2139 elif lic in liclist_deprecated:
2140 stats["LICENSE.deprecated"] += 1
2141 fails["LICENSE.deprecated"].append("%s: %s" % (relative_path, lic))
2144 myuse = myaux["KEYWORDS"].split()
2146 if mykey not in ("-*", "*", "~*"):
2148 if myskey[:1] == "-":
2150 if myskey[:1] == "~":
2152 if myskey not in kwlist:
2153 stats["KEYWORDS.invalid"] += 1
2154 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s" % mykey)
2155 elif myskey not in profiles:
2156 stats["KEYWORDS.invalid"] += 1
2157 fails["KEYWORDS.invalid"].append(x + "/" + y + ".ebuild: %s (profile invalid)" % mykey)
2162 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2163 except portage.exception.InvalidDependString as e:
2164 stats["RESTRICT.syntax"] += 1
2165 fails["RESTRICT.syntax"].append(
2166 "%s: RESTRICT: %s" % (relative_path, e))
2169 myrestrict = set(myrestrict)
2170 mybadrestrict = myrestrict.difference(valid_restrict)
2172 stats["RESTRICT.invalid"] += len(mybadrestrict)
2173 for mybad in mybadrestrict:
2174 fails["RESTRICT.invalid"].append(x + "/" + y + ".ebuild: %s" % mybad)
2175 # REQUIRED_USE check
2176 required_use = myaux["REQUIRED_USE"]
2178 if not eapi_has_required_use(eapi):
2179 stats['EAPI.incompatible'] += 1
2180 fails['EAPI.incompatible'].append(
2181 relative_path + ": REQUIRED_USE" + \
2182 " not supported with EAPI='%s'" % (eapi,))
2184 portage.dep.check_required_use(required_use, (),
2185 pkg.iuse.is_valid_flag, eapi=eapi)
2186 except portage.exception.InvalidDependString as e:
2187 stats["REQUIRED_USE.syntax"] += 1
2188 fails["REQUIRED_USE.syntax"].append(
2189 "%s: REQUIRED_USE: %s" % (relative_path, e))
2193 relative_path = os.path.join(x, y + ".ebuild")
2194 full_path = os.path.join(repodir, relative_path)
2195 if not vcs_preserves_mtime:
2196 if ebuild_path not in new_ebuilds and \
2197 ebuild_path not in modified_ebuilds:
2200 # All ebuilds should have utf_8 encoding.
2201 f = io.open(_unicode_encode(full_path,
2202 encoding=_encodings['fs'], errors='strict'),
2203 mode='r', encoding=_encodings['repo.content'])
2205 for check_name, e in run_checks(f, pkg):
2206 stats[check_name] += 1
2207 fails[check_name].append(relative_path + ': %s' % e)
2210 except UnicodeDecodeError:
2211 # A file.UTF8 failure will have already been recorded above.
2215 # The dep_check() calls are the most expensive QA test. If --force
2216 # is enabled, there's no point in wasting time on these since the
2217 # user is intent on forcing the commit anyway.
2220 relevant_profiles = []
2221 for keyword, arch, groups in arches:
2222 if arch not in profiles:
2223 # A missing profile will create an error further down
2224 # during the KEYWORDS verification.
2227 if include_arches is not None:
2228 if arch not in include_arches:
2231 relevant_profiles.extend((keyword, groups, prof)
2232 for prof in profiles[arch])
2235 return item[2].sub_path
2237 relevant_profiles.sort(key=sort_key)
2239 for keyword, groups, prof in relevant_profiles:
2241 if not (prof.status == "stable" or \
2242 (prof.status == "dev" and options.include_dev) or \
2243 (prof.status == "exp" and options.include_exp_profiles == 'y')):
2246 dep_settings = arch_caches.get(prof.sub_path)
2247 if dep_settings is None:
2248 dep_settings = portage.config(
2249 config_profile_path=prof.abs_path,
2250 config_incrementals=repoman_incrementals,
2251 config_root=config_root,
2253 _unmatched_removal=options.unmatched_removal,
2254 env=env, repositories=repoman_settings.repositories)
2255 dep_settings.categories = repoman_settings.categories
2256 if options.without_mask:
2257 dep_settings._mask_manager_obj = \
2258 copy.deepcopy(dep_settings._mask_manager)
2259 dep_settings._mask_manager._pmaskdict.clear()
2260 arch_caches[prof.sub_path] = dep_settings
2262 xmatch_cache_key = (prof.sub_path, tuple(groups))
2263 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2267 xcache = portdb.xcache
2268 xcache.update(shared_xmatch_caches)
2269 arch_xmatch_caches[xmatch_cache_key] = xcache
2271 trees[root]["porttree"].settings = dep_settings
2272 portdb.settings = dep_settings
2273 portdb.xcache = xcache
2275 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2276 # just in case, prevent config.reset() from nuking these.
2277 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2279 # This attribute is used in dbapi._match_use() to apply
2280 # use.stable.{mask,force} settings based on the stable
2281 # status of the parent package. This is required in order
2282 # for USE deps of unstable packages to be resolved correctly,
2283 # since otherwise use.stable.{mask,force} settings of
2284 # dependencies may conflict (see bug #456342).
2285 dep_settings._parent_stable = dep_settings._isStable(pkg)
2287 # Handle package.use*.{force,mask) calculation, for use
2289 dep_settings.useforce = dep_settings._use_manager.getUseForce(
2290 pkg, stable=dep_settings._parent_stable)
2291 dep_settings.usemask = dep_settings._use_manager.getUseMask(
2292 pkg, stable=dep_settings._parent_stable)
2294 if not baddepsyntax:
2295 ismasked = not ebuild_archs or \
2296 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2298 if not have_pmasked:
2299 have_pmasked = bool(dep_settings._getMaskAtom(
2300 pkg.cpv, pkg._metadata))
2301 if options.ignore_masked:
2303 # we are testing deps for a masked package; give it some lee-way
2305 matchmode = "minimum-all"
2308 matchmode = "minimum-visible"
2310 if not have_dev_keywords:
2311 have_dev_keywords = \
2312 bool(dev_keywords.intersection(keywords))
2314 if prof.status == "dev":
2315 suffix = suffix + "indev"
2317 for mytype in Package._dep_keys:
2319 mykey = "dependency.bad" + suffix
2320 myvalue = myaux[mytype]
2324 success, atoms = portage.dep_check(myvalue, portdb,
2325 dep_settings, use="all", mode=matchmode,
2331 # Don't bother with dependency.unknown for
2332 # cases in which *DEPEND.bad is triggered.
2334 # dep_check returns all blockers and they
2335 # aren't counted for *DEPEND.bad, so we
2337 if not atom.blocker:
2338 unknown_pkgs.discard(
2339 (mytype, atom.unevaluated_atom))
2341 if not prof.sub_path:
2342 # old-style virtuals currently aren't
2343 # resolvable with empty profile, since
2344 # 'virtuals' mappings are unavailable
2345 # (it would be expensive to search
2346 # for PROVIDE in all ebuilds)
2347 atoms = [atom for atom in atoms if not \
2348 (atom.cp.startswith('virtual/') and \
2349 not portdb.cp_list(atom.cp))]
2351 # we have some unsolvable deps
2352 # remove ! deps, which always show up as unsatisfiable
2353 atoms = [str(atom.unevaluated_atom) \
2354 for atom in atoms if not atom.blocker]
2356 # if we emptied out our list, continue:
2360 fails[mykey].append("%s: %s: %s(%s) %s" % \
2361 (relative_path, mytype, keyword,
2365 fails[mykey].append("%s: %s: %s(%s) %s" % \
2366 (relative_path, mytype, keyword,
2369 if not baddepsyntax and unknown_pkgs:
2371 for mytype, atom in unknown_pkgs:
2372 type_map.setdefault(mytype, set()).add(atom)
2373 for mytype, atoms in type_map.items():
2374 stats["dependency.unknown"] += 1
2375 fails["dependency.unknown"].append("%s: %s: %s" %
2376 (relative_path, mytype, ", ".join(sorted(atoms))))
2378 # check if there are unused local USE-descriptions in metadata.xml
2379 # (unless there are any invalids, to avoid noise)
2381 for myflag in muselist.difference(used_useflags):
2382 stats["metadata.warning"] += 1
2383 fails["metadata.warning"].append(
2384 "%s/metadata.xml: unused local USE-description: '%s'" % \
2387 if options.if_modified == "y" and len(effective_scanlist) < 1:
2388 logging.warn("--if-modified is enabled, but no modified packages were found!")
2390 if options.mode == "manifest":
2393 # dofail will be set to 1 if we have failed in at least one non-warning category
2395 # dowarn will be set to 1 if we tripped any warnings
2397 # dofull will be set if we should print a "repoman full" informational message
2398 dofull = options.mode != 'full'
2404 if x not in qawarnings:
2408 (dowarn and not (options.quiet or options.mode == "scan")):
2411 # Save QA output so that it can be conveniently displayed
2412 # in $EDITOR while the user creates a commit message.
2413 # Otherwise, the user would not be able to see this output
2414 # once the editor has taken over the screen.
2415 qa_output = io.StringIO()
2416 style_file = ConsoleStyleFile(sys.stdout)
2417 if options.mode == 'commit' and \
2418 (not commitmessage or not commitmessage.strip()):
2419 style_file.write_listener = qa_output
2420 console_writer = StyleWriter(file=style_file, maxcol=9999)
2421 console_writer.style_listener = style_file.new_styles
2423 f = formatter.AbstractFormatter(console_writer)
2425 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2428 del console_writer, f, style_file
2429 qa_output = qa_output.getvalue()
2430 qa_output = qa_output.splitlines(True)
2432 suggest_ignore_masked = False
2433 suggest_include_dev = False
2435 if have_pmasked and not (options.without_mask or options.ignore_masked):
2436 suggest_ignore_masked = True
2437 if have_dev_keywords and not options.include_dev:
2438 suggest_include_dev = True
2440 if suggest_ignore_masked or suggest_include_dev:
2442 if suggest_ignore_masked:
2443 print(bold("Note: use --without-mask to check " + \
2444 "KEYWORDS on dependencies of masked packages"))
2446 if suggest_include_dev:
2447 print(bold("Note: use --include-dev (-d) to check " + \
2448 "dependencies for 'dev' profiles"))
2451 if options.mode != 'commit':
2453 print(bold("Note: type \"repoman full\" for a complete listing."))
2454 if dowarn and not dofail:
2455 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.\"")
2457 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2459 print(bad("Please fix these important QA issues first."))
2460 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2463 if dofail and can_force and options.force and not options.pretend:
2464 print(green("RepoMan sez:") + \
2465 " \"You want to commit even with these QA issues?\n" + \
2466 " I'll take it this time, but I'm not happy.\"\n")
2468 if options.force and not can_force:
2469 print(bad("The --force option has been disabled due to extraordinary issues."))
2470 print(bad("Please fix these important QA issues first."))
2471 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2475 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2480 myvcstree = portage.cvstree.getentries("./", recursive=1)
2481 myunadded = portage.cvstree.findunadded(myvcstree, recursive=1, basedir="./")
2482 except SystemExit as e:
2483 raise # TODO propagate this
2485 err("Error retrieving CVS tree; exiting.")
2488 with repoman_popen("svn status --no-ignore") as f:
2489 svnstatus = f.readlines()
2490 myunadded = ["./" + elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I")]
2491 except SystemExit as e:
2492 raise # TODO propagate this
2494 err("Error retrieving SVN info; exiting.")
2496 # get list of files not under version control or missing
2497 myf = repoman_popen("git ls-files --others")
2498 myunadded = ["./" + elem[:-1] for elem in myf]
2502 with repoman_popen("bzr status -S .") as f:
2503 bzrstatus = f.readlines()
2504 myunadded = ["./" + elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D"]
2505 except SystemExit as e:
2506 raise # TODO propagate this
2508 err("Error retrieving bzr info; exiting.")
2510 with repoman_popen("hg status --no-status --unknown .") as f:
2511 myunadded = f.readlines()
2512 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2514 # Mercurial doesn't handle manually deleted files as removed from
2515 # the repository, so the user need to remove them before commit,
2516 # using "hg remove [FILES]"
2517 with repoman_popen("hg status --no-status --deleted .") as f:
2518 mydeleted = f.readlines()
2519 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2524 for x in range(len(myunadded)-1, -1, -1):
2525 xs = myunadded[x].split("/")
2526 if xs[-1] == "files":
2527 print("!!! files dir is not added! Please correct this.")
2529 elif xs[-1] == "Manifest":
2530 # It's a manifest... auto add
2531 myautoadd += [myunadded[x]]
2535 print(red("!!! The following files are in your local tree but are not added to the master"))
2536 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2543 if vcs == "hg" and mydeleted:
2544 print(red("!!! The following files are removed manually from your local tree but are not"))
2545 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2553 mycvstree = cvstree.getentries("./", recursive=1)
2554 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2555 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2556 myremoved = portage.cvstree.findremoved(mycvstree, recursive=1, basedir="./")
2557 bin_blob_pattern = re.compile("^-kb$")
2558 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2559 recursive=1, basedir="./"))
2562 with repoman_popen("svn status") as f:
2563 svnstatus = f.readlines()
2564 mychanged = ["./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2565 mynew = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2566 myremoved = ["./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2568 # Subversion expands keywords specified in svn:keywords properties.
2569 with repoman_popen("svn propget -R svn:keywords") as f:
2570 props = f.readlines()
2571 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2572 for prop in props if " - " in prop)
2575 with repoman_popen("git diff-index --name-only "
2576 "--relative --diff-filter=M HEAD") as f:
2577 mychanged = f.readlines()
2578 mychanged = ["./" + elem[:-1] for elem in mychanged]
2580 with repoman_popen("git diff-index --name-only "
2581 "--relative --diff-filter=A HEAD") as f:
2582 mynew = f.readlines()
2583 mynew = ["./" + elem[:-1] for elem in mynew]
2585 with repoman_popen("git diff-index --name-only "
2586 "--relative --diff-filter=D HEAD") as f:
2587 myremoved = f.readlines()
2588 myremoved = ["./" + elem[:-1] for elem in myremoved]
2591 with repoman_popen("bzr status -S .") as f:
2592 bzrstatus = f.readlines()
2593 mychanged = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M"]
2594 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")]
2595 myremoved = ["./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-")]
2596 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")]
2597 # Bazaar expands nothing.
2600 with repoman_popen("hg status --no-status --modified .") as f:
2601 mychanged = f.readlines()
2602 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2604 with repoman_popen("hg status --no-status --added .") as f:
2605 mynew = f.readlines()
2606 mynew = ["./" + elem.rstrip() for elem in mynew]
2608 with repoman_popen("hg status --no-status --removed .") as f:
2609 myremoved = f.readlines()
2610 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2613 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2614 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2616 print("(Didn't find any changed files...)")
2620 # Manifests need to be regenerated after all other commits, so don't commit
2621 # them now even if they have changed.
2624 for f in mychanged + mynew:
2625 if "Manifest" == os.path.basename(f):
2629 myupdates.difference_update(myremoved)
2630 myupdates = list(myupdates)
2631 mymanifests = list(mymanifests)
2635 commitmessage = options.commitmsg
2636 if options.commitmsgfile:
2638 f = io.open(_unicode_encode(options.commitmsgfile,
2639 encoding=_encodings['fs'], errors='strict'),
2640 mode='r', encoding=_encodings['content'], errors='replace')
2641 commitmessage = f.read()
2644 except (IOError, OSError) as e:
2645 if e.errno == errno.ENOENT:
2646 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2649 # We've read the content so the file is no longer needed.
2650 commitmessagefile = None
2651 if not commitmessage or not commitmessage.strip():
2653 editor = os.environ.get("EDITOR")
2654 if editor and utilities.editor_is_executable(editor):
2655 commitmessage = utilities.get_commit_message_with_editor(
2656 editor, message=qa_output)
2658 commitmessage = utilities.get_commit_message_with_stdin()
2659 except KeyboardInterrupt:
2661 if not commitmessage or not commitmessage.strip():
2662 print("* no commit message? aborting commit.")
2664 commitmessage = commitmessage.rstrip()
2665 changelog_msg = commitmessage
2666 portage_version = getattr(portage, "VERSION", None)
2667 gpg_key = repoman_settings.get("PORTAGE_GPG_KEY", "")
2668 dco_sob = repoman_settings.get("DCO_SIGNED_OFF_BY", "")
2669 if portage_version is None:
2670 sys.stderr.write("Failed to insert portage version in message!\n")
2672 portage_version = "Unknown"
2676 report_options.append("--force")
2677 if options.ignore_arches:
2678 report_options.append("--ignore-arches")
2679 if include_arches is not None:
2680 report_options.append("--include-arches=\"%s\"" %
2681 " ".join(sorted(include_arches)))
2684 # Use new footer only for git (see bug #438364).
2685 commit_footer = "\n\nPackage-Manager: portage-%s" % portage_version
2687 commit_footer += "\nRepoMan-Options: " + " ".join(report_options)
2689 commit_footer += "\nManifest-Sign-Key: %s" % (gpg_key, )
2691 commit_footer += "\nSigned-off-by: %s" % (dco_sob, )
2693 unameout = platform.system() + " "
2694 if platform.system() in ["Darwin", "SunOS"]:
2695 unameout += platform.processor()
2697 unameout += platform.machine()
2698 commit_footer = "\n\n"
2700 commit_footer += "Signed-off-by: %s\n" % (dco_sob, )
2701 commit_footer += "(Portage version: %s/%s/%s" % \
2702 (portage_version, vcs, unameout)
2704 commit_footer += ", RepoMan options: " + " ".join(report_options)
2706 commit_footer += ", signed Manifest commit with key %s" % \
2709 commit_footer += ", unsigned Manifest commit"
2710 commit_footer += ")"
2712 commitmessage += commit_footer
2714 broken_changelog_manifests = []
2715 if options.echangelog in ('y', 'force'):
2716 logging.info("checking for unmodified ChangeLog files")
2717 committer_name = utilities.get_committer_name(env=repoman_settings)
2718 for x in sorted(vcs_files_to_cps(
2719 chain(myupdates, mymanifests, myremoved))):
2720 catdir, pkgdir = x.split("/")
2721 checkdir = repodir + "/" + x
2722 checkdir_relative = ""
2724 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2726 checkdir_relative = os.path.join(catdir, checkdir_relative)
2727 checkdir_relative = os.path.join(".", checkdir_relative)
2729 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2730 changelog_modified = changelog_path in modified_changelogs
2731 if changelog_modified and options.echangelog != 'force':
2734 # get changes for this package
2735 cdrlen = len(checkdir_relative)
2736 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2737 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2738 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2740 # Skip ChangeLog generation if only the Manifest was modified,
2741 # as discussed in bug #398009.
2742 nontrivial_cl_files = set()
2743 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2744 nontrivial_cl_files.difference_update(['Manifest'])
2745 if not nontrivial_cl_files and options.echangelog != 'force':
2748 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2749 committer_name, changelog_msg,
2750 os.path.join(repodir, 'skel.ChangeLog'),
2752 new=clnew, removed=clremoved, changed=clchanged,
2753 pretend=options.pretend)
2754 if new_changelog is None:
2755 writemsg_level("!!! Updating the ChangeLog failed\n", \
2756 level=logging.ERROR, noiselevel=-1)
2759 # if the ChangeLog was just created, add it to vcs
2761 myautoadd.append(changelog_path)
2762 # myautoadd is appended to myupdates below
2764 myupdates.append(changelog_path)
2766 if options.ask and not options.pretend:
2767 # regenerate Manifest for modified ChangeLog (bug #420735)
2768 repoman_settings["O"] = checkdir
2769 digestgen(mysettings=repoman_settings, myportdb=portdb)
2771 broken_changelog_manifests.append(x)
2774 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2775 add_cmd = [vcs, "add"]
2776 add_cmd += myautoadd
2778 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2782 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2783 not os.path.isabs(add_cmd[0]):
2784 # Python 3.1 _execvp throws TypeError for non-absolute executable
2785 # path passed as bytes (see http://bugs.python.org/issue8513).
2786 fullname = find_binary(add_cmd[0])
2787 if fullname is None:
2788 raise portage.exception.CommandNotFound(add_cmd[0])
2789 add_cmd[0] = fullname
2791 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2792 retcode = subprocess.call(add_cmd)
2793 if retcode != os.EX_OK:
2795 "Exiting on %s error code: %s\n" % (vcs, retcode))
2798 myupdates += myautoadd
2800 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2802 if vcs not in ('cvs', 'svn'):
2803 # With git, bzr and hg, there's never any keyword expansion, so
2804 # there's no need to regenerate manifests and all files will be
2805 # committed in one big commit at the end.
2807 elif not repo_config.thin_manifest:
2809 headerstring = "'\$(Header|Id).*\$'"
2811 svn_keywords = dict((k.lower(), k) for k in [
2814 "LastChangedRevision",
2825 for myfile in myupdates:
2827 # for CVS, no_expansion contains files that are excluded from expansion
2829 if myfile in no_expansion:
2832 # for SVN, expansion contains files that are included in expansion
2834 if myfile not in expansion:
2837 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2838 enabled_keywords = []
2839 for k in expansion[myfile]:
2840 keyword = svn_keywords.get(k.lower())
2841 if keyword is not None:
2842 enabled_keywords.append(keyword)
2844 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2846 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2847 portage._shell_quote(myfile))
2849 myheaders.append(myfile)
2851 print("%s have headers that will change." % green(str(len(myheaders))))
2852 print("* Files with headers will cause the manifests to be changed and committed separately.")
2854 logging.info("myupdates: %s", myupdates)
2855 logging.info("myheaders: %s", myheaders)
2857 if options.ask and userquery('Commit changes?', True) != 'Yes':
2858 print("* aborting commit.")
2859 sys.exit(128 + signal.SIGINT)
2861 # Handle the case where committed files have keywords which
2862 # will change and need a priming commit before the Manifest
2864 if (myupdates or myremoved) and myheaders:
2865 myfiles = myupdates + myremoved
2866 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2867 mymsg = os.fdopen(fd, "wb")
2868 mymsg.write(_unicode_encode(commitmessage))
2872 print(green("Using commit message:"))
2873 print(green("------------------------------------------------------------------------------"))
2874 print(commitmessage)
2875 print(green("------------------------------------------------------------------------------"))
2878 # Having a leading ./ prefix on file paths can trigger a bug in
2879 # the cvs server when committing files to multiple directories,
2880 # so strip the prefix.
2881 myfiles = [f.lstrip("./") for f in myfiles]
2884 commit_cmd.extend(vcs_global_opts)
2885 commit_cmd.append("commit")
2886 commit_cmd.extend(vcs_local_opts)
2887 commit_cmd.extend(["-F", commitmessagefile])
2888 commit_cmd.extend(myfiles)
2892 print("(%s)" % (" ".join(commit_cmd),))
2894 retval = spawn(commit_cmd, env=commit_env)
2895 if retval != os.EX_OK:
2896 writemsg_level(("!!! Exiting on %s (shell) " + \
2897 "error code: %s\n") % (vcs, retval),
2898 level=logging.ERROR, noiselevel=-1)
2902 os.unlink(commitmessagefile)
2906 # Setup the GPG commands
2907 def gpgsign(filename):
2908 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2910 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2911 " Is make.globals missing?")
2912 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2913 "PORTAGE_GPG_KEY" not in repoman_settings:
2914 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2915 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2916 if "PORTAGE_GPG_DIR" not in repoman_settings:
2917 repoman_settings["PORTAGE_GPG_DIR"] = \
2918 os.path.expanduser("~/.gnupg")
2919 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2920 % repoman_settings["PORTAGE_GPG_DIR"])
2922 repoman_settings["PORTAGE_GPG_DIR"] = \
2923 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2924 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2925 raise portage.exception.InvalidLocation(
2926 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2927 repoman_settings["PORTAGE_GPG_DIR"])
2928 gpgvars = {"FILE": filename}
2929 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2930 v = repoman_settings.get(k)
2933 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2935 print("(" + gpgcmd + ")")
2937 # Encode unicode manually for bug #310789.
2938 gpgcmd = portage.util.shlex_split(gpgcmd)
2940 if sys.hexversion < 0x3020000 and sys.hexversion >= 0x3000000 and \
2941 not os.path.isabs(gpgcmd[0]):
2942 # Python 3.1 _execvp throws TypeError for non-absolute executable
2943 # path passed as bytes (see http://bugs.python.org/issue8513).
2944 fullname = find_binary(gpgcmd[0])
2945 if fullname is None:
2946 raise portage.exception.CommandNotFound(gpgcmd[0])
2947 gpgcmd[0] = fullname
2949 gpgcmd = [_unicode_encode(arg,
2950 encoding=_encodings['fs'], errors='strict') for arg in gpgcmd]
2951 rValue = subprocess.call(gpgcmd)
2952 if rValue == os.EX_OK:
2953 os.rename(filename + ".asc", filename)
2955 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2957 def need_signature(filename):
2959 with open(_unicode_encode(filename,
2960 encoding=_encodings['fs'], errors='strict'), 'rb') as f:
2961 return b"BEGIN PGP SIGNED MESSAGE" not in f.readline()
2962 except IOError as e:
2963 if e.errno in (errno.ENOENT, errno.ESTALE):
2967 # When files are removed and re-added, the cvs server will put /Attic/
2968 # inside the $Header path. This code detects the problem and corrects it
2969 # so that the Manifest will generate correctly. See bug #169500.
2970 # Use binary mode in order to avoid potential character encoding issues.
2971 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2972 attic_str = b'/Attic/'
2973 attic_replace = b'/'
2975 f = open(_unicode_encode(x,
2976 encoding=_encodings['fs'], errors='strict'),
2978 mylines = f.readlines()
2981 for i, line in enumerate(mylines):
2982 if cvs_header_re.match(line) is not None and \
2984 mylines[i] = line.replace(attic_str, attic_replace)
2987 portage.util.write_atomic(x, b''.join(mylines),
2991 print(green("RepoMan sez:"), "\"You're rather crazy... "
2992 "doing the entire repository.\"\n")
2994 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2996 for x in sorted(vcs_files_to_cps(
2997 chain(myupdates, myremoved, mymanifests))):
2998 repoman_settings["O"] = os.path.join(repodir, x)
2999 digestgen(mysettings=repoman_settings, myportdb=portdb)
3001 elif broken_changelog_manifests:
3002 for x in broken_changelog_manifests:
3003 repoman_settings["O"] = os.path.join(repodir, x)
3004 digestgen(mysettings=repoman_settings, myportdb=portdb)
3010 for x in sorted(vcs_files_to_cps(
3011 chain(myupdates, myremoved, mymanifests))):
3012 repoman_settings["O"] = os.path.join(repodir, x)
3013 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
3014 if not need_signature(manifest_path):
3016 gpgsign(manifest_path)
3017 except portage.exception.PortageException as e:
3018 portage.writemsg("!!! %s\n" % str(e))
3019 portage.writemsg("!!! Disabled FEATURES='sign'\n")
3023 # It's not safe to use the git commit -a option since there might
3024 # be some modified files elsewhere in the working tree that the
3025 # user doesn't want to commit. Therefore, call git update-index
3026 # in order to ensure that the index is updated with the latest
3027 # versions of all new and modified files in the relevant portion
3028 # of the working tree.
3029 myfiles = mymanifests + myupdates
3031 update_index_cmd = ["git", "update-index"]
3032 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
3034 print("(%s)" % (" ".join(update_index_cmd),))
3036 retval = spawn(update_index_cmd, env=os.environ)
3037 if retval != os.EX_OK:
3038 writemsg_level(("!!! Exiting on %s (shell) " + \
3039 "error code: %s\n") % (vcs, retval),
3040 level=logging.ERROR, noiselevel=-1)
3044 myfiles = mymanifests[:]
3045 # If there are no header (SVN/CVS keywords) changes in
3046 # the files, this Manifest commit must include the
3047 # other (yet uncommitted) files.
3049 myfiles += myupdates
3050 myfiles += myremoved
3053 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
3054 mymsg = os.fdopen(fd, "wb")
3055 mymsg.write(_unicode_encode(commitmessage))
3059 if options.pretend and vcs is None:
3060 # substitute a bogus value for pretend output
3061 commit_cmd.append("cvs")
3063 commit_cmd.append(vcs)
3064 commit_cmd.extend(vcs_global_opts)
3065 commit_cmd.append("commit")
3066 commit_cmd.extend(vcs_local_opts)
3068 commit_cmd.extend(["--logfile", commitmessagefile])
3069 commit_cmd.extend(myfiles)
3071 commit_cmd.extend(["-F", commitmessagefile])
3072 commit_cmd.extend(f.lstrip("./") for f in myfiles)
3076 print("(%s)" % (" ".join(commit_cmd),))
3078 retval = spawn(commit_cmd, env=commit_env)
3079 if retval != os.EX_OK:
3080 if repo_config.sign_commit and vcs == 'git' and \
3081 not git_supports_gpg_sign():
3082 # Inform user that newer git is needed (bug #403323).
3084 "Git >=1.7.9 is required for signed commits!")
3086 writemsg_level(("!!! Exiting on %s (shell) " + \
3087 "error code: %s\n") % (vcs, retval),
3088 level=logging.ERROR, noiselevel=-1)
3092 os.unlink(commitmessagefile)
3098 print("Commit complete.")
3100 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
3101 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")