2 # Copyright 1999-2012 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
30 from urllib.request import urlopen as urllib_request_urlopen
32 from urllib import urlopen as urllib_request_urlopen
34 from itertools import chain
35 from stat import S_ISDIR
37 from os import path as osp
38 pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
39 sys.path.insert(0, pym_path)
41 portage._disable_legacy_globals()
42 portage.dep._internal_warnings = True
45 import xml.etree.ElementTree
46 from xml.parsers.expat import ExpatError
47 except (SystemExit, KeyboardInterrupt):
49 except (ImportError, SystemError, RuntimeError, Exception):
50 # broken or missing xml support
51 # http://bugs.python.org/issue14988
52 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
53 from portage.output import EOutput
59 from portage import os
60 from portage import _encodings
61 from portage import _unicode_encode
62 from repoman.checks import run_checks
63 from repoman import utilities
64 from repoman.herdbase import make_herd_base
65 from _emerge.Package import Package
66 from _emerge.RootConfig import RootConfig
67 from _emerge.userquery import userquery
68 import portage.checksum
70 from portage import cvstree, normalize_path
71 from portage import util
72 from portage.exception import (FileNotFound, MissingParameter,
73 ParseError, PermissionDenied)
74 from portage.manifest import _prohibited_filename_chars_re as \
75 disallowed_filename_chars_re
76 from portage.process import find_binary, spawn
77 from portage.output import bold, create_color_func, \
79 from portage.output import ConsoleStyleFile, StyleWriter
80 from portage.util import writemsg_level
81 from portage.util._desktop_entry import validate_desktop_entry
82 from portage.package.ebuild.digestgen import digestgen
83 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
85 if sys.hexversion >= 0x3000000:
88 util.initialize_logger()
90 # 14 is the length of DESCRIPTION=""
92 allowed_filename_chars="a-zA-Z0-9._-+:"
93 pv_toolong_re = re.compile(r'[0-9]{19,}')
94 bad = create_color_func("BAD")
96 # A sane umask is needed for files that portage creates.
98 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
99 # behave incrementally.
100 repoman_incrementals = tuple(x for x in \
101 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
102 config_root = os.environ.get("PORTAGE_CONFIGROOT")
103 repoman_settings = portage.config(config_root=config_root, local_config=False)
105 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
106 repoman_settings.get('TERM') == 'dumb' or \
107 not sys.stdout.isatty():
111 print("repoman: " + txt)
117 def exithandler(signum=None, frame=None):
118 logging.fatal("Interrupted; exiting...")
122 sys.exit(128 + signum)
124 signal.signal(signal.SIGINT,exithandler)
126 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
127 """Repoman needs it's own HelpFormatter for now, because the default ones
128 murder the help text."""
130 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
131 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
133 def format_description(self, description):
136 class RepomanOptionParser(optparse.OptionParser):
137 """Add the on_tail function, ruby has it, optionParser should too
140 def __init__(self, *args, **kwargs):
141 optparse.OptionParser.__init__(self, *args, **kwargs)
144 def on_tail(self, description):
145 self.tail += description
147 def format_help(self, formatter=None):
148 result = optparse.OptionParser.format_help(self, formatter)
153 def ParseArgs(argv, qahelp):
154 """This function uses a customized optionParser to parse command line arguments for repoman
156 argv - a sequence of command line arguments
157 qahelp - a dict of qa warning to help message
159 (opts, args), just like a call to parser.parse_args()
162 if argv and isinstance(argv[0], bytes):
163 argv = [portage._unicode_decode(x) for x in argv]
166 'commit' : 'Run a scan then commit changes',
167 'ci' : 'Run a scan then commit changes',
168 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
169 'full' : 'Scan directory tree and print all issues (not a summary)',
170 'help' : 'Show this screen',
171 'manifest' : 'Generate a Manifest (fetches files if necessary)',
172 'manifest-check' : 'Check Manifests for missing or incorrect digests',
173 'scan' : 'Scan directory tree for QA issues'
176 mode_keys = list(modes)
179 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
180 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
181 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
182 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
183 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
185 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
186 help='Request a confirmation before commiting')
188 parser.add_option('-m', '--commitmsg', dest='commitmsg',
189 help='specify a commit message on the command line')
191 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
192 help='specify a path to a file that contains a commit message')
194 parser.add_option('--digest',
195 type='choice', choices=('y', 'n'), metavar='<y|n>',
196 help='Automatically update Manifest digests for modified files')
198 parser.add_option('-p', '--pretend', dest='pretend', default=False,
199 action='store_true', help='don\'t commit or fix anything; just show what would be done')
201 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
202 help='do not print unnecessary messages')
205 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
206 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
207 'regardless of modification if \'force\' is specified)')
209 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
210 help='Commit with QA violations')
212 parser.add_option('--vcs', dest='vcs',
213 help='Force using specific VCS instead of autodetection')
215 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
216 help='be very verbose in output', default=0)
218 parser.add_option('-V', '--version', dest='version', action='store_true',
219 help='show version info')
221 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
222 default=False, help='forces the metadata.xml parse check to be carried out')
225 '--if-modified', type='choice', choices=('y', 'n'), default='n',
227 help='only check packages that have uncommitted modifications')
229 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
230 default=False, help='ignore arch-specific failures (where arch != host)')
232 parser.add_option("--ignore-default-opts",
234 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
236 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
237 default=False, help='ignore masked packages (not allowed with commit mode)')
239 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
240 default=False, help='include dev profiles in dependency checks')
242 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
243 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
245 parser.add_option('--without-mask', dest='without_mask', action='store_true',
246 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
248 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
249 help='specify which mode repoman will run in (default=full)')
251 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
254 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
256 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
258 sorted_qa = list(qahelp)
261 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
263 opts, args = parser.parse_args(argv[1:])
265 if not opts.ignore_default_opts:
266 default_opts = repoman_settings.get("REPOMAN_DEFAULT_OPTS", "").split()
268 opts, args = parser.parse_args(default_opts + sys.argv[1:])
270 if opts.mode == 'help':
271 parser.print_help(short=False)
279 parser.error("invalid mode: %s" % arg)
284 if opts.mode == 'ci':
285 opts.mode = 'commit' # backwards compat shortcut
287 if opts.mode == 'commit' and not (opts.force or opts.pretend):
288 if opts.ignore_masked:
289 parser.error('Commit mode and --ignore-masked are not compatible')
290 if opts.without_mask:
291 parser.error('Commit mode and --without-mask are not compatible')
293 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
294 for val in range(opts.verbosity):
295 logger = logging.getLogger()
296 logger.setLevel(logger.getEffectiveLevel() - 10)
298 for val in range(opts.quiet):
299 logger = logging.getLogger()
300 logger.setLevel(logger.getEffectiveLevel() + 10)
305 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
306 "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
307 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
308 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
309 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
310 "changelog.missing":"Missing ChangeLog files",
311 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
312 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
313 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
314 "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)",
315 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
316 "file.size":"Files in the files directory must be under 20 KiB",
317 "file.size.fatal":"Files in the files directory must be under 60 KiB",
318 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
319 "file.UTF8":"File is not UTF8 compliant",
320 "inherit.deprecated":"Ebuild inherits a deprecated eclass",
321 "inherit.missing":"Ebuild uses functions from an eclass but does not inherit it",
322 "inherit.unused":"Ebuild inherits an eclass but does not use it",
323 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
324 "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
325 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
326 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
327 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
328 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
329 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
330 "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
331 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
332 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
333 "EAPI.definition":"EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
334 "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
335 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
336 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
337 "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
338 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
339 "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
340 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
341 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
342 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
343 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
344 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
345 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
346 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
347 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
348 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
349 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
350 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
351 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
352 "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
353 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
354 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
355 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
356 "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
357 "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
358 "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
359 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
360 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
361 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
362 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
363 "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
364 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
365 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
366 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
367 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
368 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
369 "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
370 "variable.readonly":"Assigning a readonly variable",
371 "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
372 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
373 "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
374 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
375 "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
376 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
377 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
378 "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
379 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
380 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
381 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
382 "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
383 "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
384 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
385 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
386 "ebuild.badheader":"This ebuild has a malformed header",
387 "manifest.bad":"Manifest has missing or incorrect digests",
388 "metadata.missing":"Missing metadata.xml files",
389 "metadata.bad":"Bad metadata.xml files",
390 "metadata.warning":"Warnings in metadata.xml files",
391 "portage.internal":"The ebuild uses an internal Portage function",
392 "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
393 "virtual.suspect":"Ebuild contains a package that usually should be pulled via virtual/, not directly.",
394 "usage.obsolete":"The ebuild makes use of an obsolete construct",
395 "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
398 qacats = list(qahelp)
403 "changelog.notadded",
404 "dependency.unknown",
410 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
411 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
412 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
413 "DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde",
414 "DESCRIPTION.toolong",
431 "inherit.deprecated",
432 "java.eclassesnotused",
433 "wxwidgets.eclassnotused",
437 "upstream.workaround",
442 if portage.const._ENABLE_INHERIT_CHECK:
443 # This is experimental, so it's non-fatal.
444 qawarnings.add("inherit.missing")
446 non_ascii_re = re.compile(r'[^\x00-\x7f]')
448 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
449 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
450 allvars.update(Package.metadata_keys)
451 allvars = sorted(allvars)
453 for x in missingvars:
456 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
460 valid_restrict = frozenset(["binchecks", "bindist",
461 "fetch", "installsources", "mirror",
462 "primaryuri", "strip", "test", "userpriv"])
464 live_eclasses = frozenset([
475 suspect_rdepend = frozenset([
476 "app-arch/cabextract",
477 "app-arch/rpm2targz",
482 "dev-perl/extutils-pkgconfig",
488 "dev-util/gtk-doc-am",
491 "dev-util/pkg-config-lite",
493 "dev-util/pkgconfig",
494 "dev-util/pkgconfig-openbsd",
498 "media-gfx/ebdftopcf",
500 "sys-devel/autoconf",
501 "sys-devel/automake",
508 "virtual/linux-sources",
515 "dev-util/pkg-config-lite":"virtual/pkgconfig",
516 "dev-util/pkgconf":"virtual/pkgconfig",
517 "dev-util/pkgconfig":"virtual/pkgconfig",
518 "dev-util/pkgconfig-openbsd":"virtual/pkgconfig",
519 "dev-libs/libusb":"virtual/libusb",
520 "dev-libs/libusbx":"virtual/libusb",
521 "dev-libs/libusb-compat":"virtual/libusb",
524 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
525 # force refetch if the local copy creation time is older than this
526 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
529 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
531 options, arguments = ParseArgs(sys.argv, qahelp)
534 print("Portage", portage.VERSION)
537 # Set this to False when an extraordinary issue (generally
538 # something other than a QA issue) makes it impossible to
539 # commit (like if Manifest generation fails).
542 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
546 myreporoot = os.path.basename(portdir_overlay)
547 myreporoot += mydir[len(portdir_overlay):]
550 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
555 vcses = utilities.FindVCS()
557 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
558 print(red('*** Please either clean up your workdir or specify --vcs option.'))
565 if options.if_modified == "y" and vcs is None:
566 logging.info("Not in a version controlled repository; "
567 "disabling --if-modified.")
568 options.if_modified = "n"
570 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
571 vcs_preserves_mtime = vcs in ('cvs',)
573 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
574 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
575 if vcs_global_opts is None:
576 if vcs in ('cvs', 'svn'):
577 vcs_global_opts = "-q"
580 vcs_global_opts = vcs_global_opts.split()
582 if options.mode == 'commit' and not options.pretend and not vcs:
583 logging.info("Not in a version controlled repository; enabling pretend mode.")
584 options.pretend = True
586 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
587 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
588 (repoman_settings.get('PORTDIR_OVERLAY', ''),
589 portage._shell_quote(portdir_overlay))
590 # We have to call the config constructor again so
591 # that config.repositories is initialized correctly.
592 repoman_settings = portage.config(config_root=config_root, local_config=False,
593 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
595 root = repoman_settings['EROOT']
597 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
599 portdb = trees[root]['porttree'].dbapi
601 # Constrain dependency resolution to the master(s)
602 # that are specified in layout.conf.
603 repodir = os.path.realpath(portdir_overlay)
604 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
605 portdb.porttrees = list(repo_config.eclass_db.porttrees)
606 portdir = portdb.porttrees[0]
608 if repo_config.allow_provide_virtual:
609 qawarnings.add("virtual.oldstyle")
611 if repo_config.sign_commit:
613 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
614 # the commit arguments. If key_id is unspecified, then it must be
615 # configured by `git config user.signingkey key_id`.
616 vcs_local_opts.append("--gpg-sign")
618 # In order to disable manifest signatures, repos may set
619 # "sign-manifests = false" in metadata/layout.conf. This
620 # can be used to prevent merge conflicts like those that
621 # thin-manifests is designed to prevent.
622 sign_manifests = "sign" in repoman_settings.features and \
623 repo_config.sign_manifest
625 manifest_hashes = repo_config.manifest_hashes
626 if manifest_hashes is None:
627 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
629 if options.mode in ("commit", "fix", "manifest"):
630 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
631 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
632 "metadata/layout.conf does not contain the '%s' hash which "
633 "is required by this portage version. You will have to "
634 "upgrade portage if you want to generate valid manifests for "
635 "this repository.") % \
636 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
637 for line in textwrap.wrap(msg, 70):
641 unsupported_hashes = manifest_hashes.difference(
642 portage.const.MANIFEST2_HASH_FUNCTIONS)
643 if unsupported_hashes:
644 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
645 "metadata/layout.conf contains one or more hash types '%s' "
646 "which are not supported by this portage version. You will "
647 "have to upgrade portage if you want to generate valid "
648 "manifests for this repository.") % \
649 (repo_config.name, " ".join(sorted(unsupported_hashes)))
650 for line in textwrap.wrap(msg, 70):
654 if "commit" == options.mode and \
655 repo_config.name == "gentoo" and \
656 "RMD160" in manifest_hashes and \
657 "RMD160" not in portage.checksum.hashorigin_map:
658 msg = "Please install " \
659 "pycrypto or enable python's ssl USE flag in order " \
660 "to enable RMD160 hash support. See bug #198398 for " \
663 for line in textwrap.wrap(msg, 70):
667 if options.echangelog is None and repo_config.update_changelog:
668 options.echangelog = 'y'
671 options.echangelog = 'n'
673 # The --echangelog option causes automatic ChangeLog generation,
674 # which invalidates changelog.ebuildadded and changelog.missing
676 # Note: Some don't use ChangeLogs in distributed SCMs.
677 # It will be generated on server side from scm log,
678 # before package moves to the rsync server.
679 # This is needed because they try to avoid merge collisions.
680 # Gentoo's Council decided to always use the ChangeLog file.
681 # TODO: shouldn't this just be switched on the repo, iso the VCS?
682 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
684 if 'digest' in repoman_settings.features and options.digest != 'n':
687 logging.debug("vcs: %s" % (vcs,))
688 logging.debug("repo config: %s" % (repo_config,))
689 logging.debug("options: %s" % (options,))
691 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
692 # profile-specific config constructor calls.
693 env = os.environ.copy()
694 env['PORTDIR'] = portdir
695 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
697 logging.info('Setting paths:')
698 logging.info('PORTDIR = "' + portdir + '"')
699 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
701 # It's confusing if these warnings are displayed without the user
702 # being told which profile they come from, so disable them.
703 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
706 for path in repo_config.eclass_db.porttrees:
707 categories.extend(portage.util.grabfile(
708 os.path.join(path, 'profiles', 'categories')))
709 repoman_settings.categories = frozenset(
710 portage.util.stack_lists([categories], incremental=1))
711 categories = repoman_settings.categories
713 portdb.settings = repoman_settings
714 root_config = RootConfig(repoman_settings, trees[root], None)
715 # We really only need to cache the metadata that's necessary for visibility
716 # filtering. Anything else can be discarded to reduce memory consumption.
717 portdb._aux_cache_keys.clear()
718 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
720 reposplit = myreporoot.split(os.path.sep)
721 repolevel = len(reposplit)
723 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
724 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
725 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
726 if options.mode == 'commit' and repolevel not in [1,2,3]:
727 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
728 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
729 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
731 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
733 # Make startdir relative to the canonical repodir, so that we can pass
734 # it to digestgen and it won't have to be canonicalized again.
738 startdir = normalize_path(mydir)
739 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2-repolevel+3:])
742 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.")
744 def repoman_getstatusoutput(cmd):
746 Implements an interface similar to getstatusoutput(), but with
747 customized unicode handling (see bug #310789) and without the shell.
749 args = portage.util.shlex_split(cmd)
750 encoding = _encodings['fs']
751 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
752 # Python 3.1 does not support bytes in Popen args.
753 args = [_unicode_encode(x,
754 encoding=encoding, errors='strict') for x in args]
755 proc = subprocess.Popen(args, stdout=subprocess.PIPE,
756 stderr=subprocess.STDOUT)
757 output = portage._unicode_decode(proc.communicate()[0],
758 encoding=encoding, errors='strict')
759 if output and output[-1] == "\n":
760 # getstatusoutput strips one newline
762 return (proc.wait(), output)
764 class repoman_popen(portage.proxy.objectproxy.ObjectProxy):
766 Implements an interface similar to os.popen(), but with customized
767 unicode handling (see bug #310789) and without the shell.
770 __slots__ = ('_proc', '_stdout')
772 def __init__(self, cmd):
773 args = portage.util.shlex_split(cmd)
774 encoding = _encodings['fs']
775 if sys.hexversion < 0x3000000 or sys.hexversion >= 0x3020000:
776 # Python 3.1 does not support bytes in Popen args.
777 args = [_unicode_encode(x,
778 encoding=encoding, errors='strict') for x in args]
779 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
780 object.__setattr__(self, '_proc', proc)
781 object.__setattr__(self, '_stdout',
782 codecs.getreader(encoding)(proc.stdout, 'strict'))
784 def _get_target(self):
785 return object.__getattribute__(self, '_stdout')
787 __enter__ = _get_target
789 def __exit__(self, exc_type, exc_value, traceback):
790 proc = object.__getattribute__(self, '_proc')
794 class ProfileDesc(object):
795 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
796 def __init__(self, arch, status, sub_path, tree_path):
800 sub_path = normalize_path(sub_path.lstrip(os.sep))
801 self.sub_path = sub_path
802 self.tree_path = tree_path
804 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
806 self.abs_path = tree_path
811 return 'empty profile'
814 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
816 # get lists of valid keywords, licenses, and use
820 global_pmasklines = []
822 for path in portdb.porttrees:
824 liclist.update(os.listdir(os.path.join(path, "licenses")))
827 kwlist.update(portage.grabfile(os.path.join(path,
828 "profiles", "arch.list")))
830 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
836 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
838 expand_list = os.listdir(expand_desc_dir)
842 for fn in expand_list:
843 if not fn[-5:] == '.desc':
845 use_prefix = fn[:-5].lower() + '_'
846 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
849 uselist.add(use_prefix + x[0])
851 global_pmasklines.append(portage.util.grabfile_package(
852 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
854 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
856 desc_file = io.open(_unicode_encode(desc_path,
857 encoding=_encodings['fs'], errors='strict'),
858 mode='r', encoding=_encodings['repo.content'], errors='replace')
859 except EnvironmentError:
862 for i, x in enumerate(desc_file):
869 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
870 desc_path + " line %d" % (i+1, ))
871 elif arch[0] not in kwlist:
872 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
873 desc_path + " line %d" % (i+1, ))
874 elif arch[2] not in valid_profile_types:
875 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
876 desc_path + " line %d" % (i+1, ))
877 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
878 if not os.path.isdir(profile_desc.abs_path):
880 "Invalid %s profile (%s) for arch %s in %s line %d",
881 arch[2], arch[1], arch[0], desc_path, i+1)
884 os.path.join(profile_desc.abs_path, 'deprecated')):
886 profile_list.append(profile_desc)
889 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
890 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
892 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
893 global_pmaskdict = {}
894 for x in global_pmasklines:
895 global_pmaskdict.setdefault(x.cp, []).append(x)
896 del global_pmasklines
898 def has_global_mask(pkg):
899 mask_atoms = global_pmaskdict.get(pkg.cp)
903 if portage.dep.match_from_list(x, pkg_list):
907 # Ensure that profile sub_path attributes are unique. Process in reverse order
908 # so that profiles with duplicate sub_path from overlays will override
909 # profiles with the same sub_path from parent repos.
911 profile_list.reverse()
912 profile_sub_paths = set()
913 for prof in profile_list:
914 if prof.sub_path in profile_sub_paths:
916 profile_sub_paths.add(prof.sub_path)
917 profiles.setdefault(prof.arch, []).append(prof)
919 # Use an empty profile for checking dependencies of
920 # packages that have empty KEYWORDS.
921 prof = ProfileDesc('**', 'stable', '', '')
922 profiles.setdefault(prof.arch, []).append(prof)
924 for x in repoman_settings.archlist():
927 if x not in profiles:
928 print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
929 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
930 print(red("up with the "+x+" team."))
934 logging.fatal("Couldn't find licenses?")
938 logging.fatal("Couldn't read KEYWORDS from arch.list")
942 logging.fatal("Couldn't find use.desc?")
947 #we are inside a category directory
949 if catdir not in categories:
951 mydirlist=os.listdir(startdir)
953 if x == "CVS" or x.startswith("."):
955 if os.path.isdir(startdir+"/"+x):
956 scanlist.append(catdir+"/"+x)
957 repo_subdir = catdir + os.sep
960 if not os.path.isdir(startdir+"/"+x):
962 for y in os.listdir(startdir+"/"+x):
963 if y == "CVS" or y.startswith("."):
965 if os.path.isdir(startdir+"/"+x+"/"+y):
966 scanlist.append(x+"/"+y)
969 catdir = reposplit[-2]
970 if catdir not in categories:
972 scanlist.append(catdir+"/"+reposplit[-1])
973 repo_subdir = scanlist[-1] + os.sep
975 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
976 ' from the current working directory'
977 logging.critical(msg)
980 repo_subdir_len = len(repo_subdir)
983 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
985 def vcs_files_to_cps(vcs_file_iter):
987 Iterate over the given modified file paths returned from the vcs,
988 and return a frozenset containing category/pn strings for each
995 if reposplit[-2] in categories and \
996 next(vcs_file_iter, None) is not None:
997 modified_cps.append("/".join(reposplit[-2:]))
1000 category = reposplit[-1]
1001 if category in categories:
1002 for filename in vcs_file_iter:
1003 f_split = filename.split(os.sep)
1005 if len(f_split) > 2:
1006 modified_cps.append(category + "/" + f_split[1])
1010 for filename in vcs_file_iter:
1011 f_split = filename.split(os.sep)
1012 # ['.', category, pn,...]
1013 if len(f_split) > 3 and f_split[1] in categories:
1014 modified_cps.append("/".join(f_split[1:3]))
1016 return frozenset(modified_cps)
1018 def git_supports_gpg_sign():
1019 status, cmd_output = \
1020 repoman_getstatusoutput("git --version")
1021 cmd_output = cmd_output.split()
1023 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
1024 if version is not None:
1025 version = [int(x) for x in version.groups()[1:]]
1026 if version[0] > 1 or \
1027 (version[0] == 1 and version[1] > 7) or \
1028 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
1032 def dev_keywords(profiles):
1034 Create a set of KEYWORDS values that exist in 'dev'
1035 profiles. These are used
1036 to trigger a message notifying the user when they might
1037 want to add the --include-dev option.
1040 for arch, arch_profiles in profiles.items():
1041 for prof in arch_profiles:
1042 arch_set = type_arch_map.get(prof.status)
1043 if arch_set is None:
1045 type_arch_map[prof.status] = arch_set
1048 dev_keywords = type_arch_map.get('dev', set())
1049 dev_keywords.update(['~' + arch for arch in dev_keywords])
1050 return frozenset(dev_keywords)
1052 dev_keywords = dev_keywords(profiles)
1057 # provided by the desktop-file-utils package
1058 desktop_file_validate = find_binary("desktop-file-validate")
1059 desktop_pattern = re.compile(r'.*\.desktop$')
1065 xmllint_capable = False
1066 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
1069 """Parse a RFC 822 date and time string.
1070 This is required for python3 compatibility, since the
1071 rfc822.parsedate() function is not available."""
1074 for x in s.upper().split():
1075 for y in x.split(','):
1079 if len(s_split) != 6:
1082 # %a, %d %b %Y %H:%M:%S %Z
1083 a, d, b, Y, H_M_S, Z = s_split
1085 # Convert month to integer, since strptime %w is locale-dependent.
1086 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
1087 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
1088 m = month_map.get(b)
1091 m = str(m).rjust(2, '0')
1093 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
1095 def fetch_metadata_dtd():
1097 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1098 metadata_dtd_ctime_interval.
1100 @return: True if successful, otherwise False
1104 metadata_dtd_st = None
1105 current_time = int(time.time())
1107 metadata_dtd_st = os.stat(metadata_dtd)
1108 except EnvironmentError as e:
1109 if e.errno not in (errno.ENOENT, errno.ESTALE):
1113 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1114 if abs(current_time - metadata_dtd_st.st_ctime) \
1115 < metadata_dtd_ctime_interval:
1120 print(green("***") + " the local copy of metadata.dtd " + \
1121 "needs to be refetched, doing that now")
1124 url_f = urllib_request_urlopen(metadata_dtd_uri)
1125 msg_info = url_f.info()
1126 last_modified = msg_info.get('last-modified')
1127 if last_modified is not None:
1128 last_modified = parsedate(last_modified)
1129 if last_modified is not None:
1130 last_modified = calendar.timegm(last_modified)
1132 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
1134 local_f = open(metadata_dtd_tmp, mode='wb')
1135 local_f.write(url_f.read())
1137 if last_modified is not None:
1139 os.utime(metadata_dtd_tmp,
1140 (int(last_modified), int(last_modified)))
1142 # This fails on some odd non-unix-like filesystems.
1143 # We don't really need the mtime to be preserved
1144 # anyway here (currently we use ctime to trigger
1145 # fetch), so just ignore it.
1147 os.rename(metadata_dtd_tmp, metadata_dtd)
1150 os.unlink(metadata_dtd_tmp)
1156 except EnvironmentError as e:
1158 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
1159 print(red("!!!")+" exception '%s' though." % (e,))
1160 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
1165 if options.mode == "manifest":
1167 elif not find_binary('xmllint'):
1168 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1169 if options.xml_parse or repolevel==3:
1170 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1173 if not fetch_metadata_dtd():
1175 #this can be problematic if xmllint changes their output
1176 xmllint_capable=True
1178 if options.mode == 'commit' and vcs:
1179 utilities.detect_vcs_conflicts(options, vcs)
1181 if options.mode == "manifest":
1183 elif options.pretend:
1184 print(green("\nRepoMan does a once-over of the neighborhood..."))
1186 print(green("\nRepoMan scours the neighborhood..."))
1189 modified_ebuilds = set()
1190 modified_changelogs = set()
1196 mycvstree = cvstree.getentries("./", recursive=1)
1197 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1198 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1199 if options.if_modified == "y":
1200 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1203 with repoman_popen("svn status") as f:
1204 svnstatus = f.readlines()
1205 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1206 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1207 if options.if_modified == "y":
1208 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1211 with repoman_popen("git diff-index --name-only "
1212 "--relative --diff-filter=M HEAD") as f:
1213 mychanged = f.readlines()
1214 mychanged = ["./" + elem[:-1] for elem in mychanged]
1216 with repoman_popen("git diff-index --name-only "
1217 "--relative --diff-filter=A HEAD") as f:
1218 mynew = f.readlines()
1219 mynew = ["./" + elem[:-1] for elem in mynew]
1220 if options.if_modified == "y":
1221 with repoman_popen("git diff-index --name-only "
1222 "--relative --diff-filter=D HEAD") as f:
1223 myremoved = f.readlines()
1224 myremoved = ["./" + elem[:-1] for elem in myremoved]
1227 with repoman_popen("bzr status -S .") as f:
1228 bzrstatus = f.readlines()
1229 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
1230 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
1231 if options.if_modified == "y":
1232 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" ) ]
1235 with repoman_popen("hg status --no-status --modified .") as f:
1236 mychanged = f.readlines()
1237 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1238 mynew = repoman_popen("hg status --no-status --added .").readlines()
1239 mynew = ["./" + elem.rstrip() for elem in mynew]
1240 if options.if_modified == "y":
1241 with repoman_popen("hg status --no-status --removed .") as f:
1242 myremoved = f.readlines()
1243 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1246 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1247 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1248 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1249 if os.path.basename(x) == "ChangeLog")
1251 def vcs_new_changed(relative_path):
1252 for x in chain(mychanged, mynew):
1253 if x == relative_path:
1257 have_pmasked = False
1258 have_dev_keywords = False
1261 # NOTE: match-all caches are not shared due to potential
1262 # differences between profiles in _get_implicit_iuse.
1264 arch_xmatch_caches = {}
1265 shared_xmatch_caches = {"cp-list":{}}
1267 # Disable the "ebuild.notadded" check when not in commit mode and
1268 # running `svn status` in every package dir will be too expensive.
1270 check_ebuild_notadded = not \
1271 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1273 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1274 thirdpartymirrors = []
1275 for v in repoman_settings.thirdpartymirrors().values():
1277 if not v.endswith("/"):
1279 thirdpartymirrors.append(v)
1281 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1283 Implements doctype() as required to avoid deprecation warnings with
1286 def doctype(self, name, pubid, system):
1290 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1291 except (EnvironmentError, ParseError, PermissionDenied) as e:
1293 except FileNotFound:
1294 # TODO: Download as we do for metadata.dtd, but add a way to
1295 # disable for non-gentoo repoman users who may not have herds.
1298 effective_scanlist = scanlist
1299 if options.if_modified == "y":
1300 effective_scanlist = sorted(vcs_files_to_cps(
1301 chain(mychanged, mynew, myremoved)))
1303 for x in effective_scanlist:
1304 #ebuilds and digests added to cvs respectively.
1305 logging.info("checking package %s" % x)
1306 # save memory by discarding xmatch caches from previous package(s)
1307 arch_xmatch_caches.clear()
1309 catdir,pkgdir=x.split("/")
1310 checkdir=repodir+"/"+x
1311 checkdir_relative = ""
1313 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1315 checkdir_relative = os.path.join(catdir, checkdir_relative)
1316 checkdir_relative = os.path.join(".", checkdir_relative)
1317 generated_manifest = False
1319 if options.mode == "manifest" or \
1320 (options.mode != 'manifest-check' and options.digest == 'y') or \
1321 options.mode in ('commit', 'fix') and not options.pretend:
1322 auto_assumed = set()
1323 fetchlist_dict = portage.FetchlistDict(checkdir,
1324 repoman_settings, portdb)
1325 if options.mode == 'manifest' and options.force:
1326 portage._doebuild_manifest_exempt_depend += 1
1328 distdir = repoman_settings['DISTDIR']
1329 mf = repoman_settings.repositories.get_repo_for_location(
1330 os.path.dirname(os.path.dirname(checkdir)))
1331 mf = mf.load_manifest(checkdir, distdir,
1332 fetchlist_dict=fetchlist_dict)
1333 mf.create(requiredDistfiles=None,
1334 assumeDistHashesAlways=True)
1335 for distfiles in fetchlist_dict.values():
1336 for distfile in distfiles:
1337 if os.path.isfile(os.path.join(distdir, distfile)):
1338 mf.fhashdict['DIST'].pop(distfile, None)
1340 auto_assumed.add(distfile)
1343 portage._doebuild_manifest_exempt_depend -= 1
1345 repoman_settings["O"] = checkdir
1347 generated_manifest = digestgen(
1348 mysettings=repoman_settings, myportdb=portdb)
1349 except portage.exception.PermissionDenied as e:
1350 generated_manifest = False
1351 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1352 level=logging.ERROR, noiselevel=-1)
1354 if not generated_manifest:
1355 print("Unable to generate manifest.")
1358 if options.mode == "manifest":
1359 if not dofail and options.force and auto_assumed and \
1360 'assume-digests' in repoman_settings.features:
1361 # Show which digests were assumed despite the --force option
1362 # being given. This output will already have been shown by
1363 # digestgen() if assume-digests is not enabled, so only show
1364 # it here if assume-digests is enabled.
1365 pkgs = list(fetchlist_dict)
1367 portage.writemsg_stdout(" digest.assumed" + \
1368 portage.output.colorize("WARN",
1369 str(len(auto_assumed)).rjust(18)) + "\n")
1371 fetchmap = fetchlist_dict[cpv]
1372 pf = portage.catsplit(cpv)[1]
1373 for distfile in sorted(fetchmap):
1374 if distfile in auto_assumed:
1375 portage.writemsg_stdout(
1376 " %s::%s\n" % (pf, distfile))
1381 if not generated_manifest:
1382 repoman_settings['O'] = checkdir
1383 repoman_settings['PORTAGE_QUIET'] = '1'
1384 if not portage.digestcheck([], repoman_settings, strict=1):
1385 stats["manifest.bad"] += 1
1386 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1387 repoman_settings.pop('PORTAGE_QUIET', None)
1389 if options.mode == 'manifest-check':
1392 checkdirlist=os.listdir(checkdir)
1396 for y in checkdirlist:
1397 if (y in no_exec or y.endswith(".ebuild")) and \
1398 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1399 stats["file.executable"] += 1
1400 fails["file.executable"].append(os.path.join(checkdir, y))
1401 if y.endswith(".ebuild"):
1403 ebuildlist.append(pf)
1404 cpv = "%s/%s" % (catdir, pf)
1406 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1409 stats["ebuild.syntax"] += 1
1410 fails["ebuild.syntax"].append(os.path.join(x, y))
1414 stats["ebuild.output"] += 1
1415 fails["ebuild.output"].append(os.path.join(x, y))
1417 if not portage.eapi_is_supported(myaux["EAPI"]):
1419 stats["EAPI.unsupported"] += 1
1420 fails["EAPI.unsupported"].append(os.path.join(x, y))
1422 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1423 root_config=root_config, type_name="ebuild")
1427 if len(pkgs) != len(ebuildlist):
1428 # If we can't access all the metadata then it's totally unsafe to
1429 # commit since there's no way to generate a correct Manifest.
1430 # Do not try to do any more QA checks on this package since missing
1431 # metadata leads to false positives for several checks, and false
1432 # positives confuse users.
1436 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1437 ebuildlist = sorted(pkgs.values())
1438 ebuildlist = [pkg.pf for pkg in ebuildlist]
1440 for y in checkdirlist:
1441 m = disallowed_filename_chars_re.search(y.strip(os.sep))
1443 y_relative = os.path.join(checkdir_relative, y)
1444 if vcs is not None and not vcs_new_changed(y_relative):
1445 # If the file isn't in the VCS new or changed set, then
1446 # assume that it's an irrelevant temporary file (Manifest
1447 # entries are not generated for file names containing
1448 # prohibited characters). See bug #406877.
1451 stats["file.name"] += 1
1452 fails["file.name"].append("%s/%s: char '%s'" % \
1453 (checkdir, y, m.group(0)))
1455 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1460 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1461 encoding=_encodings['fs'], errors='strict'),
1462 mode='r', encoding=_encodings['repo.content'])
1465 except UnicodeDecodeError as ue:
1466 stats["file.UTF8"] += 1
1467 s = ue.object[:ue.start]
1471 s = s[s.rfind("\n") + 1:]
1472 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1477 if vcs in ("git", "hg") and check_ebuild_notadded:
1479 myf = repoman_popen("git ls-files --others %s" % \
1480 (portage._shell_quote(checkdir_relative),))
1482 myf = repoman_popen("hg status --no-status --unknown %s" % \
1483 (portage._shell_quote(checkdir_relative),))
1485 if l[:-1][-7:] == ".ebuild":
1486 stats["ebuild.notadded"] += 1
1487 fails["ebuild.notadded"].append(
1488 os.path.join(x, os.path.basename(l[:-1])))
1491 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1494 myf=open(checkdir+"/CVS/Entries","r")
1496 myf = repoman_popen("svn status --depth=files --verbose " +
1497 portage._shell_quote(checkdir))
1499 myf = repoman_popen("bzr ls -v --kind=file " +
1500 portage._shell_quote(checkdir))
1501 myl = myf.readlines()
1507 splitl=l[1:].split("/")
1510 if splitl[0][-7:]==".ebuild":
1511 eadded.append(splitl[0][:-7])
1516 # tree conflict, new in subversion 1.6
1519 if l[-7:] == ".ebuild":
1520 eadded.append(os.path.basename(l[:-7]))
1525 if l[-7:] == ".ebuild":
1526 eadded.append(os.path.basename(l[:-7]))
1528 myf = repoman_popen("svn status " +
1529 portage._shell_quote(checkdir))
1534 l = l.rstrip().split(' ')[-1]
1535 if l[-7:] == ".ebuild":
1536 eadded.append(os.path.basename(l[:-7]))
1539 stats["CVS/Entries.IO_error"] += 1
1540 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
1545 mf = repoman_settings.repositories.get_repo_for_location(
1546 os.path.dirname(os.path.dirname(checkdir)))
1547 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1548 mydigests=mf.getTypeDigests("DIST")
1550 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1552 src_uri_error = False
1553 for mykey in fetchlist_dict:
1555 myfiles_all.extend(fetchlist_dict[mykey])
1556 except portage.exception.InvalidDependString as e:
1557 src_uri_error = True
1559 portdb.aux_get(mykey, ["SRC_URI"])
1561 # This will be reported as an "ebuild.syntax" error.
1564 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
1565 fails["SRC_URI.syntax"].append(
1566 "%s.ebuild SRC_URI: %s" % (mykey, e))
1568 if not src_uri_error:
1569 # This test can produce false positives if SRC_URI could not
1570 # be parsed for one or more ebuilds. There's no point in
1571 # producing a false error here since the root cause will
1572 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1573 # or "ebuild.sytax".
1574 myfiles_all = set(myfiles_all)
1575 for entry in mydigests:
1576 if entry not in myfiles_all:
1577 stats["digest.unused"] += 1
1578 fails["digest.unused"].append(checkdir+"::"+entry)
1579 for entry in myfiles_all:
1580 if entry not in mydigests:
1581 stats["digest.missing"] += 1
1582 fails["digest.missing"].append(checkdir+"::"+entry)
1585 if os.path.exists(checkdir+"/files"):
1586 filesdirlist=os.listdir(checkdir+"/files")
1588 # recurse through files directory
1589 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1591 y = filesdirlist.pop(0)
1592 relative_path = os.path.join(x, "files", y)
1593 full_path = os.path.join(repodir, relative_path)
1595 mystat = os.stat(full_path)
1596 except OSError as oe:
1598 # don't worry about it. it likely was removed via fix above.
1602 if S_ISDIR(mystat.st_mode):
1603 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1604 if y == "CVS" or y == ".svn":
1606 for z in os.listdir(checkdir+"/files/"+y):
1607 if z == "CVS" or z == ".svn":
1609 filesdirlist.append(y+"/"+z)
1610 # Current policy is no files over 20 KiB, these are the checks. File size between
1611 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1612 elif mystat.st_size > 61440:
1613 stats["file.size.fatal"] += 1
1614 fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1615 elif mystat.st_size > 20480:
1616 stats["file.size"] += 1
1617 fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1619 m = disallowed_filename_chars_re.search(
1620 os.path.basename(y.rstrip(os.sep)))
1622 y_relative = os.path.join(checkdir_relative, "files", y)
1623 if vcs is not None and not vcs_new_changed(y_relative):
1624 # If the file isn't in the VCS new or changed set, then
1625 # assume that it's an irrelevant temporary file (Manifest
1626 # entries are not generated for file names containing
1627 # prohibited characters). See bug #406877.
1630 stats["file.name"] += 1
1631 fails["file.name"].append("%s/files/%s: char '%s'" % \
1632 (checkdir, y, m.group(0)))
1634 if desktop_file_validate and desktop_pattern.match(y):
1635 cmd_output = validate_desktop_entry(full_path)
1637 # Note: in the future we may want to grab the
1638 # warnings in addition to the errors. We're
1639 # just doing errors now since we don't want
1640 # to generate too much noise at first.
1641 error_re = re.compile(r'.*\s*error:\s*(.*)')
1642 for line in cmd_output:
1643 error_match = error_re.match(line)
1644 if error_match is None:
1646 stats["desktop.invalid"] += 1
1647 fails["desktop.invalid"].append(
1648 relative_path + ': %s' % error_match.group(1))
1652 if check_changelog and "ChangeLog" not in checkdirlist:
1653 stats["changelog.missing"]+=1
1654 fails["changelog.missing"].append(x+"/ChangeLog")
1657 #metadata.xml file check
1658 if "metadata.xml" not in checkdirlist:
1659 stats["metadata.missing"]+=1
1660 fails["metadata.missing"].append(x+"/metadata.xml")
1661 #metadata.xml parse check
1663 metadata_bad = False
1665 # read metadata.xml into memory
1667 _metadata_xml = xml.etree.ElementTree.parse(
1668 _unicode_encode(os.path.join(checkdir, "metadata.xml"),
1669 encoding=_encodings['fs'], errors='strict'),
1670 parser=xml.etree.ElementTree.XMLParser(
1671 target=_MetadataTreeBuilder()))
1672 except (ExpatError, SyntaxError, EnvironmentError) as e:
1674 stats["metadata.bad"] += 1
1675 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1678 # load USE flags from metadata.xml
1680 musedict = utilities.parse_metadata_use(_metadata_xml)
1681 except portage.exception.ParseError as e:
1683 stats["metadata.bad"] += 1
1684 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1686 # Run other metadata.xml checkers
1688 utilities.check_metadata(_metadata_xml, herd_base)
1689 except (utilities.UnknownHerdsError, ) as e:
1691 stats["metadata.bad"] += 1
1692 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1695 #Only carry out if in package directory or check forced
1696 if xmllint_capable and not metadata_bad:
1697 # xmlint can produce garbage output even on success, so only dump
1698 # the ouput when it fails.
1699 st, out = repoman_getstatusoutput(
1700 "xmllint --nonet --noout --dtdvalid %s %s" % \
1701 (portage._shell_quote(metadata_dtd),
1702 portage._shell_quote(os.path.join(checkdir, "metadata.xml"))))
1704 print(red("!!!") + " metadata.xml is invalid:")
1705 for z in out.splitlines():
1706 print(red("!!! ")+z)
1707 stats["metadata.bad"]+=1
1708 fails["metadata.bad"].append(x+"/metadata.xml")
1711 muselist = frozenset(musedict)
1713 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1714 changelog_modified = changelog_path in modified_changelogs
1716 # detect unused local USE-descriptions
1717 used_useflags = set()
1719 for y in ebuildlist:
1720 relative_path = os.path.join(x, y + ".ebuild")
1721 full_path = os.path.join(repodir, relative_path)
1722 ebuild_path = y + ".ebuild"
1724 ebuild_path = os.path.join(pkgdir, ebuild_path)
1726 ebuild_path = os.path.join(catdir, ebuild_path)
1727 ebuild_path = os.path.join(".", ebuild_path)
1728 if check_changelog and not changelog_modified \
1729 and ebuild_path in new_ebuilds:
1730 stats['changelog.ebuildadded'] += 1
1731 fails['changelog.ebuildadded'].append(relative_path)
1733 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1734 #ebuild not added to vcs
1735 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1736 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1737 myesplit=portage.pkgsplit(y)
1738 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1739 or pv_toolong_re.search(myesplit[1]) \
1740 or pv_toolong_re.search(myesplit[2]):
1741 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1742 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1744 elif myesplit[0]!=pkgdir:
1745 print(pkgdir,myesplit[0])
1746 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1747 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1754 for k, msgs in pkg.invalid.items():
1756 stats[k] = stats[k] + 1
1757 fails[k].append("%s %s" % (relative_path, msg))
1760 myaux = pkg.metadata
1761 eapi = myaux["EAPI"]
1762 inherited = pkg.inherited
1763 live_ebuild = live_eclasses.intersection(inherited)
1765 for k, v in myaux.items():
1766 if not isinstance(v, basestring):
1768 m = non_ascii_re.search(v)
1770 stats["variable.invalidchar"] += 1
1771 fails["variable.invalidchar"].append(
1772 ("%s: %s variable contains non-ASCII " + \
1773 "character at position %s") % \
1774 (relative_path, k, m.start() + 1))
1776 if not src_uri_error:
1777 # Check that URIs don't reference a server from thirdpartymirrors.
1778 for uri in portage.dep.use_reduce( \
1779 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1780 contains_mirror = False
1781 for mirror in thirdpartymirrors:
1782 if uri.startswith(mirror):
1783 contains_mirror = True
1785 if not contains_mirror:
1788 stats["SRC_URI.mirror"] += 1
1789 fails["SRC_URI.mirror"].append(
1790 "%s: '%s' found in thirdpartymirrors" % \
1791 (relative_path, mirror))
1793 if myaux.get("PROVIDE"):
1794 stats["virtual.oldstyle"]+=1
1795 fails["virtual.oldstyle"].append(relative_path)
1797 for pos, missing_var in enumerate(missingvars):
1798 if not myaux.get(missing_var):
1799 if catdir == "virtual" and \
1800 missing_var in ("HOMEPAGE", "LICENSE"):
1802 if live_ebuild and missing_var == "KEYWORDS":
1804 myqakey=missingvars[pos]+".missing"
1805 stats[myqakey]=stats[myqakey]+1
1806 fails[myqakey].append(x+"/"+y+".ebuild")
1808 if catdir == "virtual":
1809 for var in ("HOMEPAGE", "LICENSE"):
1811 myqakey = var + ".virtual"
1812 stats[myqakey] = stats[myqakey] + 1
1813 fails[myqakey].append(relative_path)
1815 # 14 is the length of DESCRIPTION=""
1816 if len(myaux['DESCRIPTION']) > max_desc_len:
1817 stats['DESCRIPTION.toolong'] += 1
1818 fails['DESCRIPTION.toolong'].append(
1819 "%s: DESCRIPTION is %d characters (max %d)" % \
1820 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1822 keywords = myaux["KEYWORDS"].split()
1823 stable_keywords = []
1824 for keyword in keywords:
1825 if not keyword.startswith("~") and \
1826 not keyword.startswith("-"):
1827 stable_keywords.append(keyword)
1829 if ebuild_path in new_ebuilds:
1830 stable_keywords.sort()
1831 stats["KEYWORDS.stable"] += 1
1832 fails["KEYWORDS.stable"].append(
1833 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1834 " ".join(stable_keywords))
1836 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1837 if not kw.startswith("-"))
1839 previous_keywords = slot_keywords.get(myaux["SLOT"])
1840 if previous_keywords is None:
1841 slot_keywords[myaux["SLOT"]] = set()
1842 elif ebuild_archs and not live_ebuild:
1843 dropped_keywords = previous_keywords.difference(ebuild_archs)
1844 if dropped_keywords:
1845 stats["KEYWORDS.dropped"] += 1
1846 fails["KEYWORDS.dropped"].append(
1847 relative_path + ": %s" % \
1848 " ".join(sorted(dropped_keywords)))
1850 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1852 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1853 if "-*" in keywords:
1861 stats["KEYWORDS.stupid"] += 1
1862 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1865 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1866 not be allowed to be marked stable
1868 if live_ebuild and repo_config.name == "gentoo":
1869 bad_stable_keywords = []
1870 for keyword in keywords:
1871 if not keyword.startswith("~") and \
1872 not keyword.startswith("-"):
1873 bad_stable_keywords.append(keyword)
1875 if bad_stable_keywords:
1876 stats["LIVEVCS.stable"] += 1
1877 fails["LIVEVCS.stable"].append(
1878 x + "/" + y + ".ebuild with stable keywords:%s " % \
1879 bad_stable_keywords)
1880 del bad_stable_keywords
1882 if keywords and not has_global_mask(pkg):
1883 stats["LIVEVCS.unmasked"] += 1
1884 fails["LIVEVCS.unmasked"].append(relative_path)
1886 if options.ignore_arches:
1887 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1888 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1891 for keyword in myaux["KEYWORDS"].split():
1892 if (keyword[0]=="-"):
1894 elif (keyword[0]=="~"):
1895 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1897 arches.append([keyword, keyword, [keyword]])
1899 # Use an empty profile for checking dependencies of
1900 # packages that have empty KEYWORDS.
1901 arches.append(['**', '**', ['**']])
1903 unknown_pkgs = set()
1904 baddepsyntax = False
1905 badlicsyntax = False
1906 badprovsyntax = False
1907 catpkg = catdir+"/"+y
1909 inherited_java_eclass = "java-pkg-2" in inherited or \
1910 "java-pkg-opt-2" in inherited
1911 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1912 operator_tokens = set(["||", "(", ")"])
1913 type_list, badsyntax = [], []
1914 for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
1915 "LICENSE", "PROPERTIES", "PROVIDE"):
1916 mydepstr = myaux[mytype]
1919 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1920 token_class=portage.dep.Atom
1923 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1924 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1925 except portage.exception.InvalidDependString as e:
1927 badsyntax.append(str(e))
1929 if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1930 if mytype in ("RDEPEND", "PDEPEND") and \
1931 "test?" in mydepstr.split():
1932 stats[mytype + '.suspect'] += 1
1933 fails[mytype + '.suspect'].append(relative_path + \
1934 ": 'test?' USE conditional in %s" % mytype)
1940 # Skip dependency.unknown for blockers, so that we
1941 # don't encourage people to remove necessary blockers,
1942 # as discussed in bug #382407.
1943 if atom.blocker is None and \
1944 not portdb.xmatch("match-all", atom) and \
1945 not atom.cp.startswith("virtual/"):
1946 unknown_pkgs.add((mytype, atom.unevaluated_atom))
1948 is_blocker = atom.blocker
1950 if catdir != "virtual":
1951 if not is_blocker and \
1952 atom.cp in suspect_virtual:
1953 stats['virtual.suspect'] += 1
1954 fails['virtual.suspect'].append(
1956 ": %s: consider using '%s' instead of '%s'" %
1957 (mytype, suspect_virtual[atom.cp], atom))
1959 if mytype == "DEPEND" and \
1960 not is_blocker and \
1961 not inherited_java_eclass and \
1962 atom.cp == "virtual/jdk":
1963 stats['java.eclassesnotused'] += 1
1964 fails['java.eclassesnotused'].append(relative_path)
1965 elif mytype == "DEPEND" and \
1966 not is_blocker and \
1967 not inherited_wxwidgets_eclass and \
1968 atom.cp == "x11-libs/wxGTK":
1969 stats['wxwidgets.eclassnotused'] += 1
1970 fails['wxwidgets.eclassnotused'].append(
1971 relative_path + ": DEPENDs on x11-libs/wxGTK"
1972 " without inheriting wxwidgets.eclass")
1973 elif mytype in ("PDEPEND", "RDEPEND"):
1974 if not is_blocker and \
1975 atom.cp in suspect_rdepend:
1976 stats[mytype + '.suspect'] += 1
1977 fails[mytype + '.suspect'].append(
1978 relative_path + ": '%s'" % atom)
1980 if atom.operator == "~" and \
1981 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
1982 stats[mytype + '.badtilde'] += 1
1983 fails[mytype + '.badtilde'].append(
1984 (relative_path + ": %s uses the ~ operator"
1985 " with a non-zero revision:" + \
1986 " '%s'") % (mytype, atom))
1988 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1990 for m,b in zip(type_list, badsyntax):
1991 stats[m+".syntax"] += 1
1992 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1994 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
1995 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
1996 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1997 badlicsyntax = badlicsyntax > 0
1998 badprovsyntax = badprovsyntax > 0
2000 # uselist checks - global
2003 for myflag in myaux["IUSE"].split():
2004 flag_name = myflag.lstrip("+-")
2005 used_useflags.add(flag_name)
2006 if myflag != flag_name:
2007 default_use.append(myflag)
2008 if flag_name not in uselist:
2009 myuse.append(flag_name)
2011 # uselist checks - metadata
2012 for mypos in range(len(myuse)-1,-1,-1):
2013 if myuse[mypos] and (myuse[mypos] in muselist):
2016 if default_use and not eapi_has_iuse_defaults(eapi):
2017 for myflag in default_use:
2018 stats['EAPI.incompatible'] += 1
2019 fails['EAPI.incompatible'].append(
2020 (relative_path + ": IUSE defaults" + \
2021 " not supported with EAPI='%s':" + \
2022 " '%s'") % (eapi, myflag))
2024 for mypos in range(len(myuse)):
2025 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
2026 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
2029 if not badlicsyntax:
2030 # Parse the LICENSE variable, remove USE conditions and
2032 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
2033 # Check each entry to ensure that it exists in PORTDIR's
2034 # license directory.
2035 for lic in licenses:
2036 # Need to check for "||" manually as no portage
2037 # function will remove it without removing values.
2038 if lic not in liclist and lic != "||":
2039 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
2040 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
2043 myuse = myaux["KEYWORDS"].split()
2045 if mykey not in ("-*", "*", "~*"):
2047 if myskey[:1] == "-":
2049 if myskey[:1] == "~":
2051 if myskey not in kwlist:
2052 stats["KEYWORDS.invalid"] += 1
2053 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
2054 elif myskey not in profiles:
2055 stats["KEYWORDS.invalid"] += 1
2056 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
2061 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
2062 except portage.exception.InvalidDependString as e:
2063 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
2064 fails["RESTRICT.syntax"].append(
2065 "%s: RESTRICT: %s" % (relative_path, e))
2068 myrestrict = set(myrestrict)
2069 mybadrestrict = myrestrict.difference(valid_restrict)
2071 stats["RESTRICT.invalid"] += len(mybadrestrict)
2072 for mybad in mybadrestrict:
2073 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
2075 required_use = myaux["REQUIRED_USE"]
2077 if not eapi_has_required_use(eapi):
2078 stats['EAPI.incompatible'] += 1
2079 fails['EAPI.incompatible'].append(
2080 relative_path + ": REQUIRED_USE" + \
2081 " not supported with EAPI='%s'" % (eapi,))
2083 portage.dep.check_required_use(required_use, (),
2084 pkg.iuse.is_valid_flag, eapi=eapi)
2085 except portage.exception.InvalidDependString as e:
2086 stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
2087 fails["REQUIRED_USE.syntax"].append(
2088 "%s: REQUIRED_USE: %s" % (relative_path, e))
2092 relative_path = os.path.join(x, y + ".ebuild")
2093 full_path = os.path.join(repodir, relative_path)
2094 if not vcs_preserves_mtime:
2095 if ebuild_path not in new_ebuilds and \
2096 ebuild_path not in modified_ebuilds:
2099 # All ebuilds should have utf_8 encoding.
2100 f = io.open(_unicode_encode(full_path,
2101 encoding=_encodings['fs'], errors='strict'),
2102 mode='r', encoding=_encodings['repo.content'])
2104 for check_name, e in run_checks(f, pkg):
2105 stats[check_name] += 1
2106 fails[check_name].append(relative_path + ': %s' % e)
2109 except UnicodeDecodeError:
2110 # A file.UTF8 failure will have already been recorded above.
2114 # The dep_check() calls are the most expensive QA test. If --force
2115 # is enabled, there's no point in wasting time on these since the
2116 # user is intent on forcing the commit anyway.
2119 for keyword,arch,groups in arches:
2121 if arch not in profiles:
2122 # A missing profile will create an error further down
2123 # during the KEYWORDS verification.
2126 for prof in profiles[arch]:
2128 if prof.status not in ("stable", "dev") or \
2129 prof.status == "dev" and not options.include_dev:
2132 dep_settings = arch_caches.get(prof.sub_path)
2133 if dep_settings is None:
2134 dep_settings = portage.config(
2135 config_profile_path=prof.abs_path,
2136 config_incrementals=repoman_incrementals,
2137 config_root=config_root,
2139 _unmatched_removal=options.unmatched_removal,
2141 dep_settings.categories = repoman_settings.categories
2142 if options.without_mask:
2143 dep_settings._mask_manager_obj = \
2144 copy.deepcopy(dep_settings._mask_manager)
2145 dep_settings._mask_manager._pmaskdict.clear()
2146 arch_caches[prof.sub_path] = dep_settings
2148 xmatch_cache_key = (prof.sub_path, tuple(groups))
2149 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2153 xcache = portdb.xcache
2154 xcache.update(shared_xmatch_caches)
2155 arch_xmatch_caches[xmatch_cache_key] = xcache
2157 trees[root]["porttree"].settings = dep_settings
2158 portdb.settings = dep_settings
2159 portdb.xcache = xcache
2160 # for package.use.mask support inside dep_check
2161 dep_settings.setcpv(pkg)
2162 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2163 # just in case, prevent config.reset() from nuking these.
2164 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2166 if not baddepsyntax:
2167 ismasked = not ebuild_archs or \
2168 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2170 if not have_pmasked:
2171 have_pmasked = bool(dep_settings._getMaskAtom(
2172 pkg.cpv, pkg.metadata))
2173 if options.ignore_masked:
2175 #we are testing deps for a masked package; give it some lee-way
2177 matchmode = "minimum-all"
2180 matchmode = "minimum-visible"
2182 if not have_dev_keywords:
2183 have_dev_keywords = \
2184 bool(dev_keywords.intersection(keywords))
2186 if prof.status == "dev":
2187 suffix=suffix+"indev"
2189 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
2191 mykey=mytype+".bad"+suffix
2192 myvalue = myaux[mytype]
2196 success, atoms = portage.dep_check(myvalue, portdb,
2197 dep_settings, use="all", mode=matchmode,
2203 # Don't bother with dependency.unknown for
2204 # cases in which *DEPEND.bad is triggered.
2206 # dep_check returns all blockers and they
2207 # aren't counted for *DEPEND.bad, so we
2209 if not atom.blocker:
2210 unknown_pkgs.discard(
2211 (mytype, atom.unevaluated_atom))
2213 if not prof.sub_path:
2214 # old-style virtuals currently aren't
2215 # resolvable with empty profile, since
2216 # 'virtuals' mappings are unavailable
2217 # (it would be expensive to search
2218 # for PROVIDE in all ebuilds)
2219 atoms = [atom for atom in atoms if not \
2220 (atom.cp.startswith('virtual/') and \
2221 not portdb.cp_list(atom.cp))]
2223 #we have some unsolvable deps
2224 #remove ! deps, which always show up as unsatisfiable
2225 atoms = [str(atom.unevaluated_atom) \
2226 for atom in atoms if not atom.blocker]
2228 #if we emptied out our list, continue:
2231 stats[mykey]=stats[mykey]+1
2232 fails[mykey].append("%s: %s(%s) %s" % \
2233 (relative_path, keyword,
2236 stats[mykey]=stats[mykey]+1
2237 fails[mykey].append("%s: %s(%s) %s" % \
2238 (relative_path, keyword,
2241 if not baddepsyntax and unknown_pkgs:
2243 for mytype, atom in unknown_pkgs:
2244 type_map.setdefault(mytype, set()).add(atom)
2245 for mytype, atoms in type_map.items():
2246 stats["dependency.unknown"] += 1
2247 fails["dependency.unknown"].append("%s: %s: %s" %
2248 (relative_path, mytype, ", ".join(sorted(atoms))))
2250 # check if there are unused local USE-descriptions in metadata.xml
2251 # (unless there are any invalids, to avoid noise)
2253 for myflag in muselist.difference(used_useflags):
2254 stats["metadata.warning"] += 1
2255 fails["metadata.warning"].append(
2256 "%s/metadata.xml: unused local USE-description: '%s'" % \
2259 if options.if_modified == "y" and len(effective_scanlist) < 1:
2260 logging.warn("--if-modified is enabled, but no modified packages were found!")
2262 if options.mode == "manifest":
2265 #dofail will be set to 1 if we have failed in at least one non-warning category
2267 #dowarn will be set to 1 if we tripped any warnings
2269 #dofull will be set if we should print a "repoman full" informational message
2270 dofull = options.mode != 'full'
2276 if x not in qawarnings:
2280 (dowarn and not (options.quiet or options.mode == "scan")):
2283 # Save QA output so that it can be conveniently displayed
2284 # in $EDITOR while the user creates a commit message.
2285 # Otherwise, the user would not be able to see this output
2286 # once the editor has taken over the screen.
2287 qa_output = io.StringIO()
2288 style_file = ConsoleStyleFile(sys.stdout)
2289 if options.mode == 'commit' and \
2290 (not commitmessage or not commitmessage.strip()):
2291 style_file.write_listener = qa_output
2292 console_writer = StyleWriter(file=style_file, maxcol=9999)
2293 console_writer.style_listener = style_file.new_styles
2295 f = formatter.AbstractFormatter(console_writer)
2297 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2300 del console_writer, f, style_file
2301 qa_output = qa_output.getvalue()
2302 qa_output = qa_output.splitlines(True)
2304 def grouplist(mylist,seperator="/"):
2305 """(list,seperator="/") -- Takes a list of elements; groups them into
2306 same initial element categories. Returns a dict of {base:[sublist]}
2307 From: ["blah/foo","spork/spatula","blah/weee/splat"]
2308 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
2311 xs=x.split(seperator)
2314 if xs[0] not in mygroups:
2315 mygroups[xs[0]]=[seperator.join(xs[1:])]
2317 mygroups[xs[0]]+=[seperator.join(xs[1:])]
2320 suggest_ignore_masked = False
2321 suggest_include_dev = False
2323 if have_pmasked and not (options.without_mask or options.ignore_masked):
2324 suggest_ignore_masked = True
2325 if have_dev_keywords and not options.include_dev:
2326 suggest_include_dev = True
2328 if suggest_ignore_masked or suggest_include_dev:
2330 if suggest_ignore_masked:
2331 print(bold("Note: use --without-mask to check " + \
2332 "KEYWORDS on dependencies of masked packages"))
2334 if suggest_include_dev:
2335 print(bold("Note: use --include-dev (-d) to check " + \
2336 "dependencies for 'dev' profiles"))
2339 if options.mode != 'commit':
2341 print(bold("Note: type \"repoman full\" for a complete listing."))
2342 if dowarn and not dofail:
2343 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.\"")
2345 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2347 print(bad("Please fix these important QA issues first."))
2348 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2351 if dofail and can_force and options.force and not options.pretend:
2352 print(green("RepoMan sez:") + \
2353 " \"You want to commit even with these QA issues?\n" + \
2354 " I'll take it this time, but I'm not happy.\"\n")
2356 if options.force and not can_force:
2357 print(bad("The --force option has been disabled due to extraordinary issues."))
2358 print(bad("Please fix these important QA issues first."))
2359 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2363 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2368 myvcstree=portage.cvstree.getentries("./",recursive=1)
2369 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
2370 except SystemExit as e:
2371 raise # TODO propagate this
2373 err("Error retrieving CVS tree; exiting.")
2376 with repoman_popen("svn status --no-ignore") as f:
2377 svnstatus = f.readlines()
2378 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
2379 except SystemExit as e:
2380 raise # TODO propagate this
2382 err("Error retrieving SVN info; exiting.")
2384 # get list of files not under version control or missing
2385 myf = repoman_popen("git ls-files --others")
2386 myunadded = [ "./" + elem[:-1] for elem in myf ]
2390 with repoman_popen("bzr status -S .") as f:
2391 bzrstatus = f.readlines()
2392 myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
2393 except SystemExit as e:
2394 raise # TODO propagate this
2396 err("Error retrieving bzr info; exiting.")
2398 with repoman_popen("hg status --no-status --unknown .") as f:
2399 myunadded = f.readlines()
2400 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2402 # Mercurial doesn't handle manually deleted files as removed from
2403 # the repository, so the user need to remove them before commit,
2404 # using "hg remove [FILES]"
2405 with repoman_popen("hg status --no-status --deleted .") as f:
2406 mydeleted = f.readlines()
2407 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2412 for x in range(len(myunadded)-1,-1,-1):
2413 xs=myunadded[x].split("/")
2415 print("!!! files dir is not added! Please correct this.")
2417 elif xs[-1]=="Manifest":
2418 # It's a manifest... auto add
2419 myautoadd+=[myunadded[x]]
2423 print(red("!!! The following files are in your local tree but are not added to the master"))
2424 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2431 if vcs == "hg" and mydeleted:
2432 print(red("!!! The following files are removed manually from your local tree but are not"))
2433 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2441 mycvstree = cvstree.getentries("./", recursive=1)
2442 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2443 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2444 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
2445 bin_blob_pattern = re.compile("^-kb$")
2446 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2447 recursive=1, basedir="./"))
2451 with repoman_popen("svn status") as f:
2452 svnstatus = f.readlines()
2453 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2454 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2455 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2457 # Subversion expands keywords specified in svn:keywords properties.
2458 with repoman_popen("svn propget -R svn:keywords") as f:
2459 props = f.readlines()
2460 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2461 for prop in props if " - " in prop)
2464 with repoman_popen("git diff-index --name-only "
2465 "--relative --diff-filter=M HEAD") as f:
2466 mychanged = f.readlines()
2467 mychanged = ["./" + elem[:-1] for elem in mychanged]
2469 with repoman_popen("git diff-index --name-only "
2470 "--relative --diff-filter=A HEAD") as f:
2471 mynew = f.readlines()
2472 mynew = ["./" + elem[:-1] for elem in mynew]
2474 with repoman_popen("git diff-index --name-only "
2475 "--relative --diff-filter=D HEAD") as f:
2476 myremoved = f.readlines()
2477 myremoved = ["./" + elem[:-1] for elem in myremoved]
2480 with repoman_popen("bzr status -S .") as f:
2481 bzrstatus = f.readlines()
2482 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
2483 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" ) ]
2484 myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
2485 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" ) ]
2486 # Bazaar expands nothing.
2489 with repoman_popen("hg status --no-status --modified .") as f:
2490 mychanged = f.readlines()
2491 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2493 with repoman_popen("hg status --no-status --added .") as f:
2494 mynew = f.readlines()
2495 mynew = ["./" + elem.rstrip() for elem in mynew]
2497 with repoman_popen("hg status --no-status --removed .") as f:
2498 myremoved = f.readlines()
2499 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2502 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2503 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2505 print("(Didn't find any changed files...)")
2509 # Manifests need to be regenerated after all other commits, so don't commit
2510 # them now even if they have changed.
2513 for f in mychanged + mynew:
2514 if "Manifest" == os.path.basename(f):
2518 myupdates.difference_update(myremoved)
2519 myupdates = list(myupdates)
2520 mymanifests = list(mymanifests)
2524 commitmessage = options.commitmsg
2525 if options.commitmsgfile:
2527 f = io.open(_unicode_encode(options.commitmsgfile,
2528 encoding=_encodings['fs'], errors='strict'),
2529 mode='r', encoding=_encodings['content'], errors='replace')
2530 commitmessage = f.read()
2533 except (IOError, OSError) as e:
2534 if e.errno == errno.ENOENT:
2535 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2538 # We've read the content so the file is no longer needed.
2539 commitmessagefile = None
2540 if not commitmessage or not commitmessage.strip():
2542 editor = os.environ.get("EDITOR")
2543 if editor and utilities.editor_is_executable(editor):
2544 commitmessage = utilities.get_commit_message_with_editor(
2545 editor, message=qa_output)
2547 commitmessage = utilities.get_commit_message_with_stdin()
2548 except KeyboardInterrupt:
2550 if not commitmessage or not commitmessage.strip():
2551 print("* no commit message? aborting commit.")
2553 commitmessage = commitmessage.rstrip()
2554 changelog_msg = commitmessage
2555 portage_version = getattr(portage, "VERSION", None)
2556 if portage_version is None:
2557 sys.stderr.write("Failed to insert portage version in message!\n")
2559 portage_version = "Unknown"
2560 unameout = platform.system() + " "
2561 if platform.system() in ["Darwin", "SunOS"]:
2562 unameout += platform.processor()
2564 unameout += platform.machine()
2565 commitmessage += "\n\n(Portage version: %s/%s/%s" % \
2566 (portage_version, vcs, unameout)
2568 commitmessage += ", RepoMan options: --force"
2569 commitmessage += ")"
2571 if options.echangelog in ('y', 'force'):
2572 logging.info("checking for unmodified ChangeLog files")
2573 committer_name = utilities.get_committer_name(env=repoman_settings)
2574 for x in sorted(vcs_files_to_cps(
2575 chain(myupdates, mymanifests, myremoved))):
2576 catdir, pkgdir = x.split("/")
2577 checkdir = repodir + "/" + x
2578 checkdir_relative = ""
2580 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2582 checkdir_relative = os.path.join(catdir, checkdir_relative)
2583 checkdir_relative = os.path.join(".", checkdir_relative)
2585 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2586 changelog_modified = changelog_path in modified_changelogs
2587 if changelog_modified and options.echangelog != 'force':
2590 # get changes for this package
2591 cdrlen = len(checkdir_relative)
2592 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2593 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2594 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2596 # Skip ChangeLog generation if only the Manifest was modified,
2597 # as discussed in bug #398009.
2598 nontrivial_cl_files = set()
2599 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2600 nontrivial_cl_files.difference_update(['Manifest'])
2601 if not nontrivial_cl_files and options.echangelog != 'force':
2604 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2605 committer_name, changelog_msg,
2606 os.path.join(repodir, 'skel.ChangeLog'),
2608 new=clnew, removed=clremoved, changed=clchanged,
2609 pretend=options.pretend)
2610 if new_changelog is None:
2611 writemsg_level("!!! Updating the ChangeLog failed\n", \
2612 level=logging.ERROR, noiselevel=-1)
2615 # if the ChangeLog was just created, add it to vcs
2617 myautoadd.append(changelog_path)
2618 # myautoadd is appended to myupdates below
2620 myupdates.append(changelog_path)
2622 if options.ask and not options.pretend:
2623 # regenerate Manifest for modified ChangeLog (bug #420735)
2624 repoman_settings["O"] = checkdir
2625 digestgen(mysettings=repoman_settings, myportdb=portdb)
2628 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2629 add_cmd = [vcs, "add"]
2630 add_cmd += myautoadd
2632 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2635 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2636 # Python 3.1 produces the following TypeError if raw bytes are
2637 # passed to subprocess.call():
2638 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2639 # errread, errwrite)
2640 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2641 # raise child_exception
2642 # TypeError: expected an object with the buffer interface
2643 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2644 retcode = subprocess.call(add_cmd)
2645 if retcode != os.EX_OK:
2647 "Exiting on %s error code: %s\n" % (vcs, retcode))
2650 myupdates += myautoadd
2652 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2654 if vcs not in ('cvs', 'svn'):
2655 # With git, bzr and hg, there's never any keyword expansion, so
2656 # there's no need to regenerate manifests and all files will be
2657 # committed in one big commit at the end.
2659 elif not repo_config.thin_manifest:
2661 headerstring = "'\$(Header|Id).*\$'"
2663 svn_keywords = dict((k.lower(), k) for k in [
2666 "LastChangedRevision",
2677 for myfile in myupdates:
2679 # for CVS, no_expansion contains files that are excluded from expansion
2681 if myfile in no_expansion:
2684 # for SVN, expansion contains files that are included in expansion
2686 if myfile not in expansion:
2689 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2690 enabled_keywords = []
2691 for k in expansion[myfile]:
2692 keyword = svn_keywords.get(k.lower())
2693 if keyword is not None:
2694 enabled_keywords.append(keyword)
2696 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2698 myout = repoman_getstatusoutput("egrep -q " + headerstring + " " +
2699 portage._shell_quote(myfile))
2701 myheaders.append(myfile)
2703 print("%s have headers that will change." % green(str(len(myheaders))))
2704 print("* Files with headers will cause the manifests to be changed and committed separately.")
2706 logging.info("myupdates: %s", myupdates)
2707 logging.info("myheaders: %s", myheaders)
2709 if options.ask and userquery('Commit changes?', True) != 'Yes':
2710 print("* aborting commit.")
2711 sys.exit(128 + signal.SIGINT)
2713 # Handle the case where committed files have keywords which
2714 # will change and need a priming commit before the Manifest
2716 if (myupdates or myremoved) and myheaders:
2717 myfiles = myupdates + myremoved
2718 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2719 mymsg = os.fdopen(fd, "wb")
2720 mymsg.write(_unicode_encode(commitmessage))
2724 print(green("Using commit message:"))
2725 print(green("------------------------------------------------------------------------------"))
2726 print(commitmessage)
2727 print(green("------------------------------------------------------------------------------"))
2730 # Having a leading ./ prefix on file paths can trigger a bug in
2731 # the cvs server when committing files to multiple directories,
2732 # so strip the prefix.
2733 myfiles = [f.lstrip("./") for f in myfiles]
2736 commit_cmd.extend(vcs_global_opts)
2737 commit_cmd.append("commit")
2738 commit_cmd.extend(vcs_local_opts)
2739 commit_cmd.extend(["-F", commitmessagefile])
2740 commit_cmd.extend(myfiles)
2744 print("(%s)" % (" ".join(commit_cmd),))
2746 retval = spawn(commit_cmd, env=os.environ)
2747 if retval != os.EX_OK:
2748 writemsg_level(("!!! Exiting on %s (shell) " + \
2749 "error code: %s\n") % (vcs, retval),
2750 level=logging.ERROR, noiselevel=-1)
2754 os.unlink(commitmessagefile)
2758 # Setup the GPG commands
2759 def gpgsign(filename):
2760 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2762 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2763 " Is make.globals missing?")
2764 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2765 "PORTAGE_GPG_KEY" not in repoman_settings:
2766 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2767 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2768 if "PORTAGE_GPG_DIR" not in repoman_settings:
2769 repoman_settings["PORTAGE_GPG_DIR"] = \
2770 os.path.expanduser("~/.gnupg")
2771 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2772 % repoman_settings["PORTAGE_GPG_DIR"])
2774 repoman_settings["PORTAGE_GPG_DIR"] = \
2775 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2776 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2777 raise portage.exception.InvalidLocation(
2778 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2779 repoman_settings["PORTAGE_GPG_DIR"])
2780 gpgvars = {"FILE": filename}
2781 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2782 v = repoman_settings.get(k)
2785 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2787 print("("+gpgcmd+")")
2789 rValue = os.system(gpgcmd)
2790 if rValue == os.EX_OK:
2791 os.rename(filename+".asc", filename)
2793 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2795 # When files are removed and re-added, the cvs server will put /Attic/
2796 # inside the $Header path. This code detects the problem and corrects it
2797 # so that the Manifest will generate correctly. See bug #169500.
2798 # Use binary mode in order to avoid potential character encoding issues.
2799 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2800 attic_str = b'/Attic/'
2801 attic_replace = b'/'
2803 f = open(_unicode_encode(x,
2804 encoding=_encodings['fs'], errors='strict'),
2806 mylines = f.readlines()
2809 for i, line in enumerate(mylines):
2810 if cvs_header_re.match(line) is not None and \
2812 mylines[i] = line.replace(attic_str, attic_replace)
2815 portage.util.write_atomic(x, b''.join(mylines),
2819 print(green("RepoMan sez:"), "\"You're rather crazy... "
2820 "doing the entire repository.\"\n")
2822 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2824 for x in sorted(vcs_files_to_cps(
2825 chain(myupdates, myremoved, mymanifests))):
2826 repoman_settings["O"] = os.path.join(repodir, x)
2827 digestgen(mysettings=repoman_settings, myportdb=portdb)
2833 for x in sorted(vcs_files_to_cps(
2834 chain(myupdates, myremoved, mymanifests))):
2835 repoman_settings["O"] = os.path.join(repodir, x)
2836 manifest_path = os.path.join(repoman_settings["O"], "Manifest")
2837 if not os.path.exists(manifest_path):
2839 gpgsign(manifest_path)
2840 except portage.exception.PortageException as e:
2841 portage.writemsg("!!! %s\n" % str(e))
2842 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2846 # It's not safe to use the git commit -a option since there might
2847 # be some modified files elsewhere in the working tree that the
2848 # user doesn't want to commit. Therefore, call git update-index
2849 # in order to ensure that the index is updated with the latest
2850 # versions of all new and modified files in the relevant portion
2851 # of the working tree.
2852 myfiles = mymanifests + myupdates
2854 update_index_cmd = ["git", "update-index"]
2855 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2857 print("(%s)" % (" ".join(update_index_cmd),))
2859 retval = spawn(update_index_cmd, env=os.environ)
2860 if retval != os.EX_OK:
2861 writemsg_level(("!!! Exiting on %s (shell) " + \
2862 "error code: %s\n") % (vcs, retval),
2863 level=logging.ERROR, noiselevel=-1)
2868 myfiles = mymanifests[:]
2869 # If there are no header (SVN/CVS keywords) changes in
2870 # the files, this Manifest commit must include the
2871 # other (yet uncommitted) files.
2873 myfiles += myupdates
2874 myfiles += myremoved
2877 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2878 mymsg = os.fdopen(fd, "wb")
2879 # strip the closing parenthesis
2880 mymsg.write(_unicode_encode(commitmessage[:-1]))
2882 mymsg.write(_unicode_encode(
2883 ", signed Manifest commit with key %s)" % \
2884 repoman_settings["PORTAGE_GPG_KEY"]))
2886 mymsg.write(b", unsigned Manifest commit)")
2890 if options.pretend and vcs is None:
2891 # substitute a bogus value for pretend output
2892 commit_cmd.append("cvs")
2894 commit_cmd.append(vcs)
2895 commit_cmd.extend(vcs_global_opts)
2896 commit_cmd.append("commit")
2897 commit_cmd.extend(vcs_local_opts)
2899 commit_cmd.extend(["--logfile", commitmessagefile])
2900 commit_cmd.extend(myfiles)
2902 commit_cmd.extend(["-F", commitmessagefile])
2903 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2907 print("(%s)" % (" ".join(commit_cmd),))
2909 retval = spawn(commit_cmd, env=os.environ)
2910 if retval != os.EX_OK:
2912 if repo_config.sign_commit and vcs == 'git' and \
2913 not git_supports_gpg_sign():
2914 # Inform user that newer git is needed (bug #403323).
2916 "Git >=1.7.9 is required for signed commits!")
2918 writemsg_level(("!!! Exiting on %s (shell) " + \
2919 "error code: %s\n") % (vcs, retval),
2920 level=logging.ERROR, noiselevel=-1)
2924 os.unlink(commitmessagefile)
2930 print("Commit complete.")
2932 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
2933 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")