2 # Copyright 1999-2011 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
29 from urllib.request import urlopen as urllib_request_urlopen
31 from urllib import urlopen as urllib_request_urlopen
33 from itertools import chain
34 from stat import S_ISDIR
39 from os import path as osp
40 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
42 portage._disable_legacy_globals()
43 portage.dep._internal_warnings = True
46 import xml.etree.ElementTree
47 from xml.parsers.expat import ExpatError
49 msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
50 from portage.output import EOutput
56 from portage import os
57 from portage import subprocess_getstatusoutput
58 from portage import _encodings
59 from portage import _unicode_encode
60 from repoman.checks import run_checks
61 from repoman import utilities
62 from repoman.herdbase import make_herd_base
63 from _emerge.Package import Package
64 from _emerge.RootConfig import RootConfig
65 from _emerge.userquery import userquery
66 import portage.checksum
68 from portage import cvstree, normalize_path
69 from portage import util
70 from portage.exception import (FileNotFound, MissingParameter,
71 ParseError, PermissionDenied)
72 from portage.process import find_binary, spawn
73 from portage.output import bold, create_color_func, \
75 from portage.output import ConsoleStyleFile, StyleWriter
76 from portage.util import cmp_sort_key, writemsg_level
77 from portage.package.ebuild.digestgen import digestgen
78 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
80 if sys.hexversion >= 0x3000000:
83 util.initialize_logger()
85 # 14 is the length of DESCRIPTION=""
87 allowed_filename_chars="a-zA-Z0-9._-+:"
88 disallowed_filename_chars_re = re.compile(r'[^a-zA-Z0-9._\-+:]')
89 pv_toolong_re = re.compile(r'[0-9]{19,}')
90 bad = create_color_func("BAD")
92 # A sane umask is needed for files that portage creates.
94 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
95 # behave incrementally.
96 repoman_incrementals = tuple(x for x in \
97 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
98 eprefix = os.environ.get("__REPOMAN_TEST_EPREFIX")
99 repoman_settings = portage.config(local_config=False, _eprefix=eprefix)
100 repoman_settings.lock()
102 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
103 repoman_settings.get('TERM') == 'dumb' or \
104 not sys.stdout.isatty():
108 print("repoman: " + txt)
114 def exithandler(signum=None, frame=None):
115 logging.fatal("Interrupted; exiting...")
119 sys.exit(128 + signum)
121 signal.signal(signal.SIGINT,exithandler)
123 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
124 """Repoman needs it's own HelpFormatter for now, because the default ones
125 murder the help text."""
127 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
128 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
130 def format_description(self, description):
133 class RepomanOptionParser(optparse.OptionParser):
134 """Add the on_tail function, ruby has it, optionParser should too
137 def __init__(self, *args, **kwargs):
138 optparse.OptionParser.__init__(self, *args, **kwargs)
141 def on_tail(self, description):
142 self.tail += description
144 def format_help(self, formatter=None):
145 result = optparse.OptionParser.format_help(self, formatter)
150 def ParseArgs(argv, qahelp):
151 """This function uses a customized optionParser to parse command line arguments for repoman
153 argv - a sequence of command line arguments
154 qahelp - a dict of qa warning to help message
156 (opts, args), just like a call to parser.parse_args()
159 if argv and sys.hexversion < 0x3000000 and not isinstance(argv[0], unicode):
160 argv = [portage._unicode_decode(x) for x in argv]
163 'commit' : 'Run a scan then commit changes',
164 'ci' : 'Run a scan then commit changes',
165 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
166 'full' : 'Scan directory tree and print all issues (not a summary)',
167 'help' : 'Show this screen',
168 'manifest' : 'Generate a Manifest (fetches files if necessary)',
169 'manifest-check' : 'Check Manifests for missing or incorrect digests',
170 'scan' : 'Scan directory tree for QA issues'
173 mode_keys = list(modes)
176 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
177 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
178 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
179 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
180 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
182 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
183 help='Request a confirmation before commiting')
185 parser.add_option('-m', '--commitmsg', dest='commitmsg',
186 help='specify a commit message on the command line')
188 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
189 help='specify a path to a file that contains a commit message')
191 parser.add_option('-p', '--pretend', dest='pretend', default=False,
192 action='store_true', help='don\'t commit or fix anything; just show what would be done')
194 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
195 help='do not print unnecessary messages')
198 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
199 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
200 'regardless of modification if \'force\' is specified)')
202 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
203 help='Commit with QA violations')
205 parser.add_option('--vcs', dest='vcs',
206 help='Force using specific VCS instead of autodetection')
208 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
209 help='be very verbose in output', default=0)
211 parser.add_option('-V', '--version', dest='version', action='store_true',
212 help='show version info')
214 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
215 default=False, help='forces the metadata.xml parse check to be carried out')
218 '--if-modified', type='choice', choices=('y', 'n'), default='n',
220 help='only check packages that have uncommitted modifications')
222 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
223 default=False, help='ignore arch-specific failures (where arch != host)')
225 parser.add_option("--ignore-default-opts",
227 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
229 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
230 default=False, help='ignore masked packages (not allowed with commit mode)')
232 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
233 default=False, help='include dev profiles in dependency checks')
235 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
236 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
238 parser.add_option('--without-mask', dest='without_mask', action='store_true',
239 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
241 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
242 help='specify which mode repoman will run in (default=full)')
244 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
247 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
249 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
251 sorted_qa = list(qahelp)
254 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
256 opts, args = parser.parse_args(argv[1:])
258 if not opts.ignore_default_opts:
259 default_opts = repoman_settings.get("REPOMAN_DEFAULT_OPTS", "").split()
261 opts, args = parser.parse_args(default_opts + sys.argv[1:])
263 if opts.mode == 'help':
264 parser.print_help(short=False)
272 parser.error("invalid mode: %s" % arg)
277 if opts.mode == 'ci':
278 opts.mode = 'commit' # backwards compat shortcut
280 if opts.mode == 'commit' and not (opts.force or opts.pretend):
281 if opts.ignore_masked:
282 parser.error('Commit mode and --ignore-masked are not compatible')
283 if opts.without_mask:
284 parser.error('Commit mode and --without-mask are not compatible')
286 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
287 for val in range(opts.verbosity):
288 logger = logging.getLogger()
289 logger.setLevel(logger.getEffectiveLevel() - 10)
291 for val in range(opts.quiet):
292 logger = logging.getLogger()
293 logger.setLevel(logger.getEffectiveLevel() + 10)
298 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
299 "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
300 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
301 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
302 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
303 "changelog.missing":"Missing ChangeLog files",
304 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
305 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
306 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
307 "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)",
308 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
309 "file.size":"Files in the files directory must be under 20 KiB",
310 "file.size.fatal":"Files in the files directory must be under 60 KiB",
311 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
312 "file.UTF8":"File is not UTF8 compliant",
313 "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf",
314 "inherit.deprecated":"Ebuild inherits a deprecated eclass",
315 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
316 "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
317 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
318 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
319 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
320 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
321 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
322 "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
323 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
324 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
325 "EAPI.definition":"EAPI is defined after an inherit call (must be defined before)",
326 "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
327 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
328 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
329 "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
330 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
331 "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
332 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
333 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
334 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
335 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
336 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
337 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
338 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
339 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
340 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
341 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
342 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
343 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
344 "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
345 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
346 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
347 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
348 "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
349 "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
350 "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
351 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
352 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
353 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
354 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
355 "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
356 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
357 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
358 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
359 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
360 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
361 "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
362 "variable.readonly":"Assigning a readonly variable",
363 "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
364 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
365 "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
366 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
367 "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
368 "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)",
369 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
370 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
371 "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
372 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
373 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
374 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
375 "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
376 "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
377 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
378 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
379 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
380 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
381 "ebuild.badheader":"This ebuild has a malformed header",
382 "eprefixify.defined":"The ebuild uses eprefixify, but does not inherit the prefix eclass",
383 "manifest.bad":"Manifest has missing or incorrect digests",
384 "metadata.missing":"Missing metadata.xml files",
385 "metadata.bad":"Bad metadata.xml files",
386 "metadata.warning":"Warnings in metadata.xml files",
387 "portage.internal":"The ebuild uses an internal Portage function",
388 "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
389 "usage.obsolete":"The ebuild makes use of an obsolete construct",
390 "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
393 qacats = list(qahelp)
398 "changelog.notadded",
399 "dependency.unknown",
407 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
408 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
409 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
410 "DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde",
411 "DESCRIPTION.toolong",
429 "inherit.deprecated",
430 "java.eclassesnotused",
431 "wxwidgets.eclassnotused",
435 "upstream.workaround",
441 non_ascii_re = re.compile(r'[^\x00-\x7f]')
443 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
444 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
445 allvars.update(Package.metadata_keys)
446 allvars = sorted(allvars)
448 for x in missingvars:
451 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
455 valid_restrict = frozenset(["binchecks", "bindist",
456 "fetch", "installsources", "mirror",
457 "primaryuri", "strip", "test", "userpriv"])
459 live_eclasses = frozenset([
470 suspect_rdepend = frozenset([
471 "app-arch/cabextract",
472 "app-arch/rpm2targz",
477 "dev-perl/extutils-pkgconfig",
483 "dev-util/gtk-doc-am",
486 "dev-util/pkgconfig",
490 "media-gfx/ebdftopcf",
492 "sys-devel/autoconf",
493 "sys-devel/automake",
500 "virtual/linux-sources",
505 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
506 # force refetch if the local copy creation time is older than this
507 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
510 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
512 options, arguments = ParseArgs(sys.argv, qahelp)
515 print("Portage", portage.VERSION)
518 # Set this to False when an extraordinary issue (generally
519 # something other than a QA issue) makes it impossible to
520 # commit (like if Manifest generation fails).
523 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
527 myreporoot = os.path.basename(portdir_overlay)
528 myreporoot += mydir[len(portdir_overlay):]
531 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
536 vcses = utilities.FindVCS()
538 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
539 print(red('*** Please either clean up your workdir or specify --vcs option.'))
546 if options.if_modified == "y" and vcs is None:
547 logging.info("Not in a version controlled repository; "
548 "disabling --if-modified.")
549 options.if_modified = "n"
551 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
552 vcs_preserves_mtime = vcs not in ('git',)
554 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
555 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
556 if vcs_global_opts is None:
557 if vcs in ('cvs', 'svn'):
558 vcs_global_opts = "-q"
561 vcs_global_opts = vcs_global_opts.split()
563 if options.mode == 'commit' and not options.pretend and not vcs:
564 logging.info("Not in a version controlled repository; enabling pretend mode.")
565 options.pretend = True
567 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
568 repoman_settings = portage.config(local_config=False, _eprefix=eprefix)
569 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
570 (repoman_settings.get('PORTDIR_OVERLAY', ''),
571 portage._shell_quote(portdir_overlay))
572 # We have to call the config constructor again so
573 # that config.repositories is initialized correctly.
574 repoman_settings = portage.config(local_config=False, _eprefix=eprefix,
575 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
577 root = repoman_settings['EROOT']
579 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
581 portdb = trees[root]['porttree'].dbapi
583 # Constrain dependency resolution to the master(s)
584 # that are specified in layout.conf.
585 repodir = os.path.realpath(portdir_overlay)
586 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
587 repo_info = portdb._repo_info[repodir]
588 portdb.porttrees = list(repo_info.eclass_db.porttrees)
589 portdir = portdb.porttrees[0]
591 # In order to disable manifest signatures, repos may set
592 # "sign-manifests = false" in metadata/layout.conf. This
593 # can be used to prevent merge conflicts like those that
594 # thin-manifests is designed to prevent.
595 sign_manifests = "sign" in repoman_settings.features and \
596 repo_config.sign_manifest
598 manifest_hashes = repo_config.manifest_hashes
599 if manifest_hashes is None:
600 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
602 if options.mode in ("commit", "fix", "manifest"):
603 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
604 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
605 "metadata/layout.conf does not contain the '%s' hash which "
606 "is required by this portage version. You will have to "
607 "upgrade portage if you want to generate valid manifests for "
608 "this repository.") % \
609 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
610 for line in textwrap.wrap(msg, 70):
614 unsupported_hashes = manifest_hashes.difference(
615 portage.const.MANIFEST2_HASH_FUNCTIONS)
616 if unsupported_hashes:
617 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
618 "metadata/layout.conf contains one or more hash types '%s' "
619 "which are not supported by this portage version. You will "
620 "have to upgrade portage if you want to generate valid "
621 "manifests for this repository.") % \
622 (repo_config.name, " ".join(sorted(unsupported_hashes)))
623 for line in textwrap.wrap(msg, 70):
627 if "commit" == options.mode and \
628 repo_config.name == "gentoo" and \
629 "RMD160" in manifest_hashes and \
630 "RMD160" not in portage.checksum.hashorigin_map:
631 msg = "Please install " \
632 "pycrypto or enable python's ssl USE flag in order " \
633 "to enable RMD160 hash support. See bug #198398 for " \
636 for line in textwrap.wrap(msg, 70):
640 if options.echangelog is None and repo_config.update_changelog:
641 options.echangelog = 'y'
644 options.echangelog = 'n'
646 # The --echangelog option causes automatic ChangeLog generation,
647 # which invalidates changelog.ebuildadded and changelog.missing
649 # Note: Some don't use ChangeLogs in distributed SCMs.
650 # It will be generated on server side from scm log,
651 # before package moves to the rsync server.
652 # This is needed because they try to avoid merge collisions.
653 # Gentoo's Council decided to always use the ChangeLog file.
654 # TODO: shouldn't this just be switched on the repo, iso the VCS?
655 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
657 logging.debug("repo config: %s" % (repo_config,))
658 logging.debug("options: %s" % (options,))
660 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
661 # profile-specific config constructor calls.
662 env = os.environ.copy()
663 env['PORTDIR'] = portdir
664 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
666 logging.info('Setting paths:')
667 logging.info('PORTDIR = "' + portdir + '"')
668 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
670 # It's confusing if these warnings are displayed without the user
671 # being told which profile they come from, so disable them.
672 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
675 for path in repo_info.eclass_db.porttrees:
676 categories.extend(portage.util.grabfile(
677 os.path.join(path, 'profiles', 'categories')))
678 repoman_settings.categories = frozenset(
679 portage.util.stack_lists([categories], incremental=1))
680 categories = repoman_settings.categories
682 portdb.settings = repoman_settings
683 root_config = RootConfig(repoman_settings, trees[root], None)
684 # We really only need to cache the metadata that's necessary for visibility
685 # filtering. Anything else can be discarded to reduce memory consumption.
686 portdb._aux_cache_keys.clear()
687 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
689 reposplit = myreporoot.split(os.path.sep)
690 repolevel = len(reposplit)
692 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
693 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
694 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
695 if options.mode == 'commit' and repolevel not in [1,2,3]:
696 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
697 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
698 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
700 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
702 # Make startdir relative to the canonical repodir, so that we can pass
703 # it to digestgen and it won't have to be canonicalized again.
707 startdir = normalize_path(mydir)
708 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2-repolevel+3:])
711 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.")
713 class ProfileDesc(object):
714 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
715 def __init__(self, arch, status, sub_path, tree_path):
719 sub_path = normalize_path(sub_path.lstrip(os.sep))
720 self.sub_path = sub_path
721 self.tree_path = tree_path
723 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
725 self.abs_path = tree_path
730 return 'empty profile'
733 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
735 # get lists of valid keywords, licenses, and use
739 global_pmasklines = []
741 for path in portdb.porttrees:
743 liclist.update(os.listdir(os.path.join(path, "licenses")))
746 kwlist.update(portage.grabfile(os.path.join(path,
747 "profiles", "arch.list")))
749 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
755 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
757 expand_list = os.listdir(expand_desc_dir)
761 for fn in expand_list:
762 if not fn[-5:] == '.desc':
764 use_prefix = fn[:-5].lower() + '_'
765 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
768 uselist.add(use_prefix + x[0])
770 global_pmasklines.append(portage.util.grabfile_package(
771 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
773 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
775 desc_file = io.open(_unicode_encode(desc_path,
776 encoding=_encodings['fs'], errors='strict'),
777 mode='r', encoding=_encodings['repo.content'], errors='replace')
778 except EnvironmentError:
781 for i, x in enumerate(desc_file):
788 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
789 desc_path + " line %d" % (i+1, ))
790 elif arch[0] not in kwlist:
791 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
792 desc_path + " line %d" % (i+1, ))
793 elif arch[2] not in valid_profile_types:
794 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
795 desc_path + " line %d" % (i+1, ))
796 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
797 if not os.path.isdir(profile_desc.abs_path):
799 "Invalid %s profile (%s) for arch %s in %s line %d",
800 arch[2], arch[1], arch[0], desc_path, i+1)
803 os.path.join(profile_desc.abs_path, 'deprecated')):
805 profile_list.append(profile_desc)
808 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
809 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
811 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
812 global_pmaskdict = {}
813 for x in global_pmasklines:
814 global_pmaskdict.setdefault(x.cp, []).append(x)
815 del global_pmasklines
817 def has_global_mask(pkg):
818 mask_atoms = global_pmaskdict.get(pkg.cp)
822 if portage.dep.match_from_list(x, pkg_list):
826 # Ensure that profile sub_path attributes are unique. Process in reverse order
827 # so that profiles with duplicate sub_path from overlays will override
828 # profiles with the same sub_path from parent repos.
830 profile_list.reverse()
831 profile_sub_paths = set()
832 for prof in profile_list:
833 if prof.sub_path in profile_sub_paths:
835 profile_sub_paths.add(prof.sub_path)
836 profiles.setdefault(prof.arch, []).append(prof)
838 # Use an empty profile for checking dependencies of
839 # packages that have empty KEYWORDS.
840 prof = ProfileDesc('**', 'stable', '', '')
841 profiles.setdefault(prof.arch, []).append(prof)
843 for x in repoman_settings.archlist():
846 if x not in profiles:
847 print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
848 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
849 print(red("up with the "+x+" team."))
853 logging.fatal("Couldn't find licenses?")
857 logging.fatal("Couldn't read KEYWORDS from arch.list")
861 logging.fatal("Couldn't find use.desc?")
866 #we are inside a category directory
868 if catdir not in categories:
870 mydirlist=os.listdir(startdir)
872 if x == "CVS" or x.startswith("."):
874 if os.path.isdir(startdir+"/"+x):
875 scanlist.append(catdir+"/"+x)
876 repo_subdir = catdir + os.sep
879 if not os.path.isdir(startdir+"/"+x):
881 for y in os.listdir(startdir+"/"+x):
882 if y == "CVS" or y.startswith("."):
884 if os.path.isdir(startdir+"/"+x+"/"+y):
885 scanlist.append(x+"/"+y)
888 catdir = reposplit[-2]
889 if catdir not in categories:
891 scanlist.append(catdir+"/"+reposplit[-1])
892 repo_subdir = scanlist[-1] + os.sep
894 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
895 ' from the current working directory'
896 logging.critical(msg)
899 repo_subdir_len = len(repo_subdir)
902 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
904 def vcs_files_to_cps(vcs_file_iter):
906 Iterate over the given modified file paths returned from the vcs,
907 and return a frozenset containing category/pn strings for each
914 if reposplit[-2] in categories and \
915 next(vcs_file_iter, None) is not None:
916 modified_cps.append("/".join(reposplit[-2:]))
919 category = reposplit[-1]
920 if category in categories:
921 for filename in vcs_file_iter:
922 f_split = filename.split(os.sep)
925 modified_cps.append(category + "/" + f_split[1])
929 for filename in vcs_file_iter:
930 f_split = filename.split(os.sep)
931 # ['.', category, pn,...]
932 if len(f_split) > 3 and f_split[1] in categories:
933 modified_cps.append("/".join(f_split[1:3]))
935 return frozenset(modified_cps)
937 def dev_keywords(profiles):
939 Create a set of KEYWORDS values that exist in 'dev'
940 profiles. These are used
941 to trigger a message notifying the user when they might
942 want to add the --include-dev option.
945 for arch, arch_profiles in profiles.items():
946 for prof in arch_profiles:
947 arch_set = type_arch_map.get(prof.status)
950 type_arch_map[prof.status] = arch_set
953 dev_keywords = type_arch_map.get('dev', set())
954 dev_keywords.update(['~' + arch for arch in dev_keywords])
955 return frozenset(dev_keywords)
957 dev_keywords = dev_keywords(profiles)
962 # provided by the desktop-file-utils package
963 desktop_file_validate = find_binary("desktop-file-validate")
964 desktop_pattern = re.compile(r'.*\.desktop$')
970 xmllint_capable = False
971 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
974 """Parse a RFC 822 date and time string.
975 This is required for python3 compatibility, since the
976 rfc822.parsedate() function is not available."""
979 for x in s.upper().split():
980 for y in x.split(','):
984 if len(s_split) != 6:
987 # %a, %d %b %Y %H:%M:%S %Z
988 a, d, b, Y, H_M_S, Z = s_split
990 # Convert month to integer, since strptime %w is locale-dependent.
991 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
992 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
996 m = str(m).rjust(2, '0')
998 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
1000 def fetch_metadata_dtd():
1002 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1003 metadata_dtd_ctime_interval.
1005 @returns: True if successful, otherwise False
1009 metadata_dtd_st = None
1010 current_time = int(time.time())
1012 metadata_dtd_st = os.stat(metadata_dtd)
1013 except EnvironmentError as e:
1014 if e.errno not in (errno.ENOENT, errno.ESTALE):
1018 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1019 if abs(current_time - metadata_dtd_st.st_ctime) \
1020 < metadata_dtd_ctime_interval:
1025 print(green("***") + " the local copy of metadata.dtd " + \
1026 "needs to be refetched, doing that now")
1029 url_f = urllib_request_urlopen(metadata_dtd_uri)
1030 msg_info = url_f.info()
1031 last_modified = msg_info.get('last-modified')
1032 if last_modified is not None:
1033 last_modified = parsedate(last_modified)
1034 if last_modified is not None:
1035 last_modified = calendar.timegm(last_modified)
1037 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
1039 local_f = open(metadata_dtd_tmp, mode='wb')
1040 local_f.write(url_f.read())
1042 if last_modified is not None:
1044 os.utime(metadata_dtd_tmp,
1045 (int(last_modified), int(last_modified)))
1047 # This fails on some odd non-unix-like filesystems.
1048 # We don't really need the mtime to be preserved
1049 # anyway here (currently we use ctime to trigger
1050 # fetch), so just ignore it.
1052 os.rename(metadata_dtd_tmp, metadata_dtd)
1055 os.unlink(metadata_dtd_tmp)
1061 except EnvironmentError as e:
1063 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
1064 print(red("!!!")+" exception '%s' though." % (e,))
1065 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
1070 if options.mode == "manifest":
1072 elif not find_binary('xmllint'):
1073 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1074 if options.xml_parse or repolevel==3:
1075 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1078 if not fetch_metadata_dtd():
1080 #this can be problematic if xmllint changes their output
1081 xmllint_capable=True
1083 if options.mode == 'commit' and vcs:
1084 utilities.detect_vcs_conflicts(options, vcs)
1086 if options.mode == "manifest":
1088 elif options.pretend:
1089 print(green("\nRepoMan does a once-over of the neighborhood..."))
1091 print(green("\nRepoMan scours the neighborhood..."))
1094 modified_ebuilds = set()
1095 modified_changelogs = set()
1101 mycvstree = cvstree.getentries("./", recursive=1)
1102 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1103 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1104 if options.if_modified == "y":
1105 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1108 with os.popen("svn status") as f:
1109 svnstatus = f.readlines()
1110 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1111 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1112 if options.if_modified == "y":
1113 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1116 with os.popen("git diff-index --name-only "
1117 "--relative --diff-filter=M HEAD") as f:
1118 mychanged = f.readlines()
1119 mychanged = ["./" + elem[:-1] for elem in mychanged]
1121 with os.popen("git diff-index --name-only "
1122 "--relative --diff-filter=A HEAD") as f:
1123 mynew = f.readlines()
1124 mynew = ["./" + elem[:-1] for elem in mynew]
1125 if options.if_modified == "y":
1126 with os.popen("git diff-index --name-only "
1127 "--relative --diff-filter=D HEAD") as f:
1128 myremoved = f.readlines()
1129 myremoved = ["./" + elem[:-1] for elem in myremoved]
1132 with os.popen("bzr status -S .") as f:
1133 bzrstatus = f.readlines()
1134 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
1135 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
1136 if options.if_modified == "y":
1137 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" ) ]
1140 with os.popen("hg status --no-status --modified .") as f:
1141 mychanged = f.readlines()
1142 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1143 mynew = os.popen("hg status --no-status --added .").readlines()
1144 mynew = ["./" + elem.rstrip() for elem in mynew]
1145 if options.if_modified == "y":
1146 with os.popen("hg status --no-status --removed .") as f:
1147 myremoved = f.readlines()
1148 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1151 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1152 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1153 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1154 if os.path.basename(x) == "ChangeLog")
1156 have_pmasked = False
1157 have_dev_keywords = False
1160 arch_xmatch_caches = {}
1161 shared_xmatch_caches = {"cp-list":{}}
1163 # Disable the "ebuild.notadded" check when not in commit mode and
1164 # running `svn status` in every package dir will be too expensive.
1166 check_ebuild_notadded = not \
1167 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1169 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1170 thirdpartymirrors = []
1171 for v in repoman_settings.thirdpartymirrors().values():
1172 thirdpartymirrors.extend(v)
1174 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1176 Implements doctype() as required to avoid deprecation warnings with
1179 def doctype(self, name, pubid, system):
1183 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1184 except (EnvironmentError, ParseError, PermissionDenied) as e:
1186 except FileNotFound:
1187 # TODO: Download as we do for metadata.dtd, but add a way to
1188 # disable for non-gentoo repoman users who may not have herds.
1191 effective_scanlist = scanlist
1192 if options.if_modified == "y":
1193 effective_scanlist = sorted(vcs_files_to_cps(
1194 chain(mychanged, mynew, myremoved)))
1196 for x in effective_scanlist:
1197 #ebuilds and digests added to cvs respectively.
1198 logging.info("checking package %s" % x)
1200 catdir,pkgdir=x.split("/")
1201 checkdir=repodir+"/"+x
1202 checkdir_relative = ""
1204 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1206 checkdir_relative = os.path.join(catdir, checkdir_relative)
1207 checkdir_relative = os.path.join(".", checkdir_relative)
1208 generated_manifest = False
1210 if options.mode == "manifest" or \
1211 (options.mode != 'manifest-check' and \
1212 'digest' in repoman_settings.features) or \
1213 options.mode in ('commit', 'fix') and not options.pretend:
1214 auto_assumed = set()
1215 fetchlist_dict = portage.FetchlistDict(checkdir,
1216 repoman_settings, portdb)
1217 if options.mode == 'manifest' and options.force:
1218 portage._doebuild_manifest_exempt_depend += 1
1220 distdir = repoman_settings['DISTDIR']
1221 mf = repoman_settings.repositories.get_repo_for_location(
1222 os.path.dirname(os.path.dirname(checkdir)))
1223 mf = mf.load_manifest(checkdir, distdir,
1224 fetchlist_dict=fetchlist_dict)
1225 mf.create(requiredDistfiles=None,
1226 assumeDistHashesAlways=True)
1227 for distfiles in fetchlist_dict.values():
1228 for distfile in distfiles:
1229 if os.path.isfile(os.path.join(distdir, distfile)):
1230 mf.fhashdict['DIST'].pop(distfile, None)
1232 auto_assumed.add(distfile)
1235 portage._doebuild_manifest_exempt_depend -= 1
1237 repoman_settings["O"] = checkdir
1239 generated_manifest = digestgen(
1240 mysettings=repoman_settings, myportdb=portdb)
1241 except portage.exception.PermissionDenied as e:
1242 generated_manifest = False
1243 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1244 level=logging.ERROR, noiselevel=-1)
1246 if not generated_manifest:
1247 print("Unable to generate manifest.")
1250 if options.mode == "manifest":
1251 if not dofail and options.force and auto_assumed and \
1252 'assume-digests' in repoman_settings.features:
1253 # Show which digests were assumed despite the --force option
1254 # being given. This output will already have been shown by
1255 # digestgen() if assume-digests is not enabled, so only show
1256 # it here if assume-digests is enabled.
1257 pkgs = list(fetchlist_dict)
1259 portage.writemsg_stdout(" digest.assumed" + \
1260 portage.output.colorize("WARN",
1261 str(len(auto_assumed)).rjust(18)) + "\n")
1263 fetchmap = fetchlist_dict[cpv]
1264 pf = portage.catsplit(cpv)[1]
1265 for distfile in sorted(fetchmap):
1266 if distfile in auto_assumed:
1267 portage.writemsg_stdout(
1268 " %s::%s\n" % (pf, distfile))
1273 if not generated_manifest:
1274 repoman_settings['O'] = checkdir
1275 repoman_settings['PORTAGE_QUIET'] = '1'
1276 if not portage.digestcheck([], repoman_settings, strict=1):
1277 stats["manifest.bad"] += 1
1278 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1279 repoman_settings.pop('PORTAGE_QUIET', None)
1281 if options.mode == 'manifest-check':
1284 checkdirlist=os.listdir(checkdir)
1288 for y in checkdirlist:
1289 if (y in no_exec or y.endswith(".ebuild")) and \
1290 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1291 stats["file.executable"] += 1
1292 fails["file.executable"].append(os.path.join(checkdir, y))
1293 if y.endswith(".ebuild"):
1295 ebuildlist.append(pf)
1296 cpv = "%s/%s" % (catdir, pf)
1298 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1301 stats["ebuild.syntax"] += 1
1302 fails["ebuild.syntax"].append(os.path.join(x, y))
1306 stats["ebuild.output"] += 1
1307 fails["ebuild.output"].append(os.path.join(x, y))
1309 if not portage.eapi_is_supported(myaux["EAPI"]):
1311 stats["EAPI.unsupported"] += 1
1312 fails["EAPI.unsupported"].append(os.path.join(x, y))
1314 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1315 root_config=root_config, type_name="ebuild")
1317 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1319 for i in range(len(ebuildlist)):
1320 ebuild_split = portage.pkgsplit(ebuildlist[i])
1321 pkgsplits[ebuild_split] = ebuildlist[i]
1322 ebuildlist[i] = ebuild_split
1323 ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp))
1324 for i in range(len(ebuildlist)):
1325 ebuildlist[i] = pkgsplits[ebuildlist[i]]
1330 if len(pkgs) != len(ebuildlist):
1331 # If we can't access all the metadata then it's totally unsafe to
1332 # commit since there's no way to generate a correct Manifest.
1333 # Do not try to do any more QA checks on this package since missing
1334 # metadata leads to false positives for several checks, and false
1335 # positives confuse users.
1339 for y in checkdirlist:
1340 m = disallowed_filename_chars_re.search(y.strip(os.sep))
1342 stats["file.name"] += 1
1343 fails["file.name"].append("%s/%s: char '%s'" % \
1344 (checkdir, y, m.group(0)))
1346 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1351 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1352 encoding=_encodings['fs'], errors='strict'),
1353 mode='r', encoding=_encodings['repo.content'])
1356 except UnicodeDecodeError as ue:
1357 stats["file.UTF8"] += 1
1358 s = ue.object[:ue.start]
1362 s = s[s.rfind("\n") + 1:]
1363 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1368 if vcs in ("git", "hg") and check_ebuild_notadded:
1370 myf = os.popen("git ls-files --others %s" % \
1371 (portage._shell_quote(checkdir_relative),))
1373 myf = os.popen("hg status --no-status --unknown %s" % \
1374 (portage._shell_quote(checkdir_relative),))
1376 if l[:-1][-7:] == ".ebuild":
1377 stats["ebuild.notadded"] += 1
1378 fails["ebuild.notadded"].append(
1379 os.path.join(x, os.path.basename(l[:-1])))
1382 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1385 myf=open(checkdir+"/CVS/Entries","r")
1387 myf = os.popen("svn status --depth=files --verbose " + checkdir)
1389 myf = os.popen("bzr ls -v --kind=file " + checkdir)
1390 myl = myf.readlines()
1396 splitl=l[1:].split("/")
1399 if splitl[0][-7:]==".ebuild":
1400 eadded.append(splitl[0][:-7])
1405 # tree conflict, new in subversion 1.6
1408 if l[-7:] == ".ebuild":
1409 eadded.append(os.path.basename(l[:-7]))
1414 if l[-7:] == ".ebuild":
1415 eadded.append(os.path.basename(l[:-7]))
1417 myf = os.popen("svn status " + checkdir)
1422 l = l.rstrip().split(' ')[-1]
1423 if l[-7:] == ".ebuild":
1424 eadded.append(os.path.basename(l[:-7]))
1427 stats["CVS/Entries.IO_error"] += 1
1428 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
1433 mf = repoman_settings.repositories.get_repo_for_location(
1434 os.path.dirname(os.path.dirname(checkdir)))
1435 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1436 mydigests=mf.getTypeDigests("DIST")
1438 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1440 src_uri_error = False
1441 for mykey in fetchlist_dict:
1443 myfiles_all.extend(fetchlist_dict[mykey])
1444 except portage.exception.InvalidDependString as e:
1445 src_uri_error = True
1447 portdb.aux_get(mykey, ["SRC_URI"])
1449 # This will be reported as an "ebuild.syntax" error.
1452 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
1453 fails["SRC_URI.syntax"].append(
1454 "%s.ebuild SRC_URI: %s" % (mykey, e))
1456 if not src_uri_error:
1457 # This test can produce false positives if SRC_URI could not
1458 # be parsed for one or more ebuilds. There's no point in
1459 # producing a false error here since the root cause will
1460 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1461 # or "ebuild.sytax".
1462 myfiles_all = set(myfiles_all)
1463 for entry in mydigests:
1464 if entry not in myfiles_all:
1465 stats["digest.unused"] += 1
1466 fails["digest.unused"].append(checkdir+"::"+entry)
1467 for entry in myfiles_all:
1468 if entry not in mydigests:
1469 stats["digest.missing"] += 1
1470 fails["digest.missing"].append(checkdir+"::"+entry)
1473 if os.path.exists(checkdir+"/files"):
1474 filesdirlist=os.listdir(checkdir+"/files")
1476 # recurse through files directory
1477 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1479 y = filesdirlist.pop(0)
1480 relative_path = os.path.join(x, "files", y)
1481 full_path = os.path.join(repodir, relative_path)
1483 mystat = os.stat(full_path)
1484 except OSError as oe:
1486 # don't worry about it. it likely was removed via fix above.
1490 if S_ISDIR(mystat.st_mode):
1491 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1492 if y == "CVS" or y == ".svn":
1494 for z in os.listdir(checkdir+"/files/"+y):
1495 if z == "CVS" or z == ".svn":
1497 filesdirlist.append(y+"/"+z)
1498 # Current policy is no files over 20 KiB, these are the checks. File size between
1499 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1500 elif mystat.st_size > 61440:
1501 stats["file.size.fatal"] += 1
1502 fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1503 elif mystat.st_size > 20480:
1504 stats["file.size"] += 1
1505 fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1507 m = disallowed_filename_chars_re.search(
1508 os.path.basename(y.rstrip(os.sep)))
1510 stats["file.name"] += 1
1511 fails["file.name"].append("%s/files/%s: char '%s'" % \
1512 (checkdir, y, m.group(0)))
1514 if desktop_file_validate and desktop_pattern.match(y):
1515 status, cmd_output = subprocess_getstatusoutput(
1516 "'%s' '%s'" % (desktop_file_validate, full_path))
1517 if os.WIFEXITED(status) and os.WEXITSTATUS(status) != os.EX_OK:
1518 # Note: in the future we may want to grab the
1519 # warnings in addition to the errors. We're
1520 # just doing errors now since we don't want
1521 # to generate too much noise at first.
1522 error_re = re.compile(r'.*\s*error:\s*(.*)')
1523 for line in cmd_output.splitlines():
1524 error_match = error_re.match(line)
1525 if error_match is None:
1527 stats["desktop.invalid"] += 1
1528 fails["desktop.invalid"].append(
1529 relative_path + ': %s' % error_match.group(1))
1533 if check_changelog and "ChangeLog" not in checkdirlist:
1534 stats["changelog.missing"]+=1
1535 fails["changelog.missing"].append(x+"/ChangeLog")
1538 #metadata.xml file check
1539 if "metadata.xml" not in checkdirlist:
1540 stats["metadata.missing"]+=1
1541 fails["metadata.missing"].append(x+"/metadata.xml")
1542 #metadata.xml parse check
1544 metadata_bad = False
1546 # read metadata.xml into memory
1548 _metadata_xml = xml.etree.ElementTree.parse(
1549 os.path.join(checkdir, "metadata.xml"),
1550 parser=xml.etree.ElementTree.XMLParser(
1551 target=_MetadataTreeBuilder()))
1552 except (ExpatError, SyntaxError, EnvironmentError) as e:
1554 stats["metadata.bad"] += 1
1555 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1558 # load USE flags from metadata.xml
1560 musedict = utilities.parse_metadata_use(_metadata_xml)
1561 except portage.exception.ParseError as e:
1563 stats["metadata.bad"] += 1
1564 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1566 # Run other metadata.xml checkers
1568 utilities.check_metadata(_metadata_xml, herd_base)
1569 except (utilities.UnknownHerdsError, ) as e:
1571 stats["metadata.bad"] += 1
1572 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1575 #Only carry out if in package directory or check forced
1576 if xmllint_capable and not metadata_bad:
1577 # xmlint can produce garbage output even on success, so only dump
1578 # the ouput when it fails.
1579 st, out = subprocess_getstatusoutput(
1580 "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \
1581 (metadata_dtd, os.path.join(checkdir, "metadata.xml")))
1583 print(red("!!!") + " metadata.xml is invalid:")
1584 for z in out.splitlines():
1585 print(red("!!! ")+z)
1586 stats["metadata.bad"]+=1
1587 fails["metadata.bad"].append(x+"/metadata.xml")
1590 muselist = frozenset(musedict)
1592 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1593 changelog_modified = changelog_path in modified_changelogs
1596 # detect unused local USE-descriptions
1597 used_useflags = set()
1599 for y in ebuildlist:
1600 relative_path = os.path.join(x, y + ".ebuild")
1601 full_path = os.path.join(repodir, relative_path)
1602 ebuild_path = y + ".ebuild"
1604 ebuild_path = os.path.join(pkgdir, ebuild_path)
1606 ebuild_path = os.path.join(catdir, ebuild_path)
1607 ebuild_path = os.path.join(".", ebuild_path)
1608 if check_changelog and not changelog_modified \
1609 and ebuild_path in new_ebuilds:
1610 stats['changelog.ebuildadded'] += 1
1611 fails['changelog.ebuildadded'].append(relative_path)
1613 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1614 #ebuild not added to vcs
1615 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1616 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1617 myesplit=portage.pkgsplit(y)
1618 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1619 or pv_toolong_re.search(myesplit[1]) \
1620 or pv_toolong_re.search(myesplit[2]):
1621 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1622 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1624 elif myesplit[0]!=pkgdir:
1625 print(pkgdir,myesplit[0])
1626 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1627 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1634 for k, msgs in pkg.invalid.items():
1636 stats[k] = stats[k] + 1
1637 fails[k].append("%s %s" % (relative_path, msg))
1640 myaux = pkg.metadata
1641 eapi = myaux["EAPI"]
1642 inherited = pkg.inherited
1643 live_ebuild = live_eclasses.intersection(inherited)
1645 for k, v in myaux.items():
1646 if not isinstance(v, basestring):
1648 m = non_ascii_re.search(v)
1650 stats["variable.invalidchar"] += 1
1651 fails["variable.invalidchar"].append(
1652 ("%s: %s variable contains non-ASCII " + \
1653 "character at position %s") % \
1654 (relative_path, k, m.start() + 1))
1656 if not src_uri_error:
1657 # Check that URIs don't reference a server from thirdpartymirrors.
1658 for uri in portage.dep.use_reduce( \
1659 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1660 contains_mirror = False
1661 for mirror in thirdpartymirrors:
1662 if uri.startswith(mirror):
1663 contains_mirror = True
1665 if not contains_mirror:
1668 stats["SRC_URI.mirror"] += 1
1669 fails["SRC_URI.mirror"].append(
1670 "%s: '%s' found in thirdpartymirrors" % \
1671 (relative_path, mirror))
1673 if myaux.get("PROVIDE"):
1674 stats["virtual.oldstyle"]+=1
1675 fails["virtual.oldstyle"].append(relative_path)
1677 for pos, missing_var in enumerate(missingvars):
1678 if not myaux.get(missing_var):
1679 if catdir == "virtual" and \
1680 missing_var in ("HOMEPAGE", "LICENSE"):
1682 if live_ebuild and missing_var == "KEYWORDS":
1684 myqakey=missingvars[pos]+".missing"
1685 stats[myqakey]=stats[myqakey]+1
1686 fails[myqakey].append(x+"/"+y+".ebuild")
1688 if catdir == "virtual":
1689 for var in ("HOMEPAGE", "LICENSE"):
1691 myqakey = var + ".virtual"
1692 stats[myqakey] = stats[myqakey] + 1
1693 fails[myqakey].append(relative_path)
1695 # 14 is the length of DESCRIPTION=""
1696 if len(myaux['DESCRIPTION']) > max_desc_len:
1697 stats['DESCRIPTION.toolong'] += 1
1698 fails['DESCRIPTION.toolong'].append(
1699 "%s: DESCRIPTION is %d characters (max %d)" % \
1700 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1702 keywords = myaux["KEYWORDS"].split()
1703 stable_keywords = []
1704 for keyword in keywords:
1705 if not keyword.startswith("~") and \
1706 not keyword.startswith("-"):
1707 stable_keywords.append(keyword)
1709 if ebuild_path in new_ebuilds:
1710 stable_keywords.sort()
1711 stats["KEYWORDS.stable"] += 1
1712 fails["KEYWORDS.stable"].append(
1713 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1714 " ".join(stable_keywords))
1716 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1717 if not kw.startswith("-"))
1719 previous_keywords = slot_keywords.get(myaux["SLOT"])
1720 if previous_keywords is None:
1721 slot_keywords[myaux["SLOT"]] = set()
1722 elif ebuild_archs and not live_ebuild:
1723 dropped_keywords = previous_keywords.difference(ebuild_archs)
1724 if dropped_keywords:
1725 stats["KEYWORDS.dropped"] += 1
1726 fails["KEYWORDS.dropped"].append(
1727 relative_path + ": %s" % \
1728 " ".join(sorted(dropped_keywords)))
1730 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1732 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1733 if "-*" in keywords:
1741 stats["KEYWORDS.stupid"] += 1
1742 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1745 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1746 not be allowed to be marked stable
1749 bad_stable_keywords = []
1750 for keyword in keywords:
1751 if not keyword.startswith("~") and \
1752 not keyword.startswith("-"):
1753 bad_stable_keywords.append(keyword)
1755 if bad_stable_keywords:
1756 stats["LIVEVCS.stable"] += 1
1757 fails["LIVEVCS.stable"].append(
1758 x + "/" + y + ".ebuild with stable keywords:%s " % \
1759 bad_stable_keywords)
1760 del bad_stable_keywords
1762 if keywords and not has_global_mask(pkg):
1763 stats["LIVEVCS.unmasked"] += 1
1764 fails["LIVEVCS.unmasked"].append(relative_path)
1766 if options.ignore_arches:
1767 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1768 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1771 for keyword in myaux["KEYWORDS"].split():
1772 if (keyword[0]=="-"):
1774 elif (keyword[0]=="~"):
1775 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1777 arches.append([keyword, keyword, [keyword]])
1780 # Use an empty profile for checking dependencies of
1781 # packages that have empty KEYWORDS.
1782 arches.append(['**', '**', ['**']])
1784 unknown_pkgs = set()
1785 baddepsyntax = False
1786 badlicsyntax = False
1787 badprovsyntax = False
1788 catpkg = catdir+"/"+y
1790 inherited_java_eclass = "java-pkg-2" in inherited or \
1791 "java-pkg-opt-2" in inherited
1792 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1793 operator_tokens = set(["||", "(", ")"])
1794 type_list, badsyntax = [], []
1795 for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
1796 "LICENSE", "PROPERTIES", "PROVIDE"):
1797 mydepstr = myaux[mytype]
1800 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1801 token_class=portage.dep.Atom
1804 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1805 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1806 except portage.exception.InvalidDependString as e:
1808 badsyntax.append(str(e))
1810 if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1811 if mytype in ("RDEPEND", "PDEPEND") and \
1812 "test?" in mydepstr.split():
1813 stats[mytype + '.suspect'] += 1
1814 fails[mytype + '.suspect'].append(relative_path + \
1815 ": 'test?' USE conditional in %s" % mytype)
1821 if not portdb.xmatch("match-all", atom) and \
1822 not atom.cp.startswith("virtual/"):
1823 unknown_pkgs.add((mytype, atom.unevaluated_atom))
1825 is_blocker = atom.blocker
1827 if mytype == "DEPEND" and \
1828 not is_blocker and \
1829 not inherited_java_eclass and \
1830 atom.cp == "virtual/jdk":
1831 stats['java.eclassesnotused'] += 1
1832 fails['java.eclassesnotused'].append(relative_path)
1833 elif mytype == "DEPEND" and \
1834 not is_blocker and \
1835 not inherited_wxwidgets_eclass and \
1836 atom.cp == "x11-libs/wxGTK":
1837 stats['wxwidgets.eclassnotused'] += 1
1838 fails['wxwidgets.eclassnotused'].append(
1839 relative_path + ": DEPENDs on x11-libs/wxGTK"
1840 " without inheriting wxwidgets.eclass")
1841 elif mytype in ("PDEPEND", "RDEPEND"):
1842 if not is_blocker and \
1843 atom.cp in suspect_rdepend:
1844 stats[mytype + '.suspect'] += 1
1845 fails[mytype + '.suspect'].append(
1846 relative_path + ": '%s'" % atom)
1848 if atom.operator == "~" and \
1849 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
1850 stats[mytype + '.badtilde'] += 1
1851 fails[mytype + '.badtilde'].append(
1852 (relative_path + ": %s uses the ~ operator"
1853 " with a non-zero revision:" + \
1854 " '%s'") % (mytype, atom))
1856 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1858 for m,b in zip(type_list, badsyntax):
1859 stats[m+".syntax"] += 1
1860 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1862 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
1863 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
1864 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1865 badlicsyntax = badlicsyntax > 0
1866 badprovsyntax = badprovsyntax > 0
1868 # uselist checks - global
1871 for myflag in myaux["IUSE"].split():
1872 flag_name = myflag.lstrip("+-")
1873 used_useflags.add(flag_name)
1874 if myflag != flag_name:
1875 default_use.append(myflag)
1876 if flag_name not in uselist:
1877 myuse.append(flag_name)
1879 # uselist checks - metadata
1880 for mypos in range(len(myuse)-1,-1,-1):
1881 if myuse[mypos] and (myuse[mypos] in muselist):
1884 if default_use and not eapi_has_iuse_defaults(eapi):
1885 for myflag in default_use:
1886 stats['EAPI.incompatible'] += 1
1887 fails['EAPI.incompatible'].append(
1888 (relative_path + ": IUSE defaults" + \
1889 " not supported with EAPI='%s':" + \
1890 " '%s'") % (eapi, myflag))
1892 for mypos in range(len(myuse)):
1893 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1894 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1897 if not badlicsyntax:
1898 # Parse the LICENSE variable, remove USE conditions and
1900 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
1901 # Check each entry to ensure that it exists in PORTDIR's
1902 # license directory.
1903 for lic in licenses:
1904 # Need to check for "||" manually as no portage
1905 # function will remove it without removing values.
1906 if lic not in liclist and lic != "||":
1907 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1908 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
1911 myuse = myaux["KEYWORDS"].split()
1919 if myskey not in kwlist:
1920 stats["KEYWORDS.invalid"] += 1
1921 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1922 elif myskey not in profiles:
1923 stats["KEYWORDS.invalid"] += 1
1924 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1929 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
1930 except portage.exception.InvalidDependString as e:
1931 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
1932 fails["RESTRICT.syntax"].append(
1933 "%s: RESTRICT: %s" % (relative_path, e))
1936 myrestrict = set(myrestrict)
1937 mybadrestrict = myrestrict.difference(valid_restrict)
1939 stats["RESTRICT.invalid"] += len(mybadrestrict)
1940 for mybad in mybadrestrict:
1941 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
1943 required_use = myaux["REQUIRED_USE"]
1945 if not eapi_has_required_use(eapi):
1946 stats['EAPI.incompatible'] += 1
1947 fails['EAPI.incompatible'].append(
1948 relative_path + ": REQUIRED_USE" + \
1949 " not supported with EAPI='%s'" % (eapi,))
1951 portage.dep.check_required_use(required_use, (),
1952 pkg.iuse.is_valid_flag)
1953 except portage.exception.InvalidDependString as e:
1954 stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
1955 fails["REQUIRED_USE.syntax"].append(
1956 "%s: REQUIRED_USE: %s" % (relative_path, e))
1960 relative_path = os.path.join(x, y + ".ebuild")
1961 full_path = os.path.join(repodir, relative_path)
1962 if not vcs_preserves_mtime:
1963 if ebuild_path not in new_ebuilds and \
1964 ebuild_path not in modified_ebuilds:
1967 # All ebuilds should have utf_8 encoding.
1968 f = io.open(_unicode_encode(full_path,
1969 encoding=_encodings['fs'], errors='strict'),
1970 mode='r', encoding=_encodings['repo.content'])
1972 for check_name, e in run_checks(f, pkg):
1973 stats[check_name] += 1
1974 fails[check_name].append(relative_path + ': %s' % e)
1977 except UnicodeDecodeError:
1978 # A file.UTF8 failure will have already been recorded above.
1982 # The dep_check() calls are the most expensive QA test. If --force
1983 # is enabled, there's no point in wasting time on these since the
1984 # user is intent on forcing the commit anyway.
1987 for keyword,arch,groups in arches:
1989 if arch not in profiles:
1990 # A missing profile will create an error further down
1991 # during the KEYWORDS verification.
1994 for prof in profiles[arch]:
1996 if prof.status not in ("stable", "dev") or \
1997 prof.status == "dev" and not options.include_dev:
2000 dep_settings = arch_caches.get(prof.sub_path)
2001 if dep_settings is None:
2002 dep_settings = portage.config(
2003 config_profile_path=prof.abs_path,
2004 config_incrementals=repoman_incrementals,
2006 _unmatched_removal=options.unmatched_removal,
2007 env=env, _eprefix=eprefix)
2008 dep_settings.categories = repoman_settings.categories
2009 if options.without_mask:
2010 dep_settings._mask_manager = \
2011 copy.deepcopy(dep_settings._mask_manager)
2012 dep_settings._mask_manager._pmaskdict.clear()
2013 arch_caches[prof.sub_path] = dep_settings
2015 xmatch_cache_key = (prof.sub_path, tuple(groups))
2016 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2020 xcache = portdb.xcache
2021 xcache.update(shared_xmatch_caches)
2022 arch_xmatch_caches[xmatch_cache_key] = xcache
2024 trees[root]["porttree"].settings = dep_settings
2025 portdb.settings = dep_settings
2026 portdb.xcache = xcache
2027 # for package.use.mask support inside dep_check
2028 dep_settings.setcpv(pkg)
2029 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2030 # just in case, prevent config.reset() from nuking these.
2031 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2033 if not baddepsyntax:
2034 ismasked = not ebuild_archs or \
2035 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2037 if not have_pmasked:
2038 have_pmasked = bool(dep_settings._getMaskAtom(
2039 pkg.cpv, pkg.metadata))
2040 if options.ignore_masked:
2042 #we are testing deps for a masked package; give it some lee-way
2044 matchmode = "minimum-all"
2047 matchmode = "minimum-visible"
2049 if not have_dev_keywords:
2050 have_dev_keywords = \
2051 bool(dev_keywords.intersection(keywords))
2053 if prof.status == "dev":
2054 suffix=suffix+"indev"
2056 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
2058 mykey=mytype+".bad"+suffix
2059 myvalue = myaux[mytype]
2063 success, atoms = portage.dep_check(myvalue, portdb,
2064 dep_settings, use="all", mode=matchmode,
2070 # Don't bother with dependency.unknown for
2071 # cases in which *DEPEND.bad is triggered.
2073 # dep_check returns all blockers and they
2074 # aren't counted for *DEPEND.bad, so we
2076 if not atom.blocker:
2077 unknown_pkgs.discard(
2078 (mytype, atom.unevaluated_atom))
2080 if not prof.sub_path:
2081 # old-style virtuals currently aren't
2082 # resolvable with empty profile, since
2083 # 'virtuals' mappings are unavailable
2084 # (it would be expensive to search
2085 # for PROVIDE in all ebuilds)
2086 atoms = [atom for atom in atoms if not \
2087 (atom.cp.startswith('virtual/') and \
2088 not portdb.cp_list(atom.cp))]
2090 #we have some unsolvable deps
2091 #remove ! deps, which always show up as unsatisfiable
2092 atoms = [str(atom.unevaluated_atom) \
2093 for atom in atoms if not atom.blocker]
2095 #if we emptied out our list, continue:
2098 stats[mykey]=stats[mykey]+1
2099 fails[mykey].append("%s: %s(%s) %s" % \
2100 (relative_path, keyword,
2103 stats[mykey]=stats[mykey]+1
2104 fails[mykey].append("%s: %s(%s) %s" % \
2105 (relative_path, keyword,
2108 if not baddepsyntax and unknown_pkgs:
2110 for mytype, atom in unknown_pkgs:
2111 type_map.setdefault(mytype, set()).add(atom)
2112 for mytype, atoms in type_map.items():
2113 stats["dependency.unknown"] += 1
2114 fails["dependency.unknown"].append("%s: %s: %s" %
2115 (relative_path, mytype, ", ".join(sorted(atoms))))
2117 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
2118 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
2119 #if not portage.portdb.xmatch("bestmatch-visible",x):
2120 # stats["ebuild.nostable"]+=1
2121 # fails["ebuild.nostable"].append(x)
2122 if ebuildlist and allmasked and repolevel == 3:
2123 stats["ebuild.allmasked"]+=1
2124 fails["ebuild.allmasked"].append(x)
2126 # check if there are unused local USE-descriptions in metadata.xml
2127 # (unless there are any invalids, to avoid noise)
2129 for myflag in muselist.difference(used_useflags):
2130 stats["metadata.warning"] += 1
2131 fails["metadata.warning"].append(
2132 "%s/metadata.xml: unused local USE-description: '%s'" % \
2135 if options.if_modified == "y" and len(effective_scanlist) < 1:
2136 logging.warn("--if-modified is enabled, but no modified packages were found!")
2138 if options.mode == "manifest":
2141 #dofail will be set to 1 if we have failed in at least one non-warning category
2143 #dowarn will be set to 1 if we tripped any warnings
2145 #dofull will be set if we should print a "repoman full" informational message
2146 dofull = options.mode != 'full'
2152 if x not in qawarnings:
2156 (dowarn and not (options.quiet or options.mode == "scan")):
2159 # Save QA output so that it can be conveniently displayed
2160 # in $EDITOR while the user creates a commit message.
2161 # Otherwise, the user would not be able to see this output
2162 # once the editor has taken over the screen.
2163 qa_output = io.StringIO()
2164 style_file = ConsoleStyleFile(sys.stdout)
2165 if options.mode == 'commit' and \
2166 (not commitmessage or not commitmessage.strip()):
2167 style_file.write_listener = qa_output
2168 console_writer = StyleWriter(file=style_file, maxcol=9999)
2169 console_writer.style_listener = style_file.new_styles
2171 f = formatter.AbstractFormatter(console_writer)
2173 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2176 del console_writer, f, style_file
2177 qa_output = qa_output.getvalue()
2178 qa_output = qa_output.splitlines(True)
2180 def grouplist(mylist,seperator="/"):
2181 """(list,seperator="/") -- Takes a list of elements; groups them into
2182 same initial element categories. Returns a dict of {base:[sublist]}
2183 From: ["blah/foo","spork/spatula","blah/weee/splat"]
2184 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
2187 xs=x.split(seperator)
2190 if xs[0] not in mygroups:
2191 mygroups[xs[0]]=[seperator.join(xs[1:])]
2193 mygroups[xs[0]]+=[seperator.join(xs[1:])]
2196 suggest_ignore_masked = False
2197 suggest_include_dev = False
2199 if have_pmasked and not (options.without_mask or options.ignore_masked):
2200 suggest_ignore_masked = True
2201 if have_dev_keywords and not options.include_dev:
2202 suggest_include_dev = True
2204 if suggest_ignore_masked or suggest_include_dev:
2206 if suggest_ignore_masked:
2207 print(bold("Note: use --without-mask to check " + \
2208 "KEYWORDS on dependencies of masked packages"))
2210 if suggest_include_dev:
2211 print(bold("Note: use --include-dev (-d) to check " + \
2212 "dependencies for 'dev' profiles"))
2215 if options.mode != 'commit':
2217 print(bold("Note: type \"repoman full\" for a complete listing."))
2218 if dowarn and not dofail:
2219 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.\"")
2221 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2223 print(bad("Please fix these important QA issues first."))
2224 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2227 if dofail and can_force and options.force and not options.pretend:
2228 print(green("RepoMan sez:") + \
2229 " \"You want to commit even with these QA issues?\n" + \
2230 " I'll take it this time, but I'm not happy.\"\n")
2232 if options.force and not can_force:
2233 print(bad("The --force option has been disabled due to extraordinary issues."))
2234 print(bad("Please fix these important QA issues first."))
2235 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2239 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2244 myvcstree=portage.cvstree.getentries("./",recursive=1)
2245 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
2246 except SystemExit as e:
2247 raise # TODO propagate this
2249 err("Error retrieving CVS tree; exiting.")
2252 with os.popen("svn status --no-ignore") as f:
2253 svnstatus = f.readlines()
2254 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
2255 except SystemExit as e:
2256 raise # TODO propagate this
2258 err("Error retrieving SVN info; exiting.")
2260 # get list of files not under version control or missing
2261 myf = os.popen("git ls-files --others")
2262 myunadded = [ "./" + elem[:-1] for elem in myf ]
2266 with os.popen("bzr status -S .") as f:
2267 bzrstatus = f.readlines()
2268 myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
2269 except SystemExit as e:
2270 raise # TODO propagate this
2272 err("Error retrieving bzr info; exiting.")
2274 with os.popen("hg status --no-status --unknown .") as f:
2275 myunadded = f.readlines()
2276 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2278 # Mercurial doesn't handle manually deleted files as removed from
2279 # the repository, so the user need to remove them before commit,
2280 # using "hg remove [FILES]"
2281 with os.popen("hg status --no-status --deleted .") as f:
2282 mydeleted = f.readlines()
2283 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2288 for x in range(len(myunadded)-1,-1,-1):
2289 xs=myunadded[x].split("/")
2291 print("!!! files dir is not added! Please correct this.")
2293 elif xs[-1]=="Manifest":
2294 # It's a manifest... auto add
2295 myautoadd+=[myunadded[x]]
2299 print(red("!!! The following files are in your local tree but are not added to the master"))
2300 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2307 if vcs == "hg" and mydeleted:
2308 print(red("!!! The following files are removed manually from your local tree but are not"))
2309 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2317 mycvstree = cvstree.getentries("./", recursive=1)
2318 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2319 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2320 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
2321 bin_blob_pattern = re.compile("^-kb$")
2322 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2323 recursive=1, basedir="./"))
2327 with os.popen("svn status") as f:
2328 svnstatus = f.readlines()
2329 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2330 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2331 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2333 # Subversion expands keywords specified in svn:keywords properties.
2334 with os.popen("svn propget -R svn:keywords") as f:
2335 props = f.readlines()
2336 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2337 for prop in props if " - " in prop)
2340 with os.popen("git diff-index --name-only "
2341 "--relative --diff-filter=M HEAD") as f:
2342 mychanged = f.readlines()
2343 mychanged = ["./" + elem[:-1] for elem in mychanged]
2345 with os.popen("git diff-index --name-only "
2346 "--relative --diff-filter=A HEAD") as f:
2347 mynew = f.readlines()
2348 mynew = ["./" + elem[:-1] for elem in mynew]
2350 with os.popen("git diff-index --name-only "
2351 "--relative --diff-filter=D HEAD") as f:
2352 myremoved = f.readlines()
2353 myremoved = ["./" + elem[:-1] for elem in myremoved]
2356 with os.popen("bzr status -S .") as f:
2357 bzrstatus = f.readlines()
2358 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
2359 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" ) ]
2360 myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
2361 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" ) ]
2362 # Bazaar expands nothing.
2365 with os.popen("hg status --no-status --modified .") as f:
2366 mychanged = f.readlines()
2367 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2369 with os.popen("hg status --no-status --added .") as f:
2370 mynew = f.readlines()
2371 mynew = ["./" + elem.rstrip() for elem in mynew]
2373 with os.popen("hg status --no-status --removed .") as f:
2374 myremoved = f.readlines()
2375 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2378 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2379 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2381 print("(Didn't find any changed files...)")
2385 # Manifests need to be regenerated after all other commits, so don't commit
2386 # them now even if they have changed.
2389 for f in mychanged + mynew:
2390 if "Manifest" == os.path.basename(f):
2394 myupdates.difference_update(myremoved)
2395 myupdates = list(myupdates)
2396 mymanifests = list(mymanifests)
2400 commitmessage = options.commitmsg
2401 if options.commitmsgfile:
2403 f = io.open(_unicode_encode(options.commitmsgfile,
2404 encoding=_encodings['fs'], errors='strict'),
2405 mode='r', encoding=_encodings['content'], errors='replace')
2406 commitmessage = f.read()
2409 except (IOError, OSError) as e:
2410 if e.errno == errno.ENOENT:
2411 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2414 # We've read the content so the file is no longer needed.
2415 commitmessagefile = None
2416 if not commitmessage or not commitmessage.strip():
2418 editor = os.environ.get("EDITOR")
2419 if editor and utilities.editor_is_executable(editor):
2420 commitmessage = utilities.get_commit_message_with_editor(
2421 editor, message=qa_output)
2423 commitmessage = utilities.get_commit_message_with_stdin()
2424 except KeyboardInterrupt:
2426 if not commitmessage or not commitmessage.strip():
2427 print("* no commit message? aborting commit.")
2429 commitmessage = commitmessage.rstrip()
2430 changelog_msg = commitmessage
2431 portage_version = getattr(portage, "VERSION", None)
2432 if portage_version is None:
2433 sys.stderr.write("Failed to insert portage version in message!\n")
2435 portage_version = "Unknown"
2436 unameout = platform.system() + " "
2437 if platform.system() in ["Darwin", "SunOS"]:
2438 unameout += platform.processor()
2440 unameout += platform.machine()
2441 commitmessage += "\n\n(Portage version: %s/%s/%s" % \
2442 (portage_version, vcs, unameout)
2444 commitmessage += ", RepoMan options: --force"
2445 commitmessage += ")"
2447 if options.ask and userquery('Commit changes?', True) != 'Yes':
2448 print("* aborting commit.")
2451 if options.echangelog in ('y', 'force'):
2452 logging.info("checking for unmodified ChangeLog files")
2453 committer_name = utilities.get_committer_name(env=repoman_settings)
2454 for x in sorted(vcs_files_to_cps(
2455 chain(myupdates, mymanifests, myremoved))):
2456 catdir, pkgdir = x.split("/")
2457 checkdir = repodir + "/" + x
2458 checkdir_relative = ""
2460 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2462 checkdir_relative = os.path.join(catdir, checkdir_relative)
2463 checkdir_relative = os.path.join(".", checkdir_relative)
2465 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2466 changelog_modified = changelog_path in modified_changelogs
2467 if changelog_modified and options.echangelog != 'force':
2470 # get changes for this package
2471 cdrlen = len(checkdir_relative)
2472 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2473 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2474 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2475 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2476 committer_name, changelog_msg,
2477 os.path.join(repodir, 'skel.ChangeLog'),
2479 new=clnew, removed=clremoved, changed=clchanged,
2480 pretend=options.pretend)
2481 if new_changelog is None:
2482 writemsg_level("!!! Updating the ChangeLog failed\n", \
2483 level=logging.ERROR, noiselevel=-1)
2486 # if the ChangeLog was just created, add it to vcs
2488 myautoadd.append(changelog_path)
2489 # myautoadd is appended to myupdates below
2491 myupdates.append(changelog_path)
2494 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2495 add_cmd = [vcs, "add"]
2496 add_cmd += myautoadd
2498 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2501 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2502 # Python 3.1 produces the following TypeError if raw bytes are
2503 # passed to subprocess.call():
2504 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2505 # errread, errwrite)
2506 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2507 # raise child_exception
2508 # TypeError: expected an object with the buffer interface
2509 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2510 retcode = subprocess.call(add_cmd)
2511 if retcode != os.EX_OK:
2513 "Exiting on %s error code: %s\n" % (vcs, retcode))
2516 myupdates += myautoadd
2518 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2520 if vcs not in ('cvs', 'svn'):
2521 # With git, bzr and hg, there's never any keyword expansion, so
2522 # there's no need to regenerate manifests and all files will be
2523 # committed in one big commit at the end.
2525 elif not repo_config.thin_manifest:
2527 headerstring = "'\$(Header|Id).*\$'"
2529 svn_keywords = dict((k.lower(), k) for k in [
2532 "LastChangedRevision",
2543 for myfile in myupdates:
2545 # for CVS, no_expansion contains files that are excluded from expansion
2547 if myfile in no_expansion:
2550 # for SVN, expansion contains files that are included in expansion
2552 if myfile not in expansion:
2555 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2556 enabled_keywords = []
2557 for k in expansion[myfile]:
2558 keyword = svn_keywords.get(k.lower())
2559 if keyword is not None:
2560 enabled_keywords.append(keyword)
2562 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2564 myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile)
2566 myheaders.append(myfile)
2568 print("%s have headers that will change." % green(str(len(myheaders))))
2569 print("* Files with headers will cause the manifests to be changed and committed separately.")
2571 logging.info("myupdates: %s", myupdates)
2572 logging.info("myheaders: %s", myheaders)
2574 # Handle the case where committed files have keywords which
2575 # will change and need a priming commit before the Manifest
2577 if (myupdates or myremoved) and myheaders:
2578 myfiles = myupdates + myremoved
2579 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2580 mymsg = os.fdopen(fd, "wb")
2581 mymsg.write(_unicode_encode(commitmessage))
2585 print(green("Using commit message:"))
2586 print(green("------------------------------------------------------------------------------"))
2587 print(commitmessage)
2588 print(green("------------------------------------------------------------------------------"))
2591 # Having a leading ./ prefix on file paths can trigger a bug in
2592 # the cvs server when committing files to multiple directories,
2593 # so strip the prefix.
2594 myfiles = [f.lstrip("./") for f in myfiles]
2597 commit_cmd.extend(vcs_global_opts)
2598 commit_cmd.append("commit")
2599 commit_cmd.extend(vcs_local_opts)
2600 commit_cmd.extend(["-F", commitmessagefile])
2601 commit_cmd.extend(myfiles)
2605 print("(%s)" % (" ".join(commit_cmd),))
2607 retval = spawn(commit_cmd, env=os.environ)
2608 if retval != os.EX_OK:
2609 writemsg_level(("!!! Exiting on %s (shell) " + \
2610 "error code: %s\n") % (vcs, retval),
2611 level=logging.ERROR, noiselevel=-1)
2615 os.unlink(commitmessagefile)
2619 # Setup the GPG commands
2620 def gpgsign(filename):
2621 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2623 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2624 " Is make.globals missing?")
2625 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2626 "PORTAGE_GPG_KEY" not in repoman_settings:
2627 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2628 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2629 if "PORTAGE_GPG_DIR" not in repoman_settings:
2630 repoman_settings["PORTAGE_GPG_DIR"] = \
2631 os.path.expanduser("~/.gnupg")
2632 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2633 % repoman_settings["PORTAGE_GPG_DIR"])
2635 repoman_settings["PORTAGE_GPG_DIR"] = \
2636 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2637 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2638 raise portage.exception.InvalidLocation(
2639 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2640 repoman_settings["PORTAGE_GPG_DIR"])
2641 gpgvars = {"FILE": filename}
2642 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2643 v = repoman_settings.get(k)
2646 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2648 print("("+gpgcmd+")")
2650 rValue = os.system(gpgcmd)
2651 if rValue == os.EX_OK:
2652 os.rename(filename+".asc", filename)
2654 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2656 # When files are removed and re-added, the cvs server will put /Attic/
2657 # inside the $Header path. This code detects the problem and corrects it
2658 # so that the Manifest will generate correctly. See bug #169500.
2659 # Use binary mode in order to avoid potential character encoding issues.
2660 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2661 attic_str = b'/Attic/'
2662 attic_replace = b'/'
2664 f = open(_unicode_encode(x,
2665 encoding=_encodings['fs'], errors='strict'),
2667 mylines = f.readlines()
2670 for i, line in enumerate(mylines):
2671 if cvs_header_re.match(line) is not None and \
2673 mylines[i] = line.replace(attic_str, attic_replace)
2676 portage.util.write_atomic(x, b''.join(mylines),
2680 print(green("RepoMan sez:"), "\"You're rather crazy... "
2681 "doing the entire repository.\"\n")
2683 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2685 for x in sorted(vcs_files_to_cps(
2686 chain(myupdates, myremoved, mymanifests))):
2687 repoman_settings["O"] = os.path.join(repodir, x)
2688 digestgen(mysettings=repoman_settings, myportdb=portdb)
2694 for x in sorted(vcs_files_to_cps(
2695 chain(myupdates, myremoved, mymanifests))):
2696 repoman_settings["O"] = os.path.join(repodir, x)
2697 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2698 except portage.exception.PortageException as e:
2699 portage.writemsg("!!! %s\n" % str(e))
2700 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2704 # It's not safe to use the git commit -a option since there might
2705 # be some modified files elsewhere in the working tree that the
2706 # user doesn't want to commit. Therefore, call git update-index
2707 # in order to ensure that the index is updated with the latest
2708 # versions of all new and modified files in the relevant portion
2709 # of the working tree.
2710 myfiles = mymanifests + myupdates
2712 update_index_cmd = ["git", "update-index"]
2713 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2715 print("(%s)" % (" ".join(update_index_cmd),))
2717 retval = spawn(update_index_cmd, env=os.environ)
2718 if retval != os.EX_OK:
2719 writemsg_level(("!!! Exiting on %s (shell) " + \
2720 "error code: %s\n") % (vcs, retval),
2721 level=logging.ERROR, noiselevel=-1)
2726 myfiles = mymanifests[:]
2727 # If there are no header (SVN/CVS keywords) changes in
2728 # the files, this Manifest commit must include the
2729 # other (yet uncommitted) files.
2731 myfiles += myupdates
2732 myfiles += myremoved
2735 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2736 mymsg = os.fdopen(fd, "wb")
2737 # strip the closing parenthesis
2738 mymsg.write(_unicode_encode(commitmessage[:-1]))
2740 mymsg.write(_unicode_encode(
2741 ", signed Manifest commit with key %s)" % \
2742 repoman_settings["PORTAGE_GPG_KEY"]))
2744 mymsg.write(b", unsigned Manifest commit)")
2748 if options.pretend and vcs is None:
2749 # substitute a bogus value for pretend output
2750 commit_cmd.append("cvs")
2752 commit_cmd.append(vcs)
2753 commit_cmd.extend(vcs_global_opts)
2754 commit_cmd.append("commit")
2755 commit_cmd.extend(vcs_local_opts)
2757 commit_cmd.extend(["--logfile", commitmessagefile])
2758 commit_cmd.extend(myfiles)
2760 commit_cmd.extend(["-F", commitmessagefile])
2761 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2765 print("(%s)" % (" ".join(commit_cmd),))
2767 retval = spawn(commit_cmd, env=os.environ)
2768 if retval != os.EX_OK:
2769 writemsg_level(("!!! Exiting on %s (shell) " + \
2770 "error code: %s\n") % (vcs, retval),
2771 level=logging.ERROR, noiselevel=-1)
2775 os.unlink(commitmessagefile)
2781 print("Commit complete.")
2783 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
2784 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")