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
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
48 except (ImportError, SystemError):
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.manifest import _prohibited_filename_chars_re as \
73 disallowed_filename_chars_re
74 from portage.process import find_binary, spawn
75 from portage.output import bold, create_color_func, \
77 from portage.output import ConsoleStyleFile, StyleWriter
78 from portage.util import cmp_sort_key, writemsg_level
79 from portage.util._desktop_entry import validate_desktop_entry
80 from portage.package.ebuild.digestgen import digestgen
81 from portage.eapi import eapi_has_iuse_defaults, eapi_has_required_use
83 if sys.hexversion >= 0x3000000:
86 util.initialize_logger()
88 # 14 is the length of DESCRIPTION=""
90 allowed_filename_chars="a-zA-Z0-9._-+:"
91 pv_toolong_re = re.compile(r'[0-9]{19,}')
92 bad = create_color_func("BAD")
94 # A sane umask is needed for files that portage creates.
96 # Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
97 # behave incrementally.
98 repoman_incrementals = tuple(x for x in \
99 portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
100 config_root = os.environ.get("PORTAGE_CONFIGROOT")
101 repoman_settings = portage.config(config_root=config_root, local_config=False)
103 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
104 repoman_settings.get('TERM') == 'dumb' or \
105 not sys.stdout.isatty():
109 print("repoman: " + txt)
115 def exithandler(signum=None, frame=None):
116 logging.fatal("Interrupted; exiting...")
120 sys.exit(128 + signum)
122 signal.signal(signal.SIGINT,exithandler)
124 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
125 """Repoman needs it's own HelpFormatter for now, because the default ones
126 murder the help text."""
128 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
129 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
131 def format_description(self, description):
134 class RepomanOptionParser(optparse.OptionParser):
135 """Add the on_tail function, ruby has it, optionParser should too
138 def __init__(self, *args, **kwargs):
139 optparse.OptionParser.__init__(self, *args, **kwargs)
142 def on_tail(self, description):
143 self.tail += description
145 def format_help(self, formatter=None):
146 result = optparse.OptionParser.format_help(self, formatter)
151 def ParseArgs(argv, qahelp):
152 """This function uses a customized optionParser to parse command line arguments for repoman
154 argv - a sequence of command line arguments
155 qahelp - a dict of qa warning to help message
157 (opts, args), just like a call to parser.parse_args()
160 if argv and isinstance(argv[0], bytes):
161 argv = [portage._unicode_decode(x) for x in argv]
164 'commit' : 'Run a scan then commit changes',
165 'ci' : 'Run a scan then commit changes',
166 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
167 'full' : 'Scan directory tree and print all issues (not a summary)',
168 'help' : 'Show this screen',
169 'manifest' : 'Generate a Manifest (fetches files if necessary)',
170 'manifest-check' : 'Check Manifests for missing or incorrect digests',
171 'scan' : 'Scan directory tree for QA issues'
174 mode_keys = list(modes)
177 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
178 parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
179 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
180 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
181 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
183 parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
184 help='Request a confirmation before commiting')
186 parser.add_option('-m', '--commitmsg', dest='commitmsg',
187 help='specify a commit message on the command line')
189 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
190 help='specify a path to a file that contains a commit message')
192 parser.add_option('--digest',
193 type='choice', choices=('y', 'n'), metavar='<y|n>',
194 help='Automatically update Manifest digests for modified files')
196 parser.add_option('-p', '--pretend', dest='pretend', default=False,
197 action='store_true', help='don\'t commit or fix anything; just show what would be done')
199 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
200 help='do not print unnecessary messages')
203 '--echangelog', type='choice', choices=('y', 'n', 'force'), metavar="<y|n|force>",
204 help='for commit mode, call echangelog if ChangeLog is unmodified (or '
205 'regardless of modification if \'force\' is specified)')
207 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
208 help='Commit with QA violations')
210 parser.add_option('--vcs', dest='vcs',
211 help='Force using specific VCS instead of autodetection')
213 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
214 help='be very verbose in output', default=0)
216 parser.add_option('-V', '--version', dest='version', action='store_true',
217 help='show version info')
219 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
220 default=False, help='forces the metadata.xml parse check to be carried out')
223 '--if-modified', type='choice', choices=('y', 'n'), default='n',
225 help='only check packages that have uncommitted modifications')
227 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
228 default=False, help='ignore arch-specific failures (where arch != host)')
230 parser.add_option("--ignore-default-opts",
232 help="do not use the REPOMAN_DEFAULT_OPTS environment variable")
234 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
235 default=False, help='ignore masked packages (not allowed with commit mode)')
237 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
238 default=False, help='include dev profiles in dependency checks')
240 parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
241 default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
243 parser.add_option('--without-mask', dest='without_mask', action='store_true',
244 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
246 parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
247 help='specify which mode repoman will run in (default=full)')
249 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
252 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
254 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
256 sorted_qa = list(qahelp)
259 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
261 opts, args = parser.parse_args(argv[1:])
263 if not opts.ignore_default_opts:
264 default_opts = repoman_settings.get("REPOMAN_DEFAULT_OPTS", "").split()
266 opts, args = parser.parse_args(default_opts + sys.argv[1:])
268 if opts.mode == 'help':
269 parser.print_help(short=False)
277 parser.error("invalid mode: %s" % arg)
282 if opts.mode == 'ci':
283 opts.mode = 'commit' # backwards compat shortcut
285 if opts.mode == 'commit' and not (opts.force or opts.pretend):
286 if opts.ignore_masked:
287 parser.error('Commit mode and --ignore-masked are not compatible')
288 if opts.without_mask:
289 parser.error('Commit mode and --without-mask are not compatible')
291 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
292 for val in range(opts.verbosity):
293 logger = logging.getLogger()
294 logger.setLevel(logger.getEffectiveLevel() - 10)
296 for val in range(opts.quiet):
297 logger = logging.getLogger()
298 logger.setLevel(logger.getEffectiveLevel() + 10)
303 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
304 "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
305 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
306 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
307 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
308 "changelog.missing":"Missing ChangeLog files",
309 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
310 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
311 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
312 "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)",
313 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do not need the executable bit",
314 "file.size":"Files in the files directory must be under 20 KiB",
315 "file.size.fatal":"Files in the files directory must be under 60 KiB",
316 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
317 "file.UTF8":"File is not UTF8 compliant",
318 "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf",
319 "inherit.deprecated":"Ebuild inherits a deprecated eclass",
320 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
321 "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
322 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
323 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
324 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
325 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
326 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
327 "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
328 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
329 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
330 "EAPI.definition":"EAPI definition does not conform to PMS section 8.3.1 (first non-comment, non-blank line)",
331 "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
332 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
333 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
334 "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
335 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
336 "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
337 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
338 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
339 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
340 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
341 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
342 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
343 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
344 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
345 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
346 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
347 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
348 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
349 "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
350 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
351 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
352 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
353 "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
354 "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
355 "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
356 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
357 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
358 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
359 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
360 "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
361 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
362 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
363 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
364 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
365 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
366 "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
367 "variable.readonly":"Assigning a readonly variable",
368 "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
369 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
370 "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
371 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
372 "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
373 "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)",
374 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
375 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
376 "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
377 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
378 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
379 "digest.assumed":"Existing digest must be assumed correct (Package level only)",
380 "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
381 "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
382 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
383 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
384 "ebuild.badheader":"This ebuild has a malformed header",
385 "eprefixify.defined":"The ebuild uses eprefixify, but does not inherit the prefix eclass",
386 "manifest.bad":"Manifest has missing or incorrect digests",
387 "metadata.missing":"Missing metadata.xml files",
388 "metadata.bad":"Bad metadata.xml files",
389 "metadata.warning":"Warnings in metadata.xml files",
390 "portage.internal":"The ebuild uses an internal Portage function",
391 "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
392 "usage.obsolete":"The ebuild makes use of an obsolete construct",
393 "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
396 qacats = list(qahelp)
401 "changelog.notadded",
402 "dependency.unknown",
408 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
409 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
410 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
411 "DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde",
412 "DESCRIPTION.toolong",
429 "inherit.deprecated",
430 "java.eclassesnotused",
431 "wxwidgets.eclassnotused",
435 "upstream.workaround",
440 non_ascii_re = re.compile(r'[^\x00-\x7f]')
442 missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
443 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
444 allvars.update(Package.metadata_keys)
445 allvars = sorted(allvars)
447 for x in missingvars:
450 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
454 valid_restrict = frozenset(["binchecks", "bindist",
455 "fetch", "installsources", "mirror",
456 "primaryuri", "strip", "test", "userpriv"])
458 live_eclasses = frozenset([
469 suspect_rdepend = frozenset([
470 "app-arch/cabextract",
471 "app-arch/rpm2targz",
476 "dev-perl/extutils-pkgconfig",
482 "dev-util/gtk-doc-am",
485 "dev-util/pkgconfig",
489 "media-gfx/ebdftopcf",
491 "sys-devel/autoconf",
492 "sys-devel/automake",
499 "virtual/linux-sources",
504 metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
505 # force refetch if the local copy creation time is older than this
506 metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
509 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
511 options, arguments = ParseArgs(sys.argv, qahelp)
514 print("Portage", portage.VERSION)
517 # Set this to False when an extraordinary issue (generally
518 # something other than a QA issue) makes it impossible to
519 # commit (like if Manifest generation fails).
522 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
526 myreporoot = os.path.basename(portdir_overlay)
527 myreporoot += mydir[len(portdir_overlay):]
530 if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
535 vcses = utilities.FindVCS()
537 print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
538 print(red('*** Please either clean up your workdir or specify --vcs option.'))
545 if options.if_modified == "y" and vcs is None:
546 logging.info("Not in a version controlled repository; "
547 "disabling --if-modified.")
548 options.if_modified = "n"
550 # Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
551 vcs_preserves_mtime = vcs in ('cvs',)
553 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
554 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
555 if vcs_global_opts is None:
556 if vcs in ('cvs', 'svn'):
557 vcs_global_opts = "-q"
560 vcs_global_opts = vcs_global_opts.split()
562 if options.mode == 'commit' and not options.pretend and not vcs:
563 logging.info("Not in a version controlled repository; enabling pretend mode.")
564 options.pretend = True
566 # Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
567 repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
568 (repoman_settings.get('PORTDIR_OVERLAY', ''),
569 portage._shell_quote(portdir_overlay))
570 # We have to call the config constructor again so
571 # that config.repositories is initialized correctly.
572 repoman_settings = portage.config(config_root=config_root, local_config=False,
573 env=dict(os.environ, PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
575 root = repoman_settings['EROOT']
577 root : {'porttree' : portage.portagetree(settings=repoman_settings)}
579 portdb = trees[root]['porttree'].dbapi
581 # Constrain dependency resolution to the master(s)
582 # that are specified in layout.conf.
583 repodir = os.path.realpath(portdir_overlay)
584 repo_config = repoman_settings.repositories.get_repo_for_location(repodir)
585 portdb.porttrees = list(repo_config.eclass_db.porttrees)
586 portdir = portdb.porttrees[0]
588 if repo_config.allow_provide_virtual:
589 qawarnings.add("virtual.oldstyle")
591 if repo_config.sign_commit:
593 # NOTE: It's possible to use --gpg-sign=key_id to specify the key in
594 # the commit arguments. If key_id is unspecified, then it must be
595 # configured by `git config user.signingkey key_id`.
596 vcs_local_opts.append("--gpg-sign")
598 # In order to disable manifest signatures, repos may set
599 # "sign-manifests = false" in metadata/layout.conf. This
600 # can be used to prevent merge conflicts like those that
601 # thin-manifests is designed to prevent.
602 sign_manifests = "sign" in repoman_settings.features and \
603 repo_config.sign_manifest
605 manifest_hashes = repo_config.manifest_hashes
606 if manifest_hashes is None:
607 manifest_hashes = portage.const.MANIFEST2_HASH_DEFAULTS
609 if options.mode in ("commit", "fix", "manifest"):
610 if portage.const.MANIFEST2_REQUIRED_HASH not in manifest_hashes:
611 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
612 "metadata/layout.conf does not contain the '%s' hash which "
613 "is required by this portage version. You will have to "
614 "upgrade portage if you want to generate valid manifests for "
615 "this repository.") % \
616 (repo_config.name, portage.const.MANIFEST2_REQUIRED_HASH)
617 for line in textwrap.wrap(msg, 70):
621 unsupported_hashes = manifest_hashes.difference(
622 portage.const.MANIFEST2_HASH_FUNCTIONS)
623 if unsupported_hashes:
624 msg = ("The 'manifest-hashes' setting in the '%s' repository's "
625 "metadata/layout.conf contains one or more hash types '%s' "
626 "which are not supported by this portage version. You will "
627 "have to upgrade portage if you want to generate valid "
628 "manifests for this repository.") % \
629 (repo_config.name, " ".join(sorted(unsupported_hashes)))
630 for line in textwrap.wrap(msg, 70):
634 if "commit" == options.mode and \
635 repo_config.name == "gentoo" and \
636 "RMD160" in manifest_hashes and \
637 "RMD160" not in portage.checksum.hashorigin_map:
638 msg = "Please install " \
639 "pycrypto or enable python's ssl USE flag in order " \
640 "to enable RMD160 hash support. See bug #198398 for " \
643 for line in textwrap.wrap(msg, 70):
647 if options.echangelog is None and repo_config.update_changelog:
648 options.echangelog = 'y'
651 options.echangelog = 'n'
653 # The --echangelog option causes automatic ChangeLog generation,
654 # which invalidates changelog.ebuildadded and changelog.missing
656 # Note: Some don't use ChangeLogs in distributed SCMs.
657 # It will be generated on server side from scm log,
658 # before package moves to the rsync server.
659 # This is needed because they try to avoid merge collisions.
660 # Gentoo's Council decided to always use the ChangeLog file.
661 # TODO: shouldn't this just be switched on the repo, iso the VCS?
662 check_changelog = options.echangelog not in ('y', 'force') and vcs in ('cvs', 'svn')
664 if 'digest' in repoman_settings.features and options.digest != 'n':
667 logging.debug("vcs: %s" % (vcs,))
668 logging.debug("repo config: %s" % (repo_config,))
669 logging.debug("options: %s" % (options,))
671 # Generate an appropriate PORTDIR_OVERLAY value for passing into the
672 # profile-specific config constructor calls.
673 env = os.environ.copy()
674 env['PORTDIR'] = portdir
675 env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
677 logging.info('Setting paths:')
678 logging.info('PORTDIR = "' + portdir + '"')
679 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
681 # It's confusing if these warnings are displayed without the user
682 # being told which profile they come from, so disable them.
683 env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
686 for path in repo_config.eclass_db.porttrees:
687 categories.extend(portage.util.grabfile(
688 os.path.join(path, 'profiles', 'categories')))
689 repoman_settings.categories = frozenset(
690 portage.util.stack_lists([categories], incremental=1))
691 categories = repoman_settings.categories
693 portdb.settings = repoman_settings
694 root_config = RootConfig(repoman_settings, trees[root], None)
695 # We really only need to cache the metadata that's necessary for visibility
696 # filtering. Anything else can be discarded to reduce memory consumption.
697 portdb._aux_cache_keys.clear()
698 portdb._aux_cache_keys.update(["EAPI", "IUSE", "KEYWORDS", "repository", "SLOT"])
700 reposplit = myreporoot.split(os.path.sep)
701 repolevel = len(reposplit)
703 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
704 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
705 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
706 if options.mode == 'commit' and repolevel not in [1,2,3]:
707 print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
708 print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
709 print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
711 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
713 # Make startdir relative to the canonical repodir, so that we can pass
714 # it to digestgen and it won't have to be canonicalized again.
718 startdir = normalize_path(mydir)
719 startdir = os.path.join(repodir, *startdir.split(os.sep)[-2-repolevel+3:])
722 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.")
724 class ProfileDesc(object):
725 __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
726 def __init__(self, arch, status, sub_path, tree_path):
730 sub_path = normalize_path(sub_path.lstrip(os.sep))
731 self.sub_path = sub_path
732 self.tree_path = tree_path
734 self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
736 self.abs_path = tree_path
741 return 'empty profile'
744 valid_profile_types = frozenset(['dev', 'exp', 'stable'])
746 # get lists of valid keywords, licenses, and use
750 global_pmasklines = []
752 for path in portdb.porttrees:
754 liclist.update(os.listdir(os.path.join(path, "licenses")))
757 kwlist.update(portage.grabfile(os.path.join(path,
758 "profiles", "arch.list")))
760 use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
766 expand_desc_dir = os.path.join(path, 'profiles', 'desc')
768 expand_list = os.listdir(expand_desc_dir)
772 for fn in expand_list:
773 if not fn[-5:] == '.desc':
775 use_prefix = fn[:-5].lower() + '_'
776 for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
779 uselist.add(use_prefix + x[0])
781 global_pmasklines.append(portage.util.grabfile_package(
782 os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
784 desc_path = os.path.join(path, 'profiles', 'profiles.desc')
786 desc_file = io.open(_unicode_encode(desc_path,
787 encoding=_encodings['fs'], errors='strict'),
788 mode='r', encoding=_encodings['repo.content'], errors='replace')
789 except EnvironmentError:
792 for i, x in enumerate(desc_file):
799 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
800 desc_path + " line %d" % (i+1, ))
801 elif arch[0] not in kwlist:
802 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
803 desc_path + " line %d" % (i+1, ))
804 elif arch[2] not in valid_profile_types:
805 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
806 desc_path + " line %d" % (i+1, ))
807 profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
808 if not os.path.isdir(profile_desc.abs_path):
810 "Invalid %s profile (%s) for arch %s in %s line %d",
811 arch[2], arch[1], arch[0], desc_path, i+1)
814 os.path.join(profile_desc.abs_path, 'deprecated')):
816 profile_list.append(profile_desc)
819 repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
820 repoman_settings.backup_changes('PORTAGE_ARCHLIST')
822 global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
823 global_pmaskdict = {}
824 for x in global_pmasklines:
825 global_pmaskdict.setdefault(x.cp, []).append(x)
826 del global_pmasklines
828 def has_global_mask(pkg):
829 mask_atoms = global_pmaskdict.get(pkg.cp)
833 if portage.dep.match_from_list(x, pkg_list):
837 # Ensure that profile sub_path attributes are unique. Process in reverse order
838 # so that profiles with duplicate sub_path from overlays will override
839 # profiles with the same sub_path from parent repos.
841 profile_list.reverse()
842 profile_sub_paths = set()
843 for prof in profile_list:
844 if prof.sub_path in profile_sub_paths:
846 profile_sub_paths.add(prof.sub_path)
847 profiles.setdefault(prof.arch, []).append(prof)
849 # Use an empty profile for checking dependencies of
850 # packages that have empty KEYWORDS.
851 prof = ProfileDesc('**', 'stable', '', '')
852 profiles.setdefault(prof.arch, []).append(prof)
854 for x in repoman_settings.archlist():
857 if x not in profiles:
858 print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
859 print(red("You need to either \"cvs update\" your profiles dir or follow this"))
860 print(red("up with the "+x+" team."))
864 logging.fatal("Couldn't find licenses?")
868 logging.fatal("Couldn't read KEYWORDS from arch.list")
872 logging.fatal("Couldn't find use.desc?")
877 #we are inside a category directory
879 if catdir not in categories:
881 mydirlist=os.listdir(startdir)
883 if x == "CVS" or x.startswith("."):
885 if os.path.isdir(startdir+"/"+x):
886 scanlist.append(catdir+"/"+x)
887 repo_subdir = catdir + os.sep
890 if not os.path.isdir(startdir+"/"+x):
892 for y in os.listdir(startdir+"/"+x):
893 if y == "CVS" or y.startswith("."):
895 if os.path.isdir(startdir+"/"+x+"/"+y):
896 scanlist.append(x+"/"+y)
899 catdir = reposplit[-2]
900 if catdir not in categories:
902 scanlist.append(catdir+"/"+reposplit[-1])
903 repo_subdir = scanlist[-1] + os.sep
905 msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
906 ' from the current working directory'
907 logging.critical(msg)
910 repo_subdir_len = len(repo_subdir)
913 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
915 def vcs_files_to_cps(vcs_file_iter):
917 Iterate over the given modified file paths returned from the vcs,
918 and return a frozenset containing category/pn strings for each
925 if reposplit[-2] in categories and \
926 next(vcs_file_iter, None) is not None:
927 modified_cps.append("/".join(reposplit[-2:]))
930 category = reposplit[-1]
931 if category in categories:
932 for filename in vcs_file_iter:
933 f_split = filename.split(os.sep)
936 modified_cps.append(category + "/" + f_split[1])
940 for filename in vcs_file_iter:
941 f_split = filename.split(os.sep)
942 # ['.', category, pn,...]
943 if len(f_split) > 3 and f_split[1] in categories:
944 modified_cps.append("/".join(f_split[1:3]))
946 return frozenset(modified_cps)
948 def git_supports_gpg_sign():
949 status, cmd_output = \
950 subprocess_getstatusoutput("git --version")
951 cmd_output = cmd_output.split()
953 version = re.match(r'^(\d+)\.(\d+)\.(\d+)', cmd_output[-1])
954 if version is not None:
955 version = [int(x) for x in version.groups()[1:]]
956 if version[0] > 1 or \
957 (version[0] == 1 and version[1] > 7) or \
958 (version[0] == 1 and version[1] == 7 and version[2] >= 9):
962 def dev_keywords(profiles):
964 Create a set of KEYWORDS values that exist in 'dev'
965 profiles. These are used
966 to trigger a message notifying the user when they might
967 want to add the --include-dev option.
970 for arch, arch_profiles in profiles.items():
971 for prof in arch_profiles:
972 arch_set = type_arch_map.get(prof.status)
975 type_arch_map[prof.status] = arch_set
978 dev_keywords = type_arch_map.get('dev', set())
979 dev_keywords.update(['~' + arch for arch in dev_keywords])
980 return frozenset(dev_keywords)
982 dev_keywords = dev_keywords(profiles)
987 # provided by the desktop-file-utils package
988 desktop_file_validate = find_binary("desktop-file-validate")
989 desktop_pattern = re.compile(r'.*\.desktop$')
995 xmllint_capable = False
996 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
999 """Parse a RFC 822 date and time string.
1000 This is required for python3 compatibility, since the
1001 rfc822.parsedate() function is not available."""
1004 for x in s.upper().split():
1005 for y in x.split(','):
1009 if len(s_split) != 6:
1012 # %a, %d %b %Y %H:%M:%S %Z
1013 a, d, b, Y, H_M_S, Z = s_split
1015 # Convert month to integer, since strptime %w is locale-dependent.
1016 month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
1017 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
1018 m = month_map.get(b)
1021 m = str(m).rjust(2, '0')
1023 return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
1025 def fetch_metadata_dtd():
1027 Fetch metadata.dtd if it doesn't exist or the ctime is older than
1028 metadata_dtd_ctime_interval.
1030 @return: True if successful, otherwise False
1034 metadata_dtd_st = None
1035 current_time = int(time.time())
1037 metadata_dtd_st = os.stat(metadata_dtd)
1038 except EnvironmentError as e:
1039 if e.errno not in (errno.ENOENT, errno.ESTALE):
1043 # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
1044 if abs(current_time - metadata_dtd_st.st_ctime) \
1045 < metadata_dtd_ctime_interval:
1050 print(green("***") + " the local copy of metadata.dtd " + \
1051 "needs to be refetched, doing that now")
1054 url_f = urllib_request_urlopen(metadata_dtd_uri)
1055 msg_info = url_f.info()
1056 last_modified = msg_info.get('last-modified')
1057 if last_modified is not None:
1058 last_modified = parsedate(last_modified)
1059 if last_modified is not None:
1060 last_modified = calendar.timegm(last_modified)
1062 metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
1064 local_f = open(metadata_dtd_tmp, mode='wb')
1065 local_f.write(url_f.read())
1067 if last_modified is not None:
1069 os.utime(metadata_dtd_tmp,
1070 (int(last_modified), int(last_modified)))
1072 # This fails on some odd non-unix-like filesystems.
1073 # We don't really need the mtime to be preserved
1074 # anyway here (currently we use ctime to trigger
1075 # fetch), so just ignore it.
1077 os.rename(metadata_dtd_tmp, metadata_dtd)
1080 os.unlink(metadata_dtd_tmp)
1086 except EnvironmentError as e:
1088 print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
1089 print(red("!!!")+" exception '%s' though." % (e,))
1090 print(red("!!!")+" fetching new metadata.dtd failed, aborting")
1095 if options.mode == "manifest":
1097 elif not find_binary('xmllint'):
1098 print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
1099 if options.xml_parse or repolevel==3:
1100 print(red("!!!")+" sorry, xmllint is needed. failing\n")
1103 if not fetch_metadata_dtd():
1105 #this can be problematic if xmllint changes their output
1106 xmllint_capable=True
1108 if options.mode == 'commit' and vcs:
1109 utilities.detect_vcs_conflicts(options, vcs)
1111 if options.mode == "manifest":
1113 elif options.pretend:
1114 print(green("\nRepoMan does a once-over of the neighborhood..."))
1116 print(green("\nRepoMan scours the neighborhood..."))
1119 modified_ebuilds = set()
1120 modified_changelogs = set()
1126 mycvstree = cvstree.getentries("./", recursive=1)
1127 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1128 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1129 if options.if_modified == "y":
1130 myremoved = cvstree.findremoved(mycvstree, recursive=1, basedir="./")
1133 with os.popen("svn status") as f:
1134 svnstatus = f.readlines()
1135 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1136 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1137 if options.if_modified == "y":
1138 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
1141 with os.popen("git diff-index --name-only "
1142 "--relative --diff-filter=M HEAD") as f:
1143 mychanged = f.readlines()
1144 mychanged = ["./" + elem[:-1] for elem in mychanged]
1146 with os.popen("git diff-index --name-only "
1147 "--relative --diff-filter=A HEAD") as f:
1148 mynew = f.readlines()
1149 mynew = ["./" + elem[:-1] for elem in mynew]
1150 if options.if_modified == "y":
1151 with os.popen("git diff-index --name-only "
1152 "--relative --diff-filter=D HEAD") as f:
1153 myremoved = f.readlines()
1154 myremoved = ["./" + elem[:-1] for elem in myremoved]
1157 with os.popen("bzr status -S .") as f:
1158 bzrstatus = f.readlines()
1159 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
1160 mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
1161 if options.if_modified == "y":
1162 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" ) ]
1165 with os.popen("hg status --no-status --modified .") as f:
1166 mychanged = f.readlines()
1167 mychanged = ["./" + elem.rstrip() for elem in mychanged]
1168 mynew = os.popen("hg status --no-status --added .").readlines()
1169 mynew = ["./" + elem.rstrip() for elem in mynew]
1170 if options.if_modified == "y":
1171 with os.popen("hg status --no-status --removed .") as f:
1172 myremoved = f.readlines()
1173 myremoved = ["./" + elem.rstrip() for elem in myremoved]
1176 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
1177 modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
1178 modified_changelogs.update(x for x in chain(mychanged, mynew) \
1179 if os.path.basename(x) == "ChangeLog")
1181 def vcs_new_changed(relative_path):
1182 for x in chain(mychanged, mynew):
1183 if x == relative_path:
1187 have_pmasked = False
1188 have_dev_keywords = False
1191 # NOTE: match-all caches are not shared due to potential
1192 # differences between profiles in _get_implicit_iuse.
1194 arch_xmatch_caches = {}
1195 shared_xmatch_caches = {"cp-list":{}}
1197 # Disable the "ebuild.notadded" check when not in commit mode and
1198 # running `svn status` in every package dir will be too expensive.
1200 check_ebuild_notadded = not \
1201 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
1203 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
1204 thirdpartymirrors = []
1205 for v in repoman_settings.thirdpartymirrors().values():
1207 if not v.endswith("/"):
1209 thirdpartymirrors.append(v)
1211 class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
1213 Implements doctype() as required to avoid deprecation warnings with
1216 def doctype(self, name, pubid, system):
1220 herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
1221 except (EnvironmentError, ParseError, PermissionDenied) as e:
1223 except FileNotFound:
1224 # TODO: Download as we do for metadata.dtd, but add a way to
1225 # disable for non-gentoo repoman users who may not have herds.
1228 effective_scanlist = scanlist
1229 if options.if_modified == "y":
1230 effective_scanlist = sorted(vcs_files_to_cps(
1231 chain(mychanged, mynew, myremoved)))
1233 for x in effective_scanlist:
1234 #ebuilds and digests added to cvs respectively.
1235 logging.info("checking package %s" % x)
1236 # save memory by discarding xmatch caches from previous package(s)
1237 arch_xmatch_caches.clear()
1239 catdir,pkgdir=x.split("/")
1240 checkdir=repodir+"/"+x
1241 checkdir_relative = ""
1243 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
1245 checkdir_relative = os.path.join(catdir, checkdir_relative)
1246 checkdir_relative = os.path.join(".", checkdir_relative)
1247 generated_manifest = False
1249 if options.mode == "manifest" or \
1250 (options.mode != 'manifest-check' and options.digest == 'y') or \
1251 options.mode in ('commit', 'fix') and not options.pretend:
1252 auto_assumed = set()
1253 fetchlist_dict = portage.FetchlistDict(checkdir,
1254 repoman_settings, portdb)
1255 if options.mode == 'manifest' and options.force:
1256 portage._doebuild_manifest_exempt_depend += 1
1258 distdir = repoman_settings['DISTDIR']
1259 mf = repoman_settings.repositories.get_repo_for_location(
1260 os.path.dirname(os.path.dirname(checkdir)))
1261 mf = mf.load_manifest(checkdir, distdir,
1262 fetchlist_dict=fetchlist_dict)
1263 mf.create(requiredDistfiles=None,
1264 assumeDistHashesAlways=True)
1265 for distfiles in fetchlist_dict.values():
1266 for distfile in distfiles:
1267 if os.path.isfile(os.path.join(distdir, distfile)):
1268 mf.fhashdict['DIST'].pop(distfile, None)
1270 auto_assumed.add(distfile)
1273 portage._doebuild_manifest_exempt_depend -= 1
1275 repoman_settings["O"] = checkdir
1277 generated_manifest = digestgen(
1278 mysettings=repoman_settings, myportdb=portdb)
1279 except portage.exception.PermissionDenied as e:
1280 generated_manifest = False
1281 writemsg_level("!!! Permission denied: '%s'\n" % (e,),
1282 level=logging.ERROR, noiselevel=-1)
1284 if not generated_manifest:
1285 print("Unable to generate manifest.")
1288 if options.mode == "manifest":
1289 if not dofail and options.force and auto_assumed and \
1290 'assume-digests' in repoman_settings.features:
1291 # Show which digests were assumed despite the --force option
1292 # being given. This output will already have been shown by
1293 # digestgen() if assume-digests is not enabled, so only show
1294 # it here if assume-digests is enabled.
1295 pkgs = list(fetchlist_dict)
1297 portage.writemsg_stdout(" digest.assumed" + \
1298 portage.output.colorize("WARN",
1299 str(len(auto_assumed)).rjust(18)) + "\n")
1301 fetchmap = fetchlist_dict[cpv]
1302 pf = portage.catsplit(cpv)[1]
1303 for distfile in sorted(fetchmap):
1304 if distfile in auto_assumed:
1305 portage.writemsg_stdout(
1306 " %s::%s\n" % (pf, distfile))
1311 if not generated_manifest:
1312 repoman_settings['O'] = checkdir
1313 repoman_settings['PORTAGE_QUIET'] = '1'
1314 if not portage.digestcheck([], repoman_settings, strict=1):
1315 stats["manifest.bad"] += 1
1316 fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
1317 repoman_settings.pop('PORTAGE_QUIET', None)
1319 if options.mode == 'manifest-check':
1322 checkdirlist=os.listdir(checkdir)
1326 for y in checkdirlist:
1327 if (y in no_exec or y.endswith(".ebuild")) and \
1328 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
1329 stats["file.executable"] += 1
1330 fails["file.executable"].append(os.path.join(checkdir, y))
1331 if y.endswith(".ebuild"):
1333 ebuildlist.append(pf)
1334 cpv = "%s/%s" % (catdir, pf)
1336 myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
1339 stats["ebuild.syntax"] += 1
1340 fails["ebuild.syntax"].append(os.path.join(x, y))
1344 stats["ebuild.output"] += 1
1345 fails["ebuild.output"].append(os.path.join(x, y))
1347 if not portage.eapi_is_supported(myaux["EAPI"]):
1349 stats["EAPI.unsupported"] += 1
1350 fails["EAPI.unsupported"].append(os.path.join(x, y))
1352 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
1353 root_config=root_config, type_name="ebuild")
1355 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
1357 for i in range(len(ebuildlist)):
1358 ebuild_split = portage.pkgsplit(ebuildlist[i])
1359 pkgsplits[ebuild_split] = ebuildlist[i]
1360 ebuildlist[i] = ebuild_split
1361 ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp))
1362 for i in range(len(ebuildlist)):
1363 ebuildlist[i] = pkgsplits[ebuildlist[i]]
1368 if len(pkgs) != len(ebuildlist):
1369 # If we can't access all the metadata then it's totally unsafe to
1370 # commit since there's no way to generate a correct Manifest.
1371 # Do not try to do any more QA checks on this package since missing
1372 # metadata leads to false positives for several checks, and false
1373 # positives confuse users.
1377 for y in checkdirlist:
1378 m = disallowed_filename_chars_re.search(y.strip(os.sep))
1380 y_relative = os.path.join(checkdir_relative, y)
1381 if vcs is not None and not vcs_new_changed(y_relative):
1382 # If the file isn't in the VCS new or changed set, then
1383 # assume that it's an irrelevant temporary file (Manifest
1384 # entries are not generated for file names containing
1385 # prohibited characters). See bug #406877.
1388 stats["file.name"] += 1
1389 fails["file.name"].append("%s/%s: char '%s'" % \
1390 (checkdir, y, m.group(0)))
1392 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
1397 f = io.open(_unicode_encode(os.path.join(checkdir, y),
1398 encoding=_encodings['fs'], errors='strict'),
1399 mode='r', encoding=_encodings['repo.content'])
1402 except UnicodeDecodeError as ue:
1403 stats["file.UTF8"] += 1
1404 s = ue.object[:ue.start]
1408 s = s[s.rfind("\n") + 1:]
1409 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
1414 if vcs in ("git", "hg") and check_ebuild_notadded:
1416 myf = os.popen("git ls-files --others %s" % \
1417 (portage._shell_quote(checkdir_relative),))
1419 myf = os.popen("hg status --no-status --unknown %s" % \
1420 (portage._shell_quote(checkdir_relative),))
1422 if l[:-1][-7:] == ".ebuild":
1423 stats["ebuild.notadded"] += 1
1424 fails["ebuild.notadded"].append(
1425 os.path.join(x, os.path.basename(l[:-1])))
1428 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
1431 myf=open(checkdir+"/CVS/Entries","r")
1433 myf = os.popen("svn status --depth=files --verbose " + checkdir)
1435 myf = os.popen("bzr ls -v --kind=file " + checkdir)
1436 myl = myf.readlines()
1442 splitl=l[1:].split("/")
1445 if splitl[0][-7:]==".ebuild":
1446 eadded.append(splitl[0][:-7])
1451 # tree conflict, new in subversion 1.6
1454 if l[-7:] == ".ebuild":
1455 eadded.append(os.path.basename(l[:-7]))
1460 if l[-7:] == ".ebuild":
1461 eadded.append(os.path.basename(l[:-7]))
1463 myf = os.popen("svn status " + checkdir)
1468 l = l.rstrip().split(' ')[-1]
1469 if l[-7:] == ".ebuild":
1470 eadded.append(os.path.basename(l[:-7]))
1473 stats["CVS/Entries.IO_error"] += 1
1474 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
1479 mf = repoman_settings.repositories.get_repo_for_location(
1480 os.path.dirname(os.path.dirname(checkdir)))
1481 mf = mf.load_manifest(checkdir, repoman_settings["DISTDIR"])
1482 mydigests=mf.getTypeDigests("DIST")
1484 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
1486 src_uri_error = False
1487 for mykey in fetchlist_dict:
1489 myfiles_all.extend(fetchlist_dict[mykey])
1490 except portage.exception.InvalidDependString as e:
1491 src_uri_error = True
1493 portdb.aux_get(mykey, ["SRC_URI"])
1495 # This will be reported as an "ebuild.syntax" error.
1498 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
1499 fails["SRC_URI.syntax"].append(
1500 "%s.ebuild SRC_URI: %s" % (mykey, e))
1502 if not src_uri_error:
1503 # This test can produce false positives if SRC_URI could not
1504 # be parsed for one or more ebuilds. There's no point in
1505 # producing a false error here since the root cause will
1506 # produce a valid error elsewhere, such as "SRC_URI.syntax"
1507 # or "ebuild.sytax".
1508 myfiles_all = set(myfiles_all)
1509 for entry in mydigests:
1510 if entry not in myfiles_all:
1511 stats["digest.unused"] += 1
1512 fails["digest.unused"].append(checkdir+"::"+entry)
1513 for entry in myfiles_all:
1514 if entry not in mydigests:
1515 stats["digest.missing"] += 1
1516 fails["digest.missing"].append(checkdir+"::"+entry)
1519 if os.path.exists(checkdir+"/files"):
1520 filesdirlist=os.listdir(checkdir+"/files")
1522 # recurse through files directory
1523 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
1525 y = filesdirlist.pop(0)
1526 relative_path = os.path.join(x, "files", y)
1527 full_path = os.path.join(repodir, relative_path)
1529 mystat = os.stat(full_path)
1530 except OSError as oe:
1532 # don't worry about it. it likely was removed via fix above.
1536 if S_ISDIR(mystat.st_mode):
1537 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
1538 if y == "CVS" or y == ".svn":
1540 for z in os.listdir(checkdir+"/files/"+y):
1541 if z == "CVS" or z == ".svn":
1543 filesdirlist.append(y+"/"+z)
1544 # Current policy is no files over 20 KiB, these are the checks. File size between
1545 # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
1546 elif mystat.st_size > 61440:
1547 stats["file.size.fatal"] += 1
1548 fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1549 elif mystat.st_size > 20480:
1550 stats["file.size"] += 1
1551 fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
1553 m = disallowed_filename_chars_re.search(
1554 os.path.basename(y.rstrip(os.sep)))
1556 y_relative = os.path.join(checkdir_relative, "files", y)
1557 if vcs is not None and not vcs_new_changed(y_relative):
1558 # If the file isn't in the VCS new or changed set, then
1559 # assume that it's an irrelevant temporary file (Manifest
1560 # entries are not generated for file names containing
1561 # prohibited characters). See bug #406877.
1564 stats["file.name"] += 1
1565 fails["file.name"].append("%s/files/%s: char '%s'" % \
1566 (checkdir, y, m.group(0)))
1568 if desktop_file_validate and desktop_pattern.match(y):
1569 cmd_output = validate_desktop_entry(full_path)
1571 # Note: in the future we may want to grab the
1572 # warnings in addition to the errors. We're
1573 # just doing errors now since we don't want
1574 # to generate too much noise at first.
1575 error_re = re.compile(r'.*\s*error:\s*(.*)')
1576 for line in cmd_output:
1577 error_match = error_re.match(line)
1578 if error_match is None:
1580 stats["desktop.invalid"] += 1
1581 fails["desktop.invalid"].append(
1582 relative_path + ': %s' % error_match.group(1))
1586 if check_changelog and "ChangeLog" not in checkdirlist:
1587 stats["changelog.missing"]+=1
1588 fails["changelog.missing"].append(x+"/ChangeLog")
1591 #metadata.xml file check
1592 if "metadata.xml" not in checkdirlist:
1593 stats["metadata.missing"]+=1
1594 fails["metadata.missing"].append(x+"/metadata.xml")
1595 #metadata.xml parse check
1597 metadata_bad = False
1599 # read metadata.xml into memory
1601 _metadata_xml = xml.etree.ElementTree.parse(
1602 os.path.join(checkdir, "metadata.xml"),
1603 parser=xml.etree.ElementTree.XMLParser(
1604 target=_MetadataTreeBuilder()))
1605 except (ExpatError, SyntaxError, EnvironmentError) as e:
1607 stats["metadata.bad"] += 1
1608 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1611 # load USE flags from metadata.xml
1613 musedict = utilities.parse_metadata_use(_metadata_xml)
1614 except portage.exception.ParseError as e:
1616 stats["metadata.bad"] += 1
1617 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1619 # Run other metadata.xml checkers
1621 utilities.check_metadata(_metadata_xml, herd_base)
1622 except (utilities.UnknownHerdsError, ) as e:
1624 stats["metadata.bad"] += 1
1625 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1628 #Only carry out if in package directory or check forced
1629 if xmllint_capable and not metadata_bad:
1630 # xmlint can produce garbage output even on success, so only dump
1631 # the ouput when it fails.
1632 st, out = subprocess_getstatusoutput(
1633 "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \
1634 (metadata_dtd, os.path.join(checkdir, "metadata.xml")))
1636 print(red("!!!") + " metadata.xml is invalid:")
1637 for z in out.splitlines():
1638 print(red("!!! ")+z)
1639 stats["metadata.bad"]+=1
1640 fails["metadata.bad"].append(x+"/metadata.xml")
1643 muselist = frozenset(musedict)
1645 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1646 changelog_modified = changelog_path in modified_changelogs
1648 # detect unused local USE-descriptions
1649 used_useflags = set()
1651 for y in ebuildlist:
1652 relative_path = os.path.join(x, y + ".ebuild")
1653 full_path = os.path.join(repodir, relative_path)
1654 ebuild_path = y + ".ebuild"
1656 ebuild_path = os.path.join(pkgdir, ebuild_path)
1658 ebuild_path = os.path.join(catdir, ebuild_path)
1659 ebuild_path = os.path.join(".", ebuild_path)
1660 if check_changelog and not changelog_modified \
1661 and ebuild_path in new_ebuilds:
1662 stats['changelog.ebuildadded'] += 1
1663 fails['changelog.ebuildadded'].append(relative_path)
1665 if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
1666 #ebuild not added to vcs
1667 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1668 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1669 myesplit=portage.pkgsplit(y)
1670 if myesplit is None or myesplit[0] != x.split("/")[-1] \
1671 or pv_toolong_re.search(myesplit[1]) \
1672 or pv_toolong_re.search(myesplit[2]):
1673 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1674 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1676 elif myesplit[0]!=pkgdir:
1677 print(pkgdir,myesplit[0])
1678 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1679 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1686 for k, msgs in pkg.invalid.items():
1688 stats[k] = stats[k] + 1
1689 fails[k].append("%s %s" % (relative_path, msg))
1692 myaux = pkg.metadata
1693 eapi = myaux["EAPI"]
1694 inherited = pkg.inherited
1695 live_ebuild = live_eclasses.intersection(inherited)
1697 for k, v in myaux.items():
1698 if not isinstance(v, basestring):
1700 m = non_ascii_re.search(v)
1702 stats["variable.invalidchar"] += 1
1703 fails["variable.invalidchar"].append(
1704 ("%s: %s variable contains non-ASCII " + \
1705 "character at position %s") % \
1706 (relative_path, k, m.start() + 1))
1708 if not src_uri_error:
1709 # Check that URIs don't reference a server from thirdpartymirrors.
1710 for uri in portage.dep.use_reduce( \
1711 myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
1712 contains_mirror = False
1713 for mirror in thirdpartymirrors:
1714 if uri.startswith(mirror):
1715 contains_mirror = True
1717 if not contains_mirror:
1720 stats["SRC_URI.mirror"] += 1
1721 fails["SRC_URI.mirror"].append(
1722 "%s: '%s' found in thirdpartymirrors" % \
1723 (relative_path, mirror))
1725 if myaux.get("PROVIDE"):
1726 stats["virtual.oldstyle"]+=1
1727 fails["virtual.oldstyle"].append(relative_path)
1729 for pos, missing_var in enumerate(missingvars):
1730 if not myaux.get(missing_var):
1731 if catdir == "virtual" and \
1732 missing_var in ("HOMEPAGE", "LICENSE"):
1734 if live_ebuild and missing_var == "KEYWORDS":
1736 myqakey=missingvars[pos]+".missing"
1737 stats[myqakey]=stats[myqakey]+1
1738 fails[myqakey].append(x+"/"+y+".ebuild")
1740 if catdir == "virtual":
1741 for var in ("HOMEPAGE", "LICENSE"):
1743 myqakey = var + ".virtual"
1744 stats[myqakey] = stats[myqakey] + 1
1745 fails[myqakey].append(relative_path)
1747 # 14 is the length of DESCRIPTION=""
1748 if len(myaux['DESCRIPTION']) > max_desc_len:
1749 stats['DESCRIPTION.toolong'] += 1
1750 fails['DESCRIPTION.toolong'].append(
1751 "%s: DESCRIPTION is %d characters (max %d)" % \
1752 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1754 keywords = myaux["KEYWORDS"].split()
1755 stable_keywords = []
1756 for keyword in keywords:
1757 if not keyword.startswith("~") and \
1758 not keyword.startswith("-"):
1759 stable_keywords.append(keyword)
1761 if ebuild_path in new_ebuilds:
1762 stable_keywords.sort()
1763 stats["KEYWORDS.stable"] += 1
1764 fails["KEYWORDS.stable"].append(
1765 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1766 " ".join(stable_keywords))
1768 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1769 if not kw.startswith("-"))
1771 previous_keywords = slot_keywords.get(myaux["SLOT"])
1772 if previous_keywords is None:
1773 slot_keywords[myaux["SLOT"]] = set()
1774 elif ebuild_archs and not live_ebuild:
1775 dropped_keywords = previous_keywords.difference(ebuild_archs)
1776 if dropped_keywords:
1777 stats["KEYWORDS.dropped"] += 1
1778 fails["KEYWORDS.dropped"].append(
1779 relative_path + ": %s" % \
1780 " ".join(sorted(dropped_keywords)))
1782 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1784 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1785 if "-*" in keywords:
1793 stats["KEYWORDS.stupid"] += 1
1794 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1797 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1798 not be allowed to be marked stable
1800 if live_ebuild and repo_config.name == "gentoo":
1801 bad_stable_keywords = []
1802 for keyword in keywords:
1803 if not keyword.startswith("~") and \
1804 not keyword.startswith("-"):
1805 bad_stable_keywords.append(keyword)
1807 if bad_stable_keywords:
1808 stats["LIVEVCS.stable"] += 1
1809 fails["LIVEVCS.stable"].append(
1810 x + "/" + y + ".ebuild with stable keywords:%s " % \
1811 bad_stable_keywords)
1812 del bad_stable_keywords
1814 if keywords and not has_global_mask(pkg):
1815 stats["LIVEVCS.unmasked"] += 1
1816 fails["LIVEVCS.unmasked"].append(relative_path)
1818 if options.ignore_arches:
1819 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1820 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1823 for keyword in myaux["KEYWORDS"].split():
1824 if (keyword[0]=="-"):
1826 elif (keyword[0]=="~"):
1827 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1829 arches.append([keyword, keyword, [keyword]])
1831 # Use an empty profile for checking dependencies of
1832 # packages that have empty KEYWORDS.
1833 arches.append(['**', '**', ['**']])
1835 unknown_pkgs = set()
1836 baddepsyntax = False
1837 badlicsyntax = False
1838 badprovsyntax = False
1839 catpkg = catdir+"/"+y
1841 inherited_java_eclass = "java-pkg-2" in inherited or \
1842 "java-pkg-opt-2" in inherited
1843 inherited_wxwidgets_eclass = "wxwidgets" in inherited
1844 operator_tokens = set(["||", "(", ")"])
1845 type_list, badsyntax = [], []
1846 for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
1847 "LICENSE", "PROPERTIES", "PROVIDE"):
1848 mydepstr = myaux[mytype]
1851 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1852 token_class=portage.dep.Atom
1855 atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
1856 is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
1857 except portage.exception.InvalidDependString as e:
1859 badsyntax.append(str(e))
1861 if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1862 if mytype in ("RDEPEND", "PDEPEND") and \
1863 "test?" in mydepstr.split():
1864 stats[mytype + '.suspect'] += 1
1865 fails[mytype + '.suspect'].append(relative_path + \
1866 ": 'test?' USE conditional in %s" % mytype)
1872 # Skip dependency.unknown for blockers, so that we
1873 # don't encourage people to remove necessary blockers,
1874 # as discussed in bug #382407.
1875 if atom.blocker is None and \
1876 not portdb.xmatch("match-all", atom) and \
1877 not atom.cp.startswith("virtual/"):
1878 unknown_pkgs.add((mytype, atom.unevaluated_atom))
1880 is_blocker = atom.blocker
1882 if mytype == "DEPEND" and \
1883 not is_blocker and \
1884 not inherited_java_eclass and \
1885 atom.cp == "virtual/jdk":
1886 stats['java.eclassesnotused'] += 1
1887 fails['java.eclassesnotused'].append(relative_path)
1888 elif mytype == "DEPEND" and \
1889 not is_blocker and \
1890 not inherited_wxwidgets_eclass and \
1891 atom.cp == "x11-libs/wxGTK":
1892 stats['wxwidgets.eclassnotused'] += 1
1893 fails['wxwidgets.eclassnotused'].append(
1894 relative_path + ": DEPENDs on x11-libs/wxGTK"
1895 " without inheriting wxwidgets.eclass")
1896 elif mytype in ("PDEPEND", "RDEPEND"):
1897 if not is_blocker and \
1898 atom.cp in suspect_rdepend:
1899 stats[mytype + '.suspect'] += 1
1900 fails[mytype + '.suspect'].append(
1901 relative_path + ": '%s'" % atom)
1903 if atom.operator == "~" and \
1904 portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
1905 stats[mytype + '.badtilde'] += 1
1906 fails[mytype + '.badtilde'].append(
1907 (relative_path + ": %s uses the ~ operator"
1908 " with a non-zero revision:" + \
1909 " '%s'") % (mytype, atom))
1911 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1913 for m,b in zip(type_list, badsyntax):
1914 stats[m+".syntax"] += 1
1915 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1917 badlicsyntax = len([z for z in type_list if z == "LICENSE"])
1918 badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
1919 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1920 badlicsyntax = badlicsyntax > 0
1921 badprovsyntax = badprovsyntax > 0
1923 # uselist checks - global
1926 for myflag in myaux["IUSE"].split():
1927 flag_name = myflag.lstrip("+-")
1928 used_useflags.add(flag_name)
1929 if myflag != flag_name:
1930 default_use.append(myflag)
1931 if flag_name not in uselist:
1932 myuse.append(flag_name)
1934 # uselist checks - metadata
1935 for mypos in range(len(myuse)-1,-1,-1):
1936 if myuse[mypos] and (myuse[mypos] in muselist):
1939 if default_use and not eapi_has_iuse_defaults(eapi):
1940 for myflag in default_use:
1941 stats['EAPI.incompatible'] += 1
1942 fails['EAPI.incompatible'].append(
1943 (relative_path + ": IUSE defaults" + \
1944 " not supported with EAPI='%s':" + \
1945 " '%s'") % (eapi, myflag))
1947 for mypos in range(len(myuse)):
1948 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1949 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1952 if not badlicsyntax:
1953 # Parse the LICENSE variable, remove USE conditions and
1955 licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
1956 # Check each entry to ensure that it exists in PORTDIR's
1957 # license directory.
1958 for lic in licenses:
1959 # Need to check for "||" manually as no portage
1960 # function will remove it without removing values.
1961 if lic not in liclist and lic != "||":
1962 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1963 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
1966 myuse = myaux["KEYWORDS"].split()
1968 if mykey not in ("-*", "*", "~*"):
1970 if myskey[:1] == "-":
1972 if myskey[:1] == "~":
1974 if myskey not in kwlist:
1975 stats["KEYWORDS.invalid"] += 1
1976 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1977 elif myskey not in profiles:
1978 stats["KEYWORDS.invalid"] += 1
1979 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1984 myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
1985 except portage.exception.InvalidDependString as e:
1986 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
1987 fails["RESTRICT.syntax"].append(
1988 "%s: RESTRICT: %s" % (relative_path, e))
1991 myrestrict = set(myrestrict)
1992 mybadrestrict = myrestrict.difference(valid_restrict)
1994 stats["RESTRICT.invalid"] += len(mybadrestrict)
1995 for mybad in mybadrestrict:
1996 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
1998 required_use = myaux["REQUIRED_USE"]
2000 if not eapi_has_required_use(eapi):
2001 stats['EAPI.incompatible'] += 1
2002 fails['EAPI.incompatible'].append(
2003 relative_path + ": REQUIRED_USE" + \
2004 " not supported with EAPI='%s'" % (eapi,))
2006 portage.dep.check_required_use(required_use, (),
2007 pkg.iuse.is_valid_flag)
2008 except portage.exception.InvalidDependString as e:
2009 stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
2010 fails["REQUIRED_USE.syntax"].append(
2011 "%s: REQUIRED_USE: %s" % (relative_path, e))
2015 relative_path = os.path.join(x, y + ".ebuild")
2016 full_path = os.path.join(repodir, relative_path)
2017 if not vcs_preserves_mtime:
2018 if ebuild_path not in new_ebuilds and \
2019 ebuild_path not in modified_ebuilds:
2022 # All ebuilds should have utf_8 encoding.
2023 f = io.open(_unicode_encode(full_path,
2024 encoding=_encodings['fs'], errors='strict'),
2025 mode='r', encoding=_encodings['repo.content'])
2027 for check_name, e in run_checks(f, pkg):
2028 stats[check_name] += 1
2029 fails[check_name].append(relative_path + ': %s' % e)
2032 except UnicodeDecodeError:
2033 # A file.UTF8 failure will have already been recorded above.
2037 # The dep_check() calls are the most expensive QA test. If --force
2038 # is enabled, there's no point in wasting time on these since the
2039 # user is intent on forcing the commit anyway.
2042 for keyword,arch,groups in arches:
2044 if arch not in profiles:
2045 # A missing profile will create an error further down
2046 # during the KEYWORDS verification.
2049 for prof in profiles[arch]:
2051 if prof.status not in ("stable", "dev") or \
2052 prof.status == "dev" and not options.include_dev:
2055 dep_settings = arch_caches.get(prof.sub_path)
2056 if dep_settings is None:
2057 dep_settings = portage.config(
2058 config_profile_path=prof.abs_path,
2059 config_incrementals=repoman_incrementals,
2060 config_root=config_root,
2062 _unmatched_removal=options.unmatched_removal,
2064 dep_settings.categories = repoman_settings.categories
2065 if options.without_mask:
2066 dep_settings._mask_manager = \
2067 copy.deepcopy(dep_settings._mask_manager)
2068 dep_settings._mask_manager._pmaskdict.clear()
2069 arch_caches[prof.sub_path] = dep_settings
2071 xmatch_cache_key = (prof.sub_path, tuple(groups))
2072 xcache = arch_xmatch_caches.get(xmatch_cache_key)
2076 xcache = portdb.xcache
2077 xcache.update(shared_xmatch_caches)
2078 arch_xmatch_caches[xmatch_cache_key] = xcache
2080 trees[root]["porttree"].settings = dep_settings
2081 portdb.settings = dep_settings
2082 portdb.xcache = xcache
2083 # for package.use.mask support inside dep_check
2084 dep_settings.setcpv(pkg)
2085 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
2086 # just in case, prevent config.reset() from nuking these.
2087 dep_settings.backup_changes("ACCEPT_KEYWORDS")
2089 if not baddepsyntax:
2090 ismasked = not ebuild_archs or \
2091 pkg.cpv not in portdb.xmatch("match-visible", pkg.cp)
2093 if not have_pmasked:
2094 have_pmasked = bool(dep_settings._getMaskAtom(
2095 pkg.cpv, pkg.metadata))
2096 if options.ignore_masked:
2098 #we are testing deps for a masked package; give it some lee-way
2100 matchmode = "minimum-all"
2103 matchmode = "minimum-visible"
2105 if not have_dev_keywords:
2106 have_dev_keywords = \
2107 bool(dev_keywords.intersection(keywords))
2109 if prof.status == "dev":
2110 suffix=suffix+"indev"
2112 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
2114 mykey=mytype+".bad"+suffix
2115 myvalue = myaux[mytype]
2119 success, atoms = portage.dep_check(myvalue, portdb,
2120 dep_settings, use="all", mode=matchmode,
2126 # Don't bother with dependency.unknown for
2127 # cases in which *DEPEND.bad is triggered.
2129 # dep_check returns all blockers and they
2130 # aren't counted for *DEPEND.bad, so we
2132 if not atom.blocker:
2133 unknown_pkgs.discard(
2134 (mytype, atom.unevaluated_atom))
2136 if not prof.sub_path:
2137 # old-style virtuals currently aren't
2138 # resolvable with empty profile, since
2139 # 'virtuals' mappings are unavailable
2140 # (it would be expensive to search
2141 # for PROVIDE in all ebuilds)
2142 atoms = [atom for atom in atoms if not \
2143 (atom.cp.startswith('virtual/') and \
2144 not portdb.cp_list(atom.cp))]
2146 #we have some unsolvable deps
2147 #remove ! deps, which always show up as unsatisfiable
2148 atoms = [str(atom.unevaluated_atom) \
2149 for atom in atoms if not atom.blocker]
2151 #if we emptied out our list, continue:
2154 stats[mykey]=stats[mykey]+1
2155 fails[mykey].append("%s: %s(%s) %s" % \
2156 (relative_path, keyword,
2159 stats[mykey]=stats[mykey]+1
2160 fails[mykey].append("%s: %s(%s) %s" % \
2161 (relative_path, keyword,
2164 if not baddepsyntax and unknown_pkgs:
2166 for mytype, atom in unknown_pkgs:
2167 type_map.setdefault(mytype, set()).add(atom)
2168 for mytype, atoms in type_map.items():
2169 stats["dependency.unknown"] += 1
2170 fails["dependency.unknown"].append("%s: %s: %s" %
2171 (relative_path, mytype, ", ".join(sorted(atoms))))
2173 # check if there are unused local USE-descriptions in metadata.xml
2174 # (unless there are any invalids, to avoid noise)
2176 for myflag in muselist.difference(used_useflags):
2177 stats["metadata.warning"] += 1
2178 fails["metadata.warning"].append(
2179 "%s/metadata.xml: unused local USE-description: '%s'" % \
2182 if options.if_modified == "y" and len(effective_scanlist) < 1:
2183 logging.warn("--if-modified is enabled, but no modified packages were found!")
2185 if options.mode == "manifest":
2188 #dofail will be set to 1 if we have failed in at least one non-warning category
2190 #dowarn will be set to 1 if we tripped any warnings
2192 #dofull will be set if we should print a "repoman full" informational message
2193 dofull = options.mode != 'full'
2199 if x not in qawarnings:
2203 (dowarn and not (options.quiet or options.mode == "scan")):
2206 # Save QA output so that it can be conveniently displayed
2207 # in $EDITOR while the user creates a commit message.
2208 # Otherwise, the user would not be able to see this output
2209 # once the editor has taken over the screen.
2210 qa_output = io.StringIO()
2211 style_file = ConsoleStyleFile(sys.stdout)
2212 if options.mode == 'commit' and \
2213 (not commitmessage or not commitmessage.strip()):
2214 style_file.write_listener = qa_output
2215 console_writer = StyleWriter(file=style_file, maxcol=9999)
2216 console_writer.style_listener = style_file.new_styles
2218 f = formatter.AbstractFormatter(console_writer)
2220 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
2223 del console_writer, f, style_file
2224 qa_output = qa_output.getvalue()
2225 qa_output = qa_output.splitlines(True)
2227 def grouplist(mylist,seperator="/"):
2228 """(list,seperator="/") -- Takes a list of elements; groups them into
2229 same initial element categories. Returns a dict of {base:[sublist]}
2230 From: ["blah/foo","spork/spatula","blah/weee/splat"]
2231 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
2234 xs=x.split(seperator)
2237 if xs[0] not in mygroups:
2238 mygroups[xs[0]]=[seperator.join(xs[1:])]
2240 mygroups[xs[0]]+=[seperator.join(xs[1:])]
2243 suggest_ignore_masked = False
2244 suggest_include_dev = False
2246 if have_pmasked and not (options.without_mask or options.ignore_masked):
2247 suggest_ignore_masked = True
2248 if have_dev_keywords and not options.include_dev:
2249 suggest_include_dev = True
2251 if suggest_ignore_masked or suggest_include_dev:
2253 if suggest_ignore_masked:
2254 print(bold("Note: use --without-mask to check " + \
2255 "KEYWORDS on dependencies of masked packages"))
2257 if suggest_include_dev:
2258 print(bold("Note: use --include-dev (-d) to check " + \
2259 "dependencies for 'dev' profiles"))
2262 if options.mode != 'commit':
2264 print(bold("Note: type \"repoman full\" for a complete listing."))
2265 if dowarn and not dofail:
2266 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.\"")
2268 print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
2270 print(bad("Please fix these important QA issues first."))
2271 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2274 if dofail and can_force and options.force and not options.pretend:
2275 print(green("RepoMan sez:") + \
2276 " \"You want to commit even with these QA issues?\n" + \
2277 " I'll take it this time, but I'm not happy.\"\n")
2279 if options.force and not can_force:
2280 print(bad("The --force option has been disabled due to extraordinary issues."))
2281 print(bad("Please fix these important QA issues first."))
2282 print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
2286 print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
2291 myvcstree=portage.cvstree.getentries("./",recursive=1)
2292 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
2293 except SystemExit as e:
2294 raise # TODO propagate this
2296 err("Error retrieving CVS tree; exiting.")
2299 with os.popen("svn status --no-ignore") as f:
2300 svnstatus = f.readlines()
2301 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
2302 except SystemExit as e:
2303 raise # TODO propagate this
2305 err("Error retrieving SVN info; exiting.")
2307 # get list of files not under version control or missing
2308 myf = os.popen("git ls-files --others")
2309 myunadded = [ "./" + elem[:-1] for elem in myf ]
2313 with os.popen("bzr status -S .") as f:
2314 bzrstatus = f.readlines()
2315 myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
2316 except SystemExit as e:
2317 raise # TODO propagate this
2319 err("Error retrieving bzr info; exiting.")
2321 with os.popen("hg status --no-status --unknown .") as f:
2322 myunadded = f.readlines()
2323 myunadded = ["./" + elem.rstrip() for elem in myunadded]
2325 # Mercurial doesn't handle manually deleted files as removed from
2326 # the repository, so the user need to remove them before commit,
2327 # using "hg remove [FILES]"
2328 with os.popen("hg status --no-status --deleted .") as f:
2329 mydeleted = f.readlines()
2330 mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
2335 for x in range(len(myunadded)-1,-1,-1):
2336 xs=myunadded[x].split("/")
2338 print("!!! files dir is not added! Please correct this.")
2340 elif xs[-1]=="Manifest":
2341 # It's a manifest... auto add
2342 myautoadd+=[myunadded[x]]
2346 print(red("!!! The following files are in your local tree but are not added to the master"))
2347 print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
2354 if vcs == "hg" and mydeleted:
2355 print(red("!!! The following files are removed manually from your local tree but are not"))
2356 print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
2364 mycvstree = cvstree.getentries("./", recursive=1)
2365 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
2366 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
2367 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
2368 bin_blob_pattern = re.compile("^-kb$")
2369 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
2370 recursive=1, basedir="./"))
2374 with os.popen("svn status") as f:
2375 svnstatus = f.readlines()
2376 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
2377 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
2378 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
2380 # Subversion expands keywords specified in svn:keywords properties.
2381 with os.popen("svn propget -R svn:keywords") as f:
2382 props = f.readlines()
2383 expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
2384 for prop in props if " - " in prop)
2387 with os.popen("git diff-index --name-only "
2388 "--relative --diff-filter=M HEAD") as f:
2389 mychanged = f.readlines()
2390 mychanged = ["./" + elem[:-1] for elem in mychanged]
2392 with os.popen("git diff-index --name-only "
2393 "--relative --diff-filter=A HEAD") as f:
2394 mynew = f.readlines()
2395 mynew = ["./" + elem[:-1] for elem in mynew]
2397 with os.popen("git diff-index --name-only "
2398 "--relative --diff-filter=D HEAD") as f:
2399 myremoved = f.readlines()
2400 myremoved = ["./" + elem[:-1] for elem in myremoved]
2403 with os.popen("bzr status -S .") as f:
2404 bzrstatus = f.readlines()
2405 mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
2406 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" ) ]
2407 myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
2408 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" ) ]
2409 # Bazaar expands nothing.
2412 with os.popen("hg status --no-status --modified .") as f:
2413 mychanged = f.readlines()
2414 mychanged = ["./" + elem.rstrip() for elem in mychanged]
2416 with os.popen("hg status --no-status --added .") as f:
2417 mynew = f.readlines()
2418 mynew = ["./" + elem.rstrip() for elem in mynew]
2420 with os.popen("hg status --no-status --removed .") as f:
2421 myremoved = f.readlines()
2422 myremoved = ["./" + elem.rstrip() for elem in myremoved]
2425 if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
2426 print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
2428 print("(Didn't find any changed files...)")
2432 # Manifests need to be regenerated after all other commits, so don't commit
2433 # them now even if they have changed.
2436 for f in mychanged + mynew:
2437 if "Manifest" == os.path.basename(f):
2441 myupdates.difference_update(myremoved)
2442 myupdates = list(myupdates)
2443 mymanifests = list(mymanifests)
2447 commitmessage = options.commitmsg
2448 if options.commitmsgfile:
2450 f = io.open(_unicode_encode(options.commitmsgfile,
2451 encoding=_encodings['fs'], errors='strict'),
2452 mode='r', encoding=_encodings['content'], errors='replace')
2453 commitmessage = f.read()
2456 except (IOError, OSError) as e:
2457 if e.errno == errno.ENOENT:
2458 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
2461 # We've read the content so the file is no longer needed.
2462 commitmessagefile = None
2463 if not commitmessage or not commitmessage.strip():
2465 editor = os.environ.get("EDITOR")
2466 if editor and utilities.editor_is_executable(editor):
2467 commitmessage = utilities.get_commit_message_with_editor(
2468 editor, message=qa_output)
2470 commitmessage = utilities.get_commit_message_with_stdin()
2471 except KeyboardInterrupt:
2473 if not commitmessage or not commitmessage.strip():
2474 print("* no commit message? aborting commit.")
2476 commitmessage = commitmessage.rstrip()
2477 changelog_msg = commitmessage
2478 portage_version = getattr(portage, "VERSION", None)
2479 if portage_version is None:
2480 sys.stderr.write("Failed to insert portage version in message!\n")
2482 portage_version = "Unknown"
2483 unameout = platform.system() + " "
2484 if platform.system() in ["Darwin", "SunOS"]:
2485 unameout += platform.processor()
2487 unameout += platform.machine()
2488 commitmessage += "\n\n(Portage version: %s/%s/%s" % \
2489 (portage_version, vcs, unameout)
2491 commitmessage += ", RepoMan options: --force"
2492 commitmessage += ")"
2494 if options.echangelog in ('y', 'force'):
2495 logging.info("checking for unmodified ChangeLog files")
2496 committer_name = utilities.get_committer_name(env=repoman_settings)
2497 for x in sorted(vcs_files_to_cps(
2498 chain(myupdates, mymanifests, myremoved))):
2499 catdir, pkgdir = x.split("/")
2500 checkdir = repodir + "/" + x
2501 checkdir_relative = ""
2503 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
2505 checkdir_relative = os.path.join(catdir, checkdir_relative)
2506 checkdir_relative = os.path.join(".", checkdir_relative)
2508 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
2509 changelog_modified = changelog_path in modified_changelogs
2510 if changelog_modified and options.echangelog != 'force':
2513 # get changes for this package
2514 cdrlen = len(checkdir_relative)
2515 clnew = [elem[cdrlen:] for elem in mynew if elem.startswith(checkdir_relative)]
2516 clremoved = [elem[cdrlen:] for elem in myremoved if elem.startswith(checkdir_relative)]
2517 clchanged = [elem[cdrlen:] for elem in mychanged if elem.startswith(checkdir_relative)]
2519 # Skip ChangeLog generation if only the Manifest was modified,
2520 # as discussed in bug #398009.
2521 nontrivial_cl_files = set()
2522 nontrivial_cl_files.update(clnew, clremoved, clchanged)
2523 nontrivial_cl_files.difference_update(['Manifest'])
2524 if not nontrivial_cl_files and options.echangelog != 'force':
2527 new_changelog = utilities.UpdateChangeLog(checkdir_relative,
2528 committer_name, changelog_msg,
2529 os.path.join(repodir, 'skel.ChangeLog'),
2531 new=clnew, removed=clremoved, changed=clchanged,
2532 pretend=options.pretend)
2533 if new_changelog is None:
2534 writemsg_level("!!! Updating the ChangeLog failed\n", \
2535 level=logging.ERROR, noiselevel=-1)
2538 # if the ChangeLog was just created, add it to vcs
2540 myautoadd.append(changelog_path)
2541 # myautoadd is appended to myupdates below
2543 myupdates.append(changelog_path)
2546 print(">>> Auto-Adding missing Manifest/ChangeLog file(s)...")
2547 add_cmd = [vcs, "add"]
2548 add_cmd += myautoadd
2550 portage.writemsg_stdout("(%s)\n" % " ".join(add_cmd),
2553 if not (sys.hexversion >= 0x3000000 and sys.hexversion < 0x3020000):
2554 # Python 3.1 produces the following TypeError if raw bytes are
2555 # passed to subprocess.call():
2556 # File "/usr/lib/python3.1/subprocess.py", line 646, in __init__
2557 # errread, errwrite)
2558 # File "/usr/lib/python3.1/subprocess.py", line 1157, in _execute_child
2559 # raise child_exception
2560 # TypeError: expected an object with the buffer interface
2561 add_cmd = [_unicode_encode(arg) for arg in add_cmd]
2562 retcode = subprocess.call(add_cmd)
2563 if retcode != os.EX_OK:
2565 "Exiting on %s error code: %s\n" % (vcs, retcode))
2568 myupdates += myautoadd
2570 print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
2572 if vcs not in ('cvs', 'svn'):
2573 # With git, bzr and hg, there's never any keyword expansion, so
2574 # there's no need to regenerate manifests and all files will be
2575 # committed in one big commit at the end.
2577 elif not repo_config.thin_manifest:
2579 headerstring = "'\$(Header|Id).*\$'"
2581 svn_keywords = dict((k.lower(), k) for k in [
2584 "LastChangedRevision",
2595 for myfile in myupdates:
2597 # for CVS, no_expansion contains files that are excluded from expansion
2599 if myfile in no_expansion:
2602 # for SVN, expansion contains files that are included in expansion
2604 if myfile not in expansion:
2607 # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
2608 enabled_keywords = []
2609 for k in expansion[myfile]:
2610 keyword = svn_keywords.get(k.lower())
2611 if keyword is not None:
2612 enabled_keywords.append(keyword)
2614 headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
2616 myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile)
2618 myheaders.append(myfile)
2620 print("%s have headers that will change." % green(str(len(myheaders))))
2621 print("* Files with headers will cause the manifests to be changed and committed separately.")
2623 logging.info("myupdates: %s", myupdates)
2624 logging.info("myheaders: %s", myheaders)
2626 if options.ask and userquery('Commit changes?', True) != 'Yes':
2627 print("* aborting commit.")
2628 sys.exit(128 + signal.SIGINT)
2630 # Handle the case where committed files have keywords which
2631 # will change and need a priming commit before the Manifest
2633 if (myupdates or myremoved) and myheaders:
2634 myfiles = myupdates + myremoved
2635 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2636 mymsg = os.fdopen(fd, "wb")
2637 mymsg.write(_unicode_encode(commitmessage))
2641 print(green("Using commit message:"))
2642 print(green("------------------------------------------------------------------------------"))
2643 print(commitmessage)
2644 print(green("------------------------------------------------------------------------------"))
2647 # Having a leading ./ prefix on file paths can trigger a bug in
2648 # the cvs server when committing files to multiple directories,
2649 # so strip the prefix.
2650 myfiles = [f.lstrip("./") for f in myfiles]
2653 commit_cmd.extend(vcs_global_opts)
2654 commit_cmd.append("commit")
2655 commit_cmd.extend(vcs_local_opts)
2656 commit_cmd.extend(["-F", commitmessagefile])
2657 commit_cmd.extend(myfiles)
2661 print("(%s)" % (" ".join(commit_cmd),))
2663 retval = spawn(commit_cmd, env=os.environ)
2664 if retval != os.EX_OK:
2665 writemsg_level(("!!! Exiting on %s (shell) " + \
2666 "error code: %s\n") % (vcs, retval),
2667 level=logging.ERROR, noiselevel=-1)
2671 os.unlink(commitmessagefile)
2675 # Setup the GPG commands
2676 def gpgsign(filename):
2677 gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
2679 raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
2680 " Is make.globals missing?")
2681 if "${PORTAGE_GPG_KEY}" in gpgcmd and \
2682 "PORTAGE_GPG_KEY" not in repoman_settings:
2683 raise MissingParameter("PORTAGE_GPG_KEY is unset!")
2684 if "${PORTAGE_GPG_DIR}" in gpgcmd:
2685 if "PORTAGE_GPG_DIR" not in repoman_settings:
2686 repoman_settings["PORTAGE_GPG_DIR"] = \
2687 os.path.expanduser("~/.gnupg")
2688 logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
2689 % repoman_settings["PORTAGE_GPG_DIR"])
2691 repoman_settings["PORTAGE_GPG_DIR"] = \
2692 os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
2693 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
2694 raise portage.exception.InvalidLocation(
2695 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
2696 repoman_settings["PORTAGE_GPG_DIR"])
2697 gpgvars = {"FILE": filename}
2698 for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
2699 v = repoman_settings.get(k)
2702 gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
2704 print("("+gpgcmd+")")
2706 rValue = os.system(gpgcmd)
2707 if rValue == os.EX_OK:
2708 os.rename(filename+".asc", filename)
2710 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
2712 # When files are removed and re-added, the cvs server will put /Attic/
2713 # inside the $Header path. This code detects the problem and corrects it
2714 # so that the Manifest will generate correctly. See bug #169500.
2715 # Use binary mode in order to avoid potential character encoding issues.
2716 cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
2717 attic_str = b'/Attic/'
2718 attic_replace = b'/'
2720 f = open(_unicode_encode(x,
2721 encoding=_encodings['fs'], errors='strict'),
2723 mylines = f.readlines()
2726 for i, line in enumerate(mylines):
2727 if cvs_header_re.match(line) is not None and \
2729 mylines[i] = line.replace(attic_str, attic_replace)
2732 portage.util.write_atomic(x, b''.join(mylines),
2736 print(green("RepoMan sez:"), "\"You're rather crazy... "
2737 "doing the entire repository.\"\n")
2739 if vcs in ('cvs', 'svn') and (myupdates or myremoved):
2741 for x in sorted(vcs_files_to_cps(
2742 chain(myupdates, myremoved, mymanifests))):
2743 repoman_settings["O"] = os.path.join(repodir, x)
2744 digestgen(mysettings=repoman_settings, myportdb=portdb)
2750 for x in sorted(vcs_files_to_cps(
2751 chain(myupdates, myremoved, mymanifests))):
2752 repoman_settings["O"] = os.path.join(repodir, x)
2753 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2754 except portage.exception.PortageException as e:
2755 portage.writemsg("!!! %s\n" % str(e))
2756 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2760 # It's not safe to use the git commit -a option since there might
2761 # be some modified files elsewhere in the working tree that the
2762 # user doesn't want to commit. Therefore, call git update-index
2763 # in order to ensure that the index is updated with the latest
2764 # versions of all new and modified files in the relevant portion
2765 # of the working tree.
2766 myfiles = mymanifests + myupdates
2768 update_index_cmd = ["git", "update-index"]
2769 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2771 print("(%s)" % (" ".join(update_index_cmd),))
2773 retval = spawn(update_index_cmd, env=os.environ)
2774 if retval != os.EX_OK:
2775 writemsg_level(("!!! Exiting on %s (shell) " + \
2776 "error code: %s\n") % (vcs, retval),
2777 level=logging.ERROR, noiselevel=-1)
2782 myfiles = mymanifests[:]
2783 # If there are no header (SVN/CVS keywords) changes in
2784 # the files, this Manifest commit must include the
2785 # other (yet uncommitted) files.
2787 myfiles += myupdates
2788 myfiles += myremoved
2791 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2792 mymsg = os.fdopen(fd, "wb")
2793 # strip the closing parenthesis
2794 mymsg.write(_unicode_encode(commitmessage[:-1]))
2796 mymsg.write(_unicode_encode(
2797 ", signed Manifest commit with key %s)" % \
2798 repoman_settings["PORTAGE_GPG_KEY"]))
2800 mymsg.write(b", unsigned Manifest commit)")
2804 if options.pretend and vcs is None:
2805 # substitute a bogus value for pretend output
2806 commit_cmd.append("cvs")
2808 commit_cmd.append(vcs)
2809 commit_cmd.extend(vcs_global_opts)
2810 commit_cmd.append("commit")
2811 commit_cmd.extend(vcs_local_opts)
2813 commit_cmd.extend(["--logfile", commitmessagefile])
2814 commit_cmd.extend(myfiles)
2816 commit_cmd.extend(["-F", commitmessagefile])
2817 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2821 print("(%s)" % (" ".join(commit_cmd),))
2823 retval = spawn(commit_cmd, env=os.environ)
2824 if retval != os.EX_OK:
2826 if repo_config.sign_commit and vcs == 'git' and \
2827 not git_supports_gpg_sign():
2828 # Inform user that newer git is needed (bug #403323).
2830 "Git >=1.7.9 is required for signed commits!")
2832 writemsg_level(("!!! Exiting on %s (shell) " + \
2833 "error code: %s\n") % (vcs, retval),
2834 level=logging.ERROR, noiselevel=-1)
2838 os.unlink(commitmessagefile)
2844 print("Commit complete.")
2846 print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
2847 print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")