aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'portage_with_autodep/bin/repoman')
-rwxr-xr-xportage_with_autodep/bin/repoman2672
1 files changed, 2672 insertions, 0 deletions
diff --git a/portage_with_autodep/bin/repoman b/portage_with_autodep/bin/repoman
new file mode 100755
index 0000000..f1fbc24
--- /dev/null
+++ b/portage_with_autodep/bin/repoman
@@ -0,0 +1,2672 @@
+#!/usr/bin/python -O
+# Copyright 1999-2011 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+# Next to do: dep syntax checking in mask files
+# Then, check to make sure deps are satisfiable (to avoid "can't find match for" problems)
+# that last one is tricky because multiple profiles need to be checked.
+
+from __future__ import print_function
+
+import calendar
+import copy
+import errno
+import formatter
+import io
+import logging
+import optparse
+import re
+import signal
+import stat
+import sys
+import tempfile
+import time
+import platform
+
+try:
+ from urllib.request import urlopen as urllib_request_urlopen
+except ImportError:
+ from urllib import urlopen as urllib_request_urlopen
+
+from itertools import chain
+from stat import S_ISDIR
+
+try:
+ import portage
+except ImportError:
+ from os import path as osp
+ sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
+ import portage
+portage._disable_legacy_globals()
+portage.dep._internal_warnings = True
+
+try:
+ import xml.etree.ElementTree
+ from xml.parsers.expat import ExpatError
+except ImportError:
+ msg = ["Please enable python's \"xml\" USE flag in order to use repoman."]
+ from portage.output import EOutput
+ out = EOutput()
+ for line in msg:
+ out.eerror(line)
+ sys.exit(1)
+
+from portage import os
+from portage import subprocess_getstatusoutput
+from portage import _encodings
+from portage import _unicode_encode
+from repoman.checks import run_checks
+from repoman import utilities
+from repoman.herdbase import make_herd_base
+from _emerge.Package import Package
+from _emerge.RootConfig import RootConfig
+from _emerge.userquery import userquery
+import portage.checksum
+import portage.const
+from portage import cvstree, normalize_path
+from portage import util
+from portage.exception import (FileNotFound, MissingParameter,
+ ParseError, PermissionDenied)
+from portage.manifest import Manifest
+from portage.process import find_binary, spawn
+from portage.output import bold, create_color_func, \
+ green, nocolor, red
+from portage.output import ConsoleStyleFile, StyleWriter
+from portage.util import cmp_sort_key, writemsg_level
+from portage.package.ebuild.digestgen import digestgen
+from portage.eapi import eapi_has_slot_deps, \
+ eapi_has_use_deps, eapi_has_strong_blocks, eapi_has_iuse_defaults, \
+ eapi_has_required_use, eapi_has_use_dep_defaults
+
+if sys.hexversion >= 0x3000000:
+ basestring = str
+
+util.initialize_logger()
+
+# 14 is the length of DESCRIPTION=""
+max_desc_len = 100
+allowed_filename_chars="a-zA-Z0-9._-+:"
+disallowed_filename_chars_re = re.compile(r'[^a-zA-Z0-9._\-+:]')
+pv_toolong_re = re.compile(r'[0-9]{19,}')
+bad = create_color_func("BAD")
+
+# A sane umask is needed for files that portage creates.
+os.umask(0o22)
+# Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to
+# behave incrementally.
+repoman_incrementals = tuple(x for x in \
+ portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS')
+repoman_settings = portage.config(local_config=False)
+repoman_settings.lock()
+
+if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
+ repoman_settings.get('TERM') == 'dumb' or \
+ not sys.stdout.isatty():
+ nocolor()
+
+def warn(txt):
+ print("repoman: " + txt)
+
+def err(txt):
+ warn(txt)
+ sys.exit(1)
+
+def exithandler(signum=None, frame=None):
+ logging.fatal("Interrupted; exiting...")
+ if signum is None:
+ sys.exit(1)
+ else:
+ sys.exit(128 + signum)
+
+signal.signal(signal.SIGINT,exithandler)
+
+class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
+ """Repoman needs it's own HelpFormatter for now, because the default ones
+ murder the help text."""
+
+ def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
+ optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
+
+ def format_description(self, description):
+ return description
+
+class RepomanOptionParser(optparse.OptionParser):
+ """Add the on_tail function, ruby has it, optionParser should too
+ """
+
+ def __init__(self, *args, **kwargs):
+ optparse.OptionParser.__init__(self, *args, **kwargs)
+ self.tail = ""
+
+ def on_tail(self, description):
+ self.tail += description
+
+ def format_help(self, formatter=None):
+ result = optparse.OptionParser.format_help(self, formatter)
+ result += self.tail
+ return result
+
+
+def ParseArgs(argv, qahelp):
+ """This function uses a customized optionParser to parse command line arguments for repoman
+ Args:
+ argv - a sequence of command line arguments
+ qahelp - a dict of qa warning to help message
+ Returns:
+ (opts, args), just like a call to parser.parse_args()
+ """
+
+ if argv and sys.hexversion < 0x3000000 and not isinstance(argv[0], unicode):
+ argv = [portage._unicode_decode(x) for x in argv]
+
+ modes = {
+ 'commit' : 'Run a scan then commit changes',
+ 'ci' : 'Run a scan then commit changes',
+ 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
+ 'full' : 'Scan directory tree and print all issues (not a summary)',
+ 'help' : 'Show this screen',
+ 'manifest' : 'Generate a Manifest (fetches files if necessary)',
+ 'manifest-check' : 'Check Manifests for missing or incorrect digests',
+ 'scan' : 'Scan directory tree for QA issues'
+ }
+
+ mode_keys = list(modes)
+ mode_keys.sort()
+
+ parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
+ parser.description = green(" ".join((os.path.basename(argv[0]), "1.2")))
+ parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
+ parser.description += "\nDistributed under the terms of the GNU General Public License v2"
+ parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
+
+ parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False,
+ help='Request a confirmation before commiting')
+
+ parser.add_option('-m', '--commitmsg', dest='commitmsg',
+ help='specify a commit message on the command line')
+
+ parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
+ help='specify a path to a file that contains a commit message')
+
+ parser.add_option('-p', '--pretend', dest='pretend', default=False,
+ action='store_true', help='don\'t commit or fix anything; just show what would be done')
+
+ parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
+ help='do not print unnecessary messages')
+
+ parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
+ help='Commit with QA violations')
+
+ parser.add_option('--vcs', dest='vcs',
+ help='Force using specific VCS instead of autodetection')
+
+ parser.add_option('-v', '--verbose', dest="verbosity", action='count',
+ help='be very verbose in output', default=0)
+
+ parser.add_option('-V', '--version', dest='version', action='store_true',
+ help='show version info')
+
+ parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
+ default=False, help='forces the metadata.xml parse check to be carried out')
+
+ parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
+ default=False, help='ignore arch-specific failures (where arch != host)')
+
+ parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
+ default=False, help='ignore masked packages (not allowed with commit mode)')
+
+ parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
+ default=False, help='include dev profiles in dependency checks')
+
+ parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true',
+ default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms')
+
+ parser.add_option('--without-mask', dest='without_mask', action='store_true',
+ default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
+
+ parser.add_option('--mode', type='choice', dest='mode', choices=list(modes),
+ help='specify which mode repoman will run in (default=full)')
+
+ parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
+
+ for k in mode_keys:
+ parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
+
+ parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
+
+ sorted_qa = list(qahelp)
+ sorted_qa.sort()
+ for k in sorted_qa:
+ parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
+
+ opts, args = parser.parse_args(argv[1:])
+
+ if opts.mode == 'help':
+ parser.print_help(short=False)
+
+ for arg in args:
+ if arg in modes:
+ if not opts.mode:
+ opts.mode = arg
+ break
+ else:
+ parser.error("invalid mode: %s" % arg)
+
+ if not opts.mode:
+ opts.mode = 'full'
+
+ if opts.mode == 'ci':
+ opts.mode = 'commit' # backwards compat shortcut
+
+ if opts.mode == 'commit' and not (opts.force or opts.pretend):
+ if opts.ignore_masked:
+ parser.error('Commit mode and --ignore-masked are not compatible')
+ if opts.without_mask:
+ parser.error('Commit mode and --without-mask are not compatible')
+
+ # Use the verbosity and quiet options to fiddle with the loglevel appropriately
+ for val in range(opts.verbosity):
+ logger = logging.getLogger()
+ logger.setLevel(logger.getEffectiveLevel() - 10)
+
+ for val in range(opts.quiet):
+ logger = logging.getLogger()
+ logger.setLevel(logger.getEffectiveLevel() + 10)
+
+ return (opts, args)
+
+qahelp={
+ "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
+ "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
+ "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
+ "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
+ "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
+ "changelog.missing":"Missing ChangeLog files",
+ "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
+ "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
+ "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
+ "dependency.unknown" : "Ebuild has a dependency that refers to an unknown package (which may be provided by an overlay)",
+ "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
+ "file.size":"Files in the files directory must be under 20 KiB",
+ "file.size.fatal":"Files in the files directory must be under 60 KiB",
+ "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
+ "file.UTF8":"File is not UTF8 compliant",
+ "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf",
+ "inherit.deprecated":"Ebuild inherits a deprecated eclass",
+ "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
+ "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass",
+ "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
+ "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
+ "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
+ "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
+ "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
+ "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
+ "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
+ "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
+ "EAPI.definition":"EAPI is defined after an inherit call (must be defined before)",
+ "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
+ "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
+ "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
+ "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value",
+ "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
+ "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable",
+ "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
+ "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
+ "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
+ "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
+ "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
+ "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
+ "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
+ "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
+ "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
+ "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
+ "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
+ "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
+ "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.",
+ "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
+ "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
+ "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
+ "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
+ "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
+ "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)",
+ "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
+ "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
+ "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
+ "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
+ "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)",
+ "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
+ "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
+ "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
+ "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
+ "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
+ "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
+ "variable.readonly":"Assigning a readonly variable",
+ "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers",
+ "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
+ "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.",
+ "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file",
+ "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE",
+ "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)",
+ "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
+ "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
+ "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)",
+ "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
+ "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
+ "digest.assumed":"Existing digest must be assumed correct (Package level only)",
+ "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest",
+ "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
+ "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
+ "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
+ "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
+ "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
+ "ebuild.badheader":"This ebuild has a malformed header",
+ "eprefixify.defined":"The ebuild uses eprefixify, but does not inherit the prefix eclass",
+ "manifest.bad":"Manifest has missing or incorrect digests",
+ "metadata.missing":"Missing metadata.xml files",
+ "metadata.bad":"Bad metadata.xml files",
+ "metadata.warning":"Warnings in metadata.xml files",
+ "portage.internal":"The ebuild uses an internal Portage function",
+ "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)",
+ "usage.obsolete":"The ebuild makes use of an obsolete construct",
+ "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
+}
+
+qacats = list(qahelp)
+qacats.sort()
+
+qawarnings = set((
+"changelog.missing",
+"changelog.notadded",
+"dependency.unknown",
+"digest.assumed",
+"digest.unused",
+"ebuild.notadded",
+"ebuild.nostable",
+"ebuild.allmasked",
+"ebuild.nesteddie",
+"desktop.invalid",
+"DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
+"DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
+"DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
+"DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde",
+"DESCRIPTION.toolong",
+"EAPI.deprecated",
+"HOMEPAGE.virtual",
+"LICENSE.virtual",
+"KEYWORDS.dropped",
+"KEYWORDS.stupid",
+"KEYWORDS.missing",
+"IUSE.undefined",
+"PDEPEND.suspect",
+"RDEPEND.implicit",
+"RDEPEND.suspect",
+"RESTRICT.invalid",
+"SRC_URI.mirror",
+"ebuild.minorsyn",
+"ebuild.badheader",
+"ebuild.patches",
+"file.size",
+"inherit.autotools",
+"inherit.deprecated",
+"java.eclassesnotused",
+"wxwidgets.eclassnotused",
+"metadata.warning",
+"portage.internal",
+"usage.obsolete",
+"upstream.workaround",
+"virtual.oldstyle",
+"LIVEVCS.stable",
+"LIVEVCS.unmasked",
+))
+
+non_ascii_re = re.compile(r'[^\x00-\x7f]')
+
+missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"]
+allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
+allvars.update(Package.metadata_keys)
+allvars = sorted(allvars)
+commitmessage=None
+for x in missingvars:
+ x += ".missing"
+ if x not in qacats:
+ logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
+ qacats.append(x)
+ qawarnings.add(x)
+
+valid_restrict = frozenset(["binchecks", "bindist",
+ "fetch", "installsources", "mirror",
+ "primaryuri", "strip", "test", "userpriv"])
+
+live_eclasses = frozenset([
+ "bzr",
+ "cvs",
+ "darcs",
+ "git",
+ "git-2",
+ "mercurial",
+ "subversion",
+ "tla",
+])
+
+suspect_rdepend = frozenset([
+ "app-arch/cabextract",
+ "app-arch/rpm2targz",
+ "app-doc/doxygen",
+ "dev-lang/nasm",
+ "dev-lang/swig",
+ "dev-lang/yasm",
+ "dev-perl/extutils-pkgconfig",
+ "dev-util/byacc",
+ "dev-util/cmake",
+ "dev-util/ftjam",
+ "dev-util/gperf",
+ "dev-util/gtk-doc",
+ "dev-util/gtk-doc-am",
+ "dev-util/intltool",
+ "dev-util/jam",
+ "dev-util/pkgconfig",
+ "dev-util/scons",
+ "dev-util/unifdef",
+ "dev-util/yacc",
+ "media-gfx/ebdftopcf",
+ "sys-apps/help2man",
+ "sys-devel/autoconf",
+ "sys-devel/automake",
+ "sys-devel/bin86",
+ "sys-devel/bison",
+ "sys-devel/dev86",
+ "sys-devel/flex",
+ "sys-devel/m4",
+ "sys-devel/pmake",
+ "virtual/linux-sources",
+ "x11-misc/bdftopcf",
+ "x11-misc/imake",
+])
+
+metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd'
+# force refetch if the local copy creation time is older than this
+metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days
+
+# file.executable
+no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
+
+options, arguments = ParseArgs(sys.argv, qahelp)
+
+if options.version:
+ print("Portage", portage.VERSION)
+ sys.exit(0)
+
+# Set this to False when an extraordinary issue (generally
+# something other than a QA issue) makes it impossible to
+# commit (like if Manifest generation fails).
+can_force = True
+
+portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
+if portdir is None:
+ sys.exit(1)
+
+myreporoot = os.path.basename(portdir_overlay)
+myreporoot += mydir[len(portdir_overlay):]
+
+if options.vcs:
+ if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'):
+ vcs = options.vcs
+ else:
+ vcs = None
+else:
+ vcses = utilities.FindVCS()
+ if len(vcses) > 1:
+ print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses)))
+ print(red('*** Please either clean up your workdir or specify --vcs option.'))
+ sys.exit(1)
+ elif vcses:
+ vcs = vcses[0]
+ else:
+ vcs = None
+
+# Note: We don't use ChangeLogs in distributed SCMs.
+# It will be generated on server side from scm log,
+# before package moves to the rsync server.
+# This is needed because we try to avoid merge collisions.
+check_changelog = vcs in ('cvs', 'svn')
+
+# Disable copyright/mtime check if vcs does not preserve mtime (bug #324075).
+vcs_preserves_mtime = vcs not in ('git',)
+
+vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
+vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
+if vcs_global_opts is None:
+ if vcs in ('cvs', 'svn'):
+ vcs_global_opts = "-q"
+ else:
+ vcs_global_opts = ""
+vcs_global_opts = vcs_global_opts.split()
+
+if vcs == "cvs" and \
+ "commit" == options.mode and \
+ "RMD160" not in portage.checksum.hashorigin_map:
+ from portage.util import grablines
+ repo_lines = grablines("./CVS/Repository")
+ if repo_lines and \
+ "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]:
+ msg = "Please install " \
+ "pycrypto or enable python's ssl USE flag in order " \
+ "to enable RMD160 hash support. See bug #198398 for " \
+ "more information."
+ prefix = bad(" * ")
+ from textwrap import wrap
+ for line in wrap(msg, 70):
+ print(prefix + line)
+ sys.exit(1)
+ del repo_lines
+
+if options.mode == 'commit' and not options.pretend and not vcs:
+ logging.info("Not in a version controlled repository; enabling pretend mode.")
+ options.pretend = True
+
+# Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD.
+repoman_settings = portage.config(local_config=False)
+repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \
+ (repoman_settings.get('PORTDIR_OVERLAY', ''), portdir_overlay)
+# We have to call the config constructor again so
+# that config.repositories is initialized correctly.
+repoman_settings = portage.config(local_config=False, env=dict(os.environ,
+ PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY']))
+
+root = '/'
+trees = {
+ root : {'porttree' : portage.portagetree(root, settings=repoman_settings)}
+}
+portdb = trees[root]['porttree'].dbapi
+
+# Constrain dependency resolution to the master(s)
+# that are specified in layout.conf.
+portdir_overlay = os.path.realpath(portdir_overlay)
+repo_info = portdb._repo_info[portdir_overlay]
+portdb.porttrees = list(repo_info.eclass_db.porttrees)
+portdir = portdb.porttrees[0]
+
+# Generate an appropriate PORTDIR_OVERLAY value for passing into the
+# profile-specific config constructor calls.
+env = os.environ.copy()
+env['PORTDIR'] = portdir
+env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:])
+
+logging.info('Setting paths:')
+logging.info('PORTDIR = "' + portdir + '"')
+logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
+
+# It's confusing if these warnings are displayed without the user
+# being told which profile they come from, so disable them.
+env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn'
+
+categories = []
+for path in set([portdir, portdir_overlay]):
+ categories.extend(portage.util.grabfile(
+ os.path.join(path, 'profiles', 'categories')))
+repoman_settings.categories = tuple(sorted(
+ portage.util.stack_lists([categories], incremental=1)))
+del categories
+
+portdb.settings = repoman_settings
+root_config = RootConfig(repoman_settings, trees[root], None)
+# We really only need to cache the metadata that's necessary for visibility
+# filtering. Anything else can be discarded to reduce memory consumption.
+portdb._aux_cache_keys.clear()
+portdb._aux_cache_keys.update(["EAPI", "KEYWORDS", "SLOT"])
+
+reposplit = myreporoot.split(os.path.sep)
+repolevel = len(reposplit)
+
+# check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
+# Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
+# this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
+if options.mode == 'commit' and repolevel not in [1,2,3]:
+ print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.")
+ print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.")
+ print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.")
+ print(red("***"))
+ err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
+
+startdir = normalize_path(mydir)
+repodir = startdir
+for x in range(0, repolevel - 1):
+ repodir = os.path.dirname(repodir)
+repodir = os.path.realpath(repodir)
+
+def caterror(mycat):
+ 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.")
+
+class ProfileDesc(object):
+ __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',)
+ def __init__(self, arch, status, sub_path, tree_path):
+ self.arch = arch
+ self.status = status
+ if sub_path:
+ sub_path = normalize_path(sub_path.lstrip(os.sep))
+ self.sub_path = sub_path
+ self.tree_path = tree_path
+ if tree_path:
+ self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
+ else:
+ self.abs_path = tree_path
+
+ def __str__(self):
+ if self.sub_path:
+ return self.sub_path
+ return 'empty profile'
+
+profile_list = []
+valid_profile_types = frozenset(['dev', 'exp', 'stable'])
+
+# get lists of valid keywords, licenses, and use
+kwlist = set()
+liclist = set()
+uselist = set()
+global_pmasklines = []
+
+for path in portdb.porttrees:
+ try:
+ liclist.update(os.listdir(os.path.join(path, "licenses")))
+ except OSError:
+ pass
+ kwlist.update(portage.grabfile(os.path.join(path,
+ "profiles", "arch.list")))
+
+ use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc'))
+ for x in use_desc:
+ x = x.split()
+ if x:
+ uselist.add(x[0])
+
+ expand_desc_dir = os.path.join(path, 'profiles', 'desc')
+ try:
+ expand_list = os.listdir(expand_desc_dir)
+ except OSError:
+ pass
+ else:
+ for fn in expand_list:
+ if not fn[-5:] == '.desc':
+ continue
+ use_prefix = fn[:-5].lower() + '_'
+ for x in portage.grabfile(os.path.join(expand_desc_dir, fn)):
+ x = x.split()
+ if x:
+ uselist.add(use_prefix + x[0])
+
+ global_pmasklines.append(portage.util.grabfile_package(
+ os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True))
+
+ desc_path = os.path.join(path, 'profiles', 'profiles.desc')
+ try:
+ desc_file = io.open(_unicode_encode(desc_path,
+ encoding=_encodings['fs'], errors='strict'),
+ mode='r', encoding=_encodings['repo.content'], errors='replace')
+ except EnvironmentError:
+ pass
+ else:
+ for i, x in enumerate(desc_file):
+ if x[0] == "#":
+ continue
+ arch = x.split()
+ if len(arch) == 0:
+ continue
+ if len(arch) != 3:
+ err("wrong format: \"" + bad(x.strip()) + "\" in " + \
+ desc_path + " line %d" % (i+1, ))
+ elif arch[0] not in kwlist:
+ err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
+ desc_path + " line %d" % (i+1, ))
+ elif arch[2] not in valid_profile_types:
+ err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
+ desc_path + " line %d" % (i+1, ))
+ profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path)
+ if not os.path.isdir(profile_desc.abs_path):
+ logging.error(
+ "Invalid %s profile (%s) for arch %s in %s line %d",
+ arch[2], arch[1], arch[0], desc_path, i+1)
+ continue
+ if os.path.exists(
+ os.path.join(profile_desc.abs_path, 'deprecated')):
+ continue
+ profile_list.append(profile_desc)
+ desc_file.close()
+
+repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist))
+repoman_settings.backup_changes('PORTAGE_ARCHLIST')
+
+global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1)
+global_pmaskdict = {}
+for x in global_pmasklines:
+ global_pmaskdict.setdefault(x.cp, []).append(x)
+del global_pmasklines
+
+def has_global_mask(pkg):
+ mask_atoms = global_pmaskdict.get(pkg.cp)
+ if mask_atoms:
+ pkg_list = [pkg]
+ for x in mask_atoms:
+ if portage.dep.match_from_list(x, pkg_list):
+ return x
+ return None
+
+# Ensure that profile sub_path attributes are unique. Process in reverse order
+# so that profiles with duplicate sub_path from overlays will override
+# profiles with the same sub_path from parent repos.
+profiles = {}
+profile_list.reverse()
+profile_sub_paths = set()
+for prof in profile_list:
+ if prof.sub_path in profile_sub_paths:
+ continue
+ profile_sub_paths.add(prof.sub_path)
+ profiles.setdefault(prof.arch, []).append(prof)
+
+# Use an empty profile for checking dependencies of
+# packages that have empty KEYWORDS.
+prof = ProfileDesc('**', 'stable', '', '')
+profiles.setdefault(prof.arch, []).append(prof)
+
+for x in repoman_settings.archlist():
+ if x[0] == "~":
+ continue
+ if x not in profiles:
+ print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc."))
+ print(red("You need to either \"cvs update\" your profiles dir or follow this"))
+ print(red("up with the "+x+" team."))
+ print()
+
+if not liclist:
+ logging.fatal("Couldn't find licenses?")
+ sys.exit(1)
+
+if not kwlist:
+ logging.fatal("Couldn't read KEYWORDS from arch.list")
+ sys.exit(1)
+
+if not uselist:
+ logging.fatal("Couldn't find use.desc?")
+ sys.exit(1)
+
+scanlist=[]
+if repolevel==2:
+ #we are inside a category directory
+ catdir=reposplit[-1]
+ if catdir not in repoman_settings.categories:
+ caterror(catdir)
+ mydirlist=os.listdir(startdir)
+ for x in mydirlist:
+ if x == "CVS" or x.startswith("."):
+ continue
+ if os.path.isdir(startdir+"/"+x):
+ scanlist.append(catdir+"/"+x)
+ repo_subdir = catdir + os.sep
+elif repolevel==1:
+ for x in repoman_settings.categories:
+ if not os.path.isdir(startdir+"/"+x):
+ continue
+ for y in os.listdir(startdir+"/"+x):
+ if y == "CVS" or y.startswith("."):
+ continue
+ if os.path.isdir(startdir+"/"+x+"/"+y):
+ scanlist.append(x+"/"+y)
+ repo_subdir = ""
+elif repolevel==3:
+ catdir = reposplit[-2]
+ if catdir not in repoman_settings.categories:
+ caterror(catdir)
+ scanlist.append(catdir+"/"+reposplit[-1])
+ repo_subdir = scanlist[-1] + os.sep
+else:
+ msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \
+ ' from the current working directory'
+ logging.critical(msg)
+ sys.exit(1)
+
+repo_subdir_len = len(repo_subdir)
+scanlist.sort()
+
+logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
+
+def dev_keywords(profiles):
+ """
+ Create a set of KEYWORDS values that exist in 'dev'
+ profiles. These are used
+ to trigger a message notifying the user when they might
+ want to add the --include-dev option.
+ """
+ type_arch_map = {}
+ for arch, arch_profiles in profiles.items():
+ for prof in arch_profiles:
+ arch_set = type_arch_map.get(prof.status)
+ if arch_set is None:
+ arch_set = set()
+ type_arch_map[prof.status] = arch_set
+ arch_set.add(arch)
+
+ dev_keywords = type_arch_map.get('dev', set())
+ dev_keywords.update(['~' + arch for arch in dev_keywords])
+ return frozenset(dev_keywords)
+
+dev_keywords = dev_keywords(profiles)
+
+stats={}
+fails={}
+
+# provided by the desktop-file-utils package
+desktop_file_validate = find_binary("desktop-file-validate")
+desktop_pattern = re.compile(r'.*\.desktop$')
+
+for x in qacats:
+ stats[x]=0
+ fails[x]=[]
+
+xmllint_capable = False
+metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
+
+def parsedate(s):
+ """Parse a RFC 822 date and time string.
+ This is required for python3 compatibility, since the
+ rfc822.parsedate() function is not available."""
+
+ s_split = []
+ for x in s.upper().split():
+ for y in x.split(','):
+ if y:
+ s_split.append(y)
+
+ if len(s_split) != 6:
+ return None
+
+ # %a, %d %b %Y %H:%M:%S %Z
+ a, d, b, Y, H_M_S, Z = s_split
+
+ # Convert month to integer, since strptime %w is locale-dependent.
+ month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6,
+ 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12}
+ m = month_map.get(b)
+ if m is None:
+ return None
+ m = str(m).rjust(2, '0')
+
+ return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S')
+
+def fetch_metadata_dtd():
+ """
+ Fetch metadata.dtd if it doesn't exist or the ctime is older than
+ metadata_dtd_ctime_interval.
+ @rtype: bool
+ @returns: True if successful, otherwise False
+ """
+
+ must_fetch = True
+ metadata_dtd_st = None
+ current_time = int(time.time())
+ try:
+ metadata_dtd_st = os.stat(metadata_dtd)
+ except EnvironmentError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ raise
+ del e
+ else:
+ # Trigger fetch if metadata.dtd mtime is old or clock is wrong.
+ if abs(current_time - metadata_dtd_st.st_ctime) \
+ < metadata_dtd_ctime_interval:
+ must_fetch = False
+
+ if must_fetch:
+ print()
+ print(green("***") + " the local copy of metadata.dtd " + \
+ "needs to be refetched, doing that now")
+ print()
+ try:
+ url_f = urllib_request_urlopen(metadata_dtd_uri)
+ msg_info = url_f.info()
+ last_modified = msg_info.get('last-modified')
+ if last_modified is not None:
+ last_modified = parsedate(last_modified)
+ if last_modified is not None:
+ last_modified = calendar.timegm(last_modified)
+
+ metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid())
+ try:
+ local_f = open(metadata_dtd_tmp, mode='wb')
+ local_f.write(url_f.read())
+ local_f.close()
+ if last_modified is not None:
+ try:
+ os.utime(metadata_dtd_tmp,
+ (int(last_modified), int(last_modified)))
+ except OSError:
+ # This fails on some odd non-unix-like filesystems.
+ # We don't really need the mtime to be preserved
+ # anyway here (currently we use ctime to trigger
+ # fetch), so just ignore it.
+ pass
+ os.rename(metadata_dtd_tmp, metadata_dtd)
+ finally:
+ try:
+ os.unlink(metadata_dtd_tmp)
+ except OSError:
+ pass
+
+ url_f.close()
+
+ except EnvironmentError as e:
+ print()
+ print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri)
+ print(red("!!!")+" exception '%s' though." % (e,))
+ print(red("!!!")+" fetching new metadata.dtd failed, aborting")
+ return False
+
+ return True
+
+if options.mode == "manifest":
+ pass
+elif not find_binary('xmllint'):
+ print(red("!!! xmllint not found. Can't check metadata.xml.\n"))
+ if options.xml_parse or repolevel==3:
+ print(red("!!!")+" sorry, xmllint is needed. failing\n")
+ sys.exit(1)
+else:
+ if not fetch_metadata_dtd():
+ sys.exit(1)
+ #this can be problematic if xmllint changes their output
+ xmllint_capable=True
+
+if options.mode == 'commit' and vcs:
+ utilities.detect_vcs_conflicts(options, vcs)
+
+if options.mode == "manifest":
+ pass
+elif options.pretend:
+ print(green("\nRepoMan does a once-over of the neighborhood..."))
+else:
+ print(green("\nRepoMan scours the neighborhood..."))
+
+new_ebuilds = set()
+modified_ebuilds = set()
+modified_changelogs = set()
+mychanged = []
+mynew = []
+myremoved = []
+
+if vcs == "cvs":
+ mycvstree = cvstree.getentries("./", recursive=1)
+ mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
+ mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
+if vcs == "svn":
+ svnstatus = os.popen("svn status").readlines()
+ mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
+ mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
+elif vcs == "git":
+ mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines()
+ mychanged = ["./" + elem[:-1] for elem in mychanged]
+
+ mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines()
+ mynew = ["./" + elem[:-1] for elem in mynew]
+elif vcs == "bzr":
+ bzrstatus = os.popen("bzr status -S .").readlines()
+ mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
+ mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ]
+elif vcs == "hg":
+ mychanged = os.popen("hg status --no-status --modified .").readlines()
+ mychanged = ["./" + elem.rstrip() for elem in mychanged]
+ mynew = os.popen("hg status --no-status --added .").readlines()
+ mynew = ["./" + elem.rstrip() for elem in mynew]
+
+if vcs:
+ new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
+ modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild"))
+ modified_changelogs.update(x for x in chain(mychanged, mynew) \
+ if os.path.basename(x) == "ChangeLog")
+
+have_pmasked = False
+have_dev_keywords = False
+dofail = 0
+arch_caches={}
+arch_xmatch_caches = {}
+shared_xmatch_caches = {"cp-list":{}}
+
+# Disable the "ebuild.notadded" check when not in commit mode and
+# running `svn status` in every package dir will be too expensive.
+
+check_ebuild_notadded = not \
+ (vcs == "svn" and repolevel < 3 and options.mode != "commit")
+
+# Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
+thirdpartymirrors = []
+for v in repoman_settings.thirdpartymirrors().values():
+ thirdpartymirrors.extend(v)
+
+class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder):
+ """
+ Implements doctype() as required to avoid deprecation warnings with
+ >=python-2.7.
+ """
+ def doctype(self, name, pubid, system):
+ pass
+
+try:
+ herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml"))
+except (EnvironmentError, ParseError, PermissionDenied) as e:
+ err(str(e))
+except FileNotFound:
+ # TODO: Download as we do for metadata.dtd, but add a way to
+ # disable for non-gentoo repoman users who may not have herds.
+ herd_base = None
+
+for x in scanlist:
+ #ebuilds and digests added to cvs respectively.
+ logging.info("checking package %s" % x)
+ eadded=[]
+ catdir,pkgdir=x.split("/")
+ checkdir=repodir+"/"+x
+ checkdir_relative = ""
+ if repolevel < 3:
+ checkdir_relative = os.path.join(pkgdir, checkdir_relative)
+ if repolevel < 2:
+ checkdir_relative = os.path.join(catdir, checkdir_relative)
+ checkdir_relative = os.path.join(".", checkdir_relative)
+ generated_manifest = False
+
+ if options.mode == "manifest" or \
+ (options.mode != 'manifest-check' and \
+ 'digest' in repoman_settings.features) or \
+ options.mode in ('commit', 'fix') and not options.pretend:
+ auto_assumed = set()
+ fetchlist_dict = portage.FetchlistDict(checkdir,
+ repoman_settings, portdb)
+ if options.mode == 'manifest' and options.force:
+ portage._doebuild_manifest_exempt_depend += 1
+ try:
+ distdir = repoman_settings['DISTDIR']
+ mf = portage.manifest.Manifest(checkdir, distdir,
+ fetchlist_dict=fetchlist_dict)
+ mf.create(requiredDistfiles=None,
+ assumeDistHashesAlways=True)
+ for distfiles in fetchlist_dict.values():
+ for distfile in distfiles:
+ if os.path.isfile(os.path.join(distdir, distfile)):
+ mf.fhashdict['DIST'].pop(distfile, None)
+ else:
+ auto_assumed.add(distfile)
+ mf.write()
+ finally:
+ portage._doebuild_manifest_exempt_depend -= 1
+
+ repoman_settings["O"] = checkdir
+ try:
+ generated_manifest = digestgen(
+ mysettings=repoman_settings, myportdb=portdb)
+ except portage.exception.PermissionDenied as e:
+ generated_manifest = False
+ writemsg_level("!!! Permission denied: '%s'\n" % (e,),
+ level=logging.ERROR, noiselevel=-1)
+
+ if not generated_manifest:
+ print("Unable to generate manifest.")
+ dofail = 1
+
+ if options.mode == "manifest":
+ if not dofail and options.force and auto_assumed and \
+ 'assume-digests' in repoman_settings.features:
+ # Show which digests were assumed despite the --force option
+ # being given. This output will already have been shown by
+ # digestgen() if assume-digests is not enabled, so only show
+ # it here if assume-digests is enabled.
+ pkgs = list(fetchlist_dict)
+ pkgs.sort()
+ portage.writemsg_stdout(" digest.assumed" + \
+ portage.output.colorize("WARN",
+ str(len(auto_assumed)).rjust(18)) + "\n")
+ for cpv in pkgs:
+ fetchmap = fetchlist_dict[cpv]
+ pf = portage.catsplit(cpv)[1]
+ for distfile in sorted(fetchmap):
+ if distfile in auto_assumed:
+ portage.writemsg_stdout(
+ " %s::%s\n" % (pf, distfile))
+ continue
+ elif dofail:
+ sys.exit(1)
+
+ if not generated_manifest:
+ repoman_settings['O'] = checkdir
+ repoman_settings['PORTAGE_QUIET'] = '1'
+ if not portage.digestcheck([], repoman_settings, strict=1):
+ stats["manifest.bad"] += 1
+ fails["manifest.bad"].append(os.path.join(x, 'Manifest'))
+ repoman_settings.pop('PORTAGE_QUIET', None)
+
+ if options.mode == 'manifest-check':
+ continue
+
+ checkdirlist=os.listdir(checkdir)
+ ebuildlist=[]
+ pkgs = {}
+ allvalid = True
+ for y in checkdirlist:
+ if (y in no_exec or y.endswith(".ebuild")) and \
+ stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111:
+ stats["file.executable"] += 1
+ fails["file.executable"].append(os.path.join(checkdir, y))
+ if y.endswith(".ebuild"):
+ pf = y[:-7]
+ ebuildlist.append(pf)
+ cpv = "%s/%s" % (catdir, pf)
+ try:
+ myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
+ except KeyError:
+ allvalid = False
+ stats["ebuild.syntax"] += 1
+ fails["ebuild.syntax"].append(os.path.join(x, y))
+ continue
+ except IOError:
+ allvalid = False
+ stats["ebuild.output"] += 1
+ fails["ebuild.output"].append(os.path.join(x, y))
+ continue
+ if not portage.eapi_is_supported(myaux["EAPI"]):
+ allvalid = False
+ stats["EAPI.unsupported"] += 1
+ fails["EAPI.unsupported"].append(os.path.join(x, y))
+ continue
+ pkgs[pf] = Package(cpv=cpv, metadata=myaux,
+ root_config=root_config, type_name="ebuild")
+
+ # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
+ pkgsplits = {}
+ for i in range(len(ebuildlist)):
+ ebuild_split = portage.pkgsplit(ebuildlist[i])
+ pkgsplits[ebuild_split] = ebuildlist[i]
+ ebuildlist[i] = ebuild_split
+ ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp))
+ for i in range(len(ebuildlist)):
+ ebuildlist[i] = pkgsplits[ebuildlist[i]]
+ del pkgsplits
+
+ slot_keywords = {}
+
+ if len(pkgs) != len(ebuildlist):
+ # If we can't access all the metadata then it's totally unsafe to
+ # commit since there's no way to generate a correct Manifest.
+ # Do not try to do any more QA checks on this package since missing
+ # metadata leads to false positives for several checks, and false
+ # positives confuse users.
+ can_force = False
+ continue
+
+ for y in checkdirlist:
+ m = disallowed_filename_chars_re.search(y.strip(os.sep))
+ if m is not None:
+ stats["file.name"] += 1
+ fails["file.name"].append("%s/%s: char '%s'" % \
+ (checkdir, y, m.group(0)))
+
+ if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
+ continue
+ try:
+ line = 1
+ for l in io.open(_unicode_encode(os.path.join(checkdir, y),
+ encoding=_encodings['fs'], errors='strict'),
+ mode='r', encoding=_encodings['repo.content']):
+ line +=1
+ except UnicodeDecodeError as ue:
+ stats["file.UTF8"] += 1
+ s = ue.object[:ue.start]
+ l2 = s.count("\n")
+ line += l2
+ if l2 != 0:
+ s = s[s.rfind("\n") + 1:]
+ fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
+
+ if vcs in ("git", "hg") and check_ebuild_notadded:
+ if vcs == "git":
+ myf = os.popen("git ls-files --others %s" % \
+ (portage._shell_quote(checkdir_relative),))
+ if vcs == "hg":
+ myf = os.popen("hg status --no-status --unknown %s" % \
+ (portage._shell_quote(checkdir_relative),))
+ for l in myf:
+ if l[:-1][-7:] == ".ebuild":
+ stats["ebuild.notadded"] += 1
+ fails["ebuild.notadded"].append(
+ os.path.join(x, os.path.basename(l[:-1])))
+ myf.close()
+
+ if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded:
+ try:
+ if vcs == "cvs":
+ myf=open(checkdir+"/CVS/Entries","r")
+ if vcs == "svn":
+ myf = os.popen("svn status --depth=files --verbose " + checkdir)
+ if vcs == "bzr":
+ myf = os.popen("bzr ls -v --kind=file " + checkdir)
+ myl = myf.readlines()
+ myf.close()
+ for l in myl:
+ if vcs == "cvs":
+ if l[0]!="/":
+ continue
+ splitl=l[1:].split("/")
+ if not len(splitl):
+ continue
+ if splitl[0][-7:]==".ebuild":
+ eadded.append(splitl[0][:-7])
+ if vcs == "svn":
+ if l[:1] == "?":
+ continue
+ if l[:7] == ' >':
+ # tree conflict, new in subversion 1.6
+ continue
+ l = l.split()[-1]
+ if l[-7:] == ".ebuild":
+ eadded.append(os.path.basename(l[:-7]))
+ if vcs == "bzr":
+ if l[1:2] == "?":
+ continue
+ l = l.split()[-1]
+ if l[-7:] == ".ebuild":
+ eadded.append(os.path.basename(l[:-7]))
+ if vcs == "svn":
+ myf = os.popen("svn status " + checkdir)
+ myl=myf.readlines()
+ myf.close()
+ for l in myl:
+ if l[0] == "A":
+ l = l.rstrip().split(' ')[-1]
+ if l[-7:] == ".ebuild":
+ eadded.append(os.path.basename(l[:-7]))
+ except IOError:
+ if vcs == "cvs":
+ stats["CVS/Entries.IO_error"] += 1
+ fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
+ else:
+ raise
+ continue
+
+ mf = Manifest(checkdir, repoman_settings["DISTDIR"])
+ mydigests=mf.getTypeDigests("DIST")
+
+ fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
+ myfiles_all = []
+ src_uri_error = False
+ for mykey in fetchlist_dict:
+ try:
+ myfiles_all.extend(fetchlist_dict[mykey])
+ except portage.exception.InvalidDependString as e:
+ src_uri_error = True
+ try:
+ portdb.aux_get(mykey, ["SRC_URI"])
+ except KeyError:
+ # This will be reported as an "ebuild.syntax" error.
+ pass
+ else:
+ stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
+ fails["SRC_URI.syntax"].append(
+ "%s.ebuild SRC_URI: %s" % (mykey, e))
+ del fetchlist_dict
+ if not src_uri_error:
+ # This test can produce false positives if SRC_URI could not
+ # be parsed for one or more ebuilds. There's no point in
+ # producing a false error here since the root cause will
+ # produce a valid error elsewhere, such as "SRC_URI.syntax"
+ # or "ebuild.sytax".
+ myfiles_all = set(myfiles_all)
+ for entry in mydigests:
+ if entry not in myfiles_all:
+ stats["digest.unused"] += 1
+ fails["digest.unused"].append(checkdir+"::"+entry)
+ for entry in myfiles_all:
+ if entry not in mydigests:
+ stats["digest.missing"] += 1
+ fails["digest.missing"].append(checkdir+"::"+entry)
+ del myfiles_all
+
+ if os.path.exists(checkdir+"/files"):
+ filesdirlist=os.listdir(checkdir+"/files")
+
+ # recurse through files directory
+ # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
+ while filesdirlist:
+ y = filesdirlist.pop(0)
+ relative_path = os.path.join(x, "files", y)
+ full_path = os.path.join(repodir, relative_path)
+ try:
+ mystat = os.stat(full_path)
+ except OSError as oe:
+ if oe.errno == 2:
+ # don't worry about it. it likely was removed via fix above.
+ continue
+ else:
+ raise oe
+ if S_ISDIR(mystat.st_mode):
+ # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
+ if y == "CVS" or y == ".svn":
+ continue
+ for z in os.listdir(checkdir+"/files/"+y):
+ if z == "CVS" or z == ".svn":
+ continue
+ filesdirlist.append(y+"/"+z)
+ # Current policy is no files over 20 KiB, these are the checks. File size between
+ # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error.
+ elif mystat.st_size > 61440:
+ stats["file.size.fatal"] += 1
+ fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
+ elif mystat.st_size > 20480:
+ stats["file.size"] += 1
+ fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y)
+
+ m = disallowed_filename_chars_re.search(
+ os.path.basename(y.rstrip(os.sep)))
+ if m is not None:
+ stats["file.name"] += 1
+ fails["file.name"].append("%s/files/%s: char '%s'" % \
+ (checkdir, y, m.group(0)))
+
+ if desktop_file_validate and desktop_pattern.match(y):
+ status, cmd_output = subprocess_getstatusoutput(
+ "'%s' '%s'" % (desktop_file_validate, full_path))
+ if os.WIFEXITED(status) and os.WEXITSTATUS(status) != os.EX_OK:
+ # Note: in the future we may want to grab the
+ # warnings in addition to the errors. We're
+ # just doing errors now since we don't want
+ # to generate too much noise at first.
+ error_re = re.compile(r'.*\s*error:\s*(.*)')
+ for line in cmd_output.splitlines():
+ error_match = error_re.match(line)
+ if error_match is None:
+ continue
+ stats["desktop.invalid"] += 1
+ fails["desktop.invalid"].append(
+ relative_path + ': %s' % error_match.group(1))
+
+ del mydigests
+
+ if check_changelog and "ChangeLog" not in checkdirlist:
+ stats["changelog.missing"]+=1
+ fails["changelog.missing"].append(x+"/ChangeLog")
+
+ musedict = {}
+ #metadata.xml file check
+ if "metadata.xml" not in checkdirlist:
+ stats["metadata.missing"]+=1
+ fails["metadata.missing"].append(x+"/metadata.xml")
+ #metadata.xml parse check
+ else:
+ metadata_bad = False
+
+ # read metadata.xml into memory
+ try:
+ _metadata_xml = xml.etree.ElementTree.parse(
+ os.path.join(checkdir, "metadata.xml"),
+ parser=xml.etree.ElementTree.XMLParser(
+ target=_MetadataTreeBuilder()))
+ except (ExpatError, SyntaxError, EnvironmentError) as e:
+ metadata_bad = True
+ stats["metadata.bad"] += 1
+ fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
+ del e
+ else:
+ # load USE flags from metadata.xml
+ try:
+ musedict = utilities.parse_metadata_use(_metadata_xml)
+ except portage.exception.ParseError as e:
+ metadata_bad = True
+ stats["metadata.bad"] += 1
+ fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
+
+ # Run other metadata.xml checkers
+ try:
+ utilities.check_metadata(_metadata_xml, herd_base)
+ except (utilities.UnknownHerdsError, ) as e:
+ metadata_bad = True
+ stats["metadata.bad"] += 1
+ fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
+ del e
+
+ #Only carry out if in package directory or check forced
+ if xmllint_capable and not metadata_bad:
+ # xmlint can produce garbage output even on success, so only dump
+ # the ouput when it fails.
+ st, out = subprocess_getstatusoutput(
+ "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \
+ (metadata_dtd, os.path.join(checkdir, "metadata.xml")))
+ if st != os.EX_OK:
+ print(red("!!!") + " metadata.xml is invalid:")
+ for z in out.splitlines():
+ print(red("!!! ")+z)
+ stats["metadata.bad"]+=1
+ fails["metadata.bad"].append(x+"/metadata.xml")
+
+ del metadata_bad
+ muselist = frozenset(musedict)
+
+ changelog_path = os.path.join(checkdir_relative, "ChangeLog")
+ changelog_modified = changelog_path in modified_changelogs
+
+ allmasked = True
+ # detect unused local USE-descriptions
+ used_useflags = set()
+
+ for y in ebuildlist:
+ relative_path = os.path.join(x, y + ".ebuild")
+ full_path = os.path.join(repodir, relative_path)
+ ebuild_path = y + ".ebuild"
+ if repolevel < 3:
+ ebuild_path = os.path.join(pkgdir, ebuild_path)
+ if repolevel < 2:
+ ebuild_path = os.path.join(catdir, ebuild_path)
+ ebuild_path = os.path.join(".", ebuild_path)
+ if check_changelog and not changelog_modified \
+ and ebuild_path in new_ebuilds:
+ stats['changelog.ebuildadded'] += 1
+ fails['changelog.ebuildadded'].append(relative_path)
+
+ if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded:
+ #ebuild not added to vcs
+ stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
+ fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
+ myesplit=portage.pkgsplit(y)
+ if myesplit is None or myesplit[0] != x.split("/")[-1] \
+ or pv_toolong_re.search(myesplit[1]) \
+ or pv_toolong_re.search(myesplit[2]):
+ stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
+ fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
+ continue
+ elif myesplit[0]!=pkgdir:
+ print(pkgdir,myesplit[0])
+ stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
+ fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
+ continue
+
+ pkg = pkgs[y]
+
+ if pkg.invalid:
+ allvalid = False
+ for k, msgs in pkg.invalid.items():
+ for msg in msgs:
+ stats[k] = stats[k] + 1
+ fails[k].append("%s %s" % (relative_path, msg))
+ continue
+
+ myaux = pkg.metadata
+ eapi = myaux["EAPI"]
+ inherited = pkg.inherited
+ live_ebuild = live_eclasses.intersection(inherited)
+
+ for k, v in myaux.items():
+ if not isinstance(v, basestring):
+ continue
+ m = non_ascii_re.search(v)
+ if m is not None:
+ stats["variable.invalidchar"] += 1
+ fails["variable.invalidchar"].append(
+ ("%s: %s variable contains non-ASCII " + \
+ "character at position %s") % \
+ (relative_path, k, m.start() + 1))
+
+ if not src_uri_error:
+ # Check that URIs don't reference a server from thirdpartymirrors.
+ for uri in portage.dep.use_reduce( \
+ myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True):
+ contains_mirror = False
+ for mirror in thirdpartymirrors:
+ if uri.startswith(mirror):
+ contains_mirror = True
+ break
+ if not contains_mirror:
+ continue
+
+ stats["SRC_URI.mirror"] += 1
+ fails["SRC_URI.mirror"].append(
+ "%s: '%s' found in thirdpartymirrors" % \
+ (relative_path, mirror))
+
+ if myaux.get("PROVIDE"):
+ stats["virtual.oldstyle"]+=1
+ fails["virtual.oldstyle"].append(relative_path)
+
+ for pos, missing_var in enumerate(missingvars):
+ if not myaux.get(missing_var):
+ if catdir == "virtual" and \
+ missing_var in ("HOMEPAGE", "LICENSE"):
+ continue
+ if live_ebuild and missing_var == "KEYWORDS":
+ continue
+ myqakey=missingvars[pos]+".missing"
+ stats[myqakey]=stats[myqakey]+1
+ fails[myqakey].append(x+"/"+y+".ebuild")
+
+ if catdir == "virtual":
+ for var in ("HOMEPAGE", "LICENSE"):
+ if myaux.get(var):
+ myqakey = var + ".virtual"
+ stats[myqakey] = stats[myqakey] + 1
+ fails[myqakey].append(relative_path)
+
+ # 14 is the length of DESCRIPTION=""
+ if len(myaux['DESCRIPTION']) > max_desc_len:
+ stats['DESCRIPTION.toolong'] += 1
+ fails['DESCRIPTION.toolong'].append(
+ "%s: DESCRIPTION is %d characters (max %d)" % \
+ (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
+
+ keywords = myaux["KEYWORDS"].split()
+ stable_keywords = []
+ for keyword in keywords:
+ if not keyword.startswith("~") and \
+ not keyword.startswith("-"):
+ stable_keywords.append(keyword)
+ if stable_keywords:
+ if ebuild_path in new_ebuilds:
+ stable_keywords.sort()
+ stats["KEYWORDS.stable"] += 1
+ fails["KEYWORDS.stable"].append(
+ x + "/" + y + ".ebuild added with stable keywords: %s" % \
+ " ".join(stable_keywords))
+
+ ebuild_archs = set(kw.lstrip("~") for kw in keywords \
+ if not kw.startswith("-"))
+
+ previous_keywords = slot_keywords.get(myaux["SLOT"])
+ if previous_keywords is None:
+ slot_keywords[myaux["SLOT"]] = set()
+ elif ebuild_archs and not live_ebuild:
+ dropped_keywords = previous_keywords.difference(ebuild_archs)
+ if dropped_keywords:
+ stats["KEYWORDS.dropped"] += 1
+ fails["KEYWORDS.dropped"].append(
+ relative_path + ": %s" % \
+ " ".join(sorted(dropped_keywords)))
+
+ slot_keywords[myaux["SLOT"]].update(ebuild_archs)
+
+ # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
+ if "-*" in keywords:
+ haskeyword = False
+ for kw in keywords:
+ if kw[0] == "~":
+ kw = kw[1:]
+ if kw in kwlist:
+ haskeyword = True
+ if not haskeyword:
+ stats["KEYWORDS.stupid"] += 1
+ fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
+
+ """
+ Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
+ not be allowed to be marked stable
+ """
+ if live_ebuild:
+ bad_stable_keywords = []
+ for keyword in keywords:
+ if not keyword.startswith("~") and \
+ not keyword.startswith("-"):
+ bad_stable_keywords.append(keyword)
+ del keyword
+ if bad_stable_keywords:
+ stats["LIVEVCS.stable"] += 1
+ fails["LIVEVCS.stable"].append(
+ x + "/" + y + ".ebuild with stable keywords:%s " % \
+ bad_stable_keywords)
+ del bad_stable_keywords
+
+ if keywords and not has_global_mask(pkg):
+ stats["LIVEVCS.unmasked"] += 1
+ fails["LIVEVCS.unmasked"].append(relative_path)
+
+ if options.ignore_arches:
+ arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
+ repoman_settings["ACCEPT_KEYWORDS"].split()]]
+ else:
+ arches=[]
+ for keyword in myaux["KEYWORDS"].split():
+ if (keyword[0]=="-"):
+ continue
+ elif (keyword[0]=="~"):
+ arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
+ else:
+ arches.append([keyword, keyword, [keyword]])
+ allmasked = False
+ if not arches:
+ # Use an empty profile for checking dependencies of
+ # packages that have empty KEYWORDS.
+ arches.append(['**', '**', ['**']])
+
+ unknown_pkgs = {}
+ baddepsyntax = False
+ badlicsyntax = False
+ badprovsyntax = False
+ catpkg = catdir+"/"+y
+
+ inherited_java_eclass = "java-pkg-2" in inherited or \
+ "java-pkg-opt-2" in inherited
+ inherited_wxwidgets_eclass = "wxwidgets" in inherited
+ operator_tokens = set(["||", "(", ")"])
+ type_list, badsyntax = [], []
+ for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
+ "LICENSE", "PROPERTIES", "PROVIDE"):
+ mydepstr = myaux[mytype]
+
+ token_class = None
+ if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
+ token_class=portage.dep.Atom
+
+ try:
+ atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \
+ is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class)
+ except portage.exception.InvalidDependString as e:
+ atoms = None
+ badsyntax.append(str(e))
+
+ if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
+ if mytype in ("RDEPEND", "PDEPEND") and \
+ "test?" in mydepstr.split():
+ stats[mytype + '.suspect'] += 1
+ fails[mytype + '.suspect'].append(relative_path + \
+ ": 'test?' USE conditional in %s" % mytype)
+
+ for atom in atoms:
+ if atom == "||":
+ continue
+
+ if not atom.blocker and \
+ not portdb.cp_list(atom.cp) and \
+ not atom.cp.startswith("virtual/"):
+ unknown_pkgs.setdefault(atom.cp, set()).add(
+ (mytype, atom.unevaluated_atom))
+
+ is_blocker = atom.blocker
+
+ if mytype == "DEPEND" and \
+ not is_blocker and \
+ not inherited_java_eclass and \
+ atom.cp == "virtual/jdk":
+ stats['java.eclassesnotused'] += 1
+ fails['java.eclassesnotused'].append(relative_path)
+ elif mytype == "DEPEND" and \
+ not is_blocker and \
+ not inherited_wxwidgets_eclass and \
+ atom.cp == "x11-libs/wxGTK":
+ stats['wxwidgets.eclassnotused'] += 1
+ fails['wxwidgets.eclassnotused'].append(
+ relative_path + ": DEPENDs on x11-libs/wxGTK"
+ " without inheriting wxwidgets.eclass")
+ elif mytype in ("PDEPEND", "RDEPEND"):
+ if not is_blocker and \
+ atom.cp in suspect_rdepend:
+ stats[mytype + '.suspect'] += 1
+ fails[mytype + '.suspect'].append(
+ relative_path + ": '%s'" % atom)
+
+ if atom.operator == "~" and \
+ portage.versions.catpkgsplit(atom.cpv)[3] != "r0":
+ stats[mytype + '.badtilde'] += 1
+ fails[mytype + '.badtilde'].append(
+ (relative_path + ": %s uses the ~ operator"
+ " with a non-zero revision:" + \
+ " '%s'") % (mytype, atom))
+
+ type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
+
+ for m,b in zip(type_list, badsyntax):
+ stats[m+".syntax"] += 1
+ fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
+
+ badlicsyntax = len([z for z in type_list if z == "LICENSE"])
+ badprovsyntax = len([z for z in type_list if z == "PROVIDE"])
+ baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
+ badlicsyntax = badlicsyntax > 0
+ badprovsyntax = badprovsyntax > 0
+
+ # uselist checks - global
+ myuse = []
+ default_use = []
+ for myflag in myaux["IUSE"].split():
+ flag_name = myflag.lstrip("+-")
+ used_useflags.add(flag_name)
+ if myflag != flag_name:
+ default_use.append(myflag)
+ if flag_name not in uselist:
+ myuse.append(flag_name)
+
+ # uselist checks - metadata
+ for mypos in range(len(myuse)-1,-1,-1):
+ if myuse[mypos] and (myuse[mypos] in muselist):
+ del myuse[mypos]
+
+ if default_use and not eapi_has_iuse_defaults(eapi):
+ for myflag in default_use:
+ stats['EAPI.incompatible'] += 1
+ fails['EAPI.incompatible'].append(
+ (relative_path + ": IUSE defaults" + \
+ " not supported with EAPI='%s':" + \
+ " '%s'") % (eapi, myflag))
+
+ for mypos in range(len(myuse)):
+ stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
+ fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
+
+ # license checks
+ if not badlicsyntax:
+ # Parse the LICENSE variable, remove USE conditions and
+ # flatten it.
+ licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True)
+ # Check each entry to ensure that it exists in PORTDIR's
+ # license directory.
+ for lic in licenses:
+ # Need to check for "||" manually as no portage
+ # function will remove it without removing values.
+ if lic not in liclist and lic != "||":
+ stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
+ fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic)
+
+ #keyword checks
+ myuse = myaux["KEYWORDS"].split()
+ for mykey in myuse:
+ myskey=mykey[:]
+ if myskey[0]=="-":
+ myskey=myskey[1:]
+ if myskey[0]=="~":
+ myskey=myskey[1:]
+ if mykey!="-*":
+ if myskey not in kwlist:
+ stats["KEYWORDS.invalid"] += 1
+ fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
+ elif myskey not in profiles:
+ stats["KEYWORDS.invalid"] += 1
+ fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
+
+ #restrict checks
+ myrestrict = None
+ try:
+ myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True)
+ except portage.exception.InvalidDependString as e:
+ stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
+ fails["RESTRICT.syntax"].append(
+ "%s: RESTRICT: %s" % (relative_path, e))
+ del e
+ if myrestrict:
+ myrestrict = set(myrestrict)
+ mybadrestrict = myrestrict.difference(valid_restrict)
+ if mybadrestrict:
+ stats["RESTRICT.invalid"] += len(mybadrestrict)
+ for mybad in mybadrestrict:
+ fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
+ #REQUIRED_USE check
+ required_use = myaux["REQUIRED_USE"]
+ if required_use:
+ if not eapi_has_required_use(eapi):
+ stats['EAPI.incompatible'] += 1
+ fails['EAPI.incompatible'].append(
+ relative_path + ": REQUIRED_USE" + \
+ " not supported with EAPI='%s'" % (eapi,))
+ try:
+ portage.dep.check_required_use(required_use, (),
+ pkg.iuse.is_valid_flag)
+ except portage.exception.InvalidDependString as e:
+ stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1
+ fails["REQUIRED_USE.syntax"].append(
+ "%s: REQUIRED_USE: %s" % (relative_path, e))
+ del e
+
+ # Syntax Checks
+ relative_path = os.path.join(x, y + ".ebuild")
+ full_path = os.path.join(repodir, relative_path)
+ if not vcs_preserves_mtime:
+ if ebuild_path not in new_ebuilds and \
+ ebuild_path not in modified_ebuilds:
+ pkg.mtime = None
+ try:
+ # All ebuilds should have utf_8 encoding.
+ f = io.open(_unicode_encode(full_path,
+ encoding=_encodings['fs'], errors='strict'),
+ mode='r', encoding=_encodings['repo.content'])
+ try:
+ for check_name, e in run_checks(f, pkg):
+ stats[check_name] += 1
+ fails[check_name].append(relative_path + ': %s' % e)
+ finally:
+ f.close()
+ except UnicodeDecodeError:
+ # A file.UTF8 failure will have already been recorded above.
+ pass
+
+ if options.force:
+ # The dep_check() calls are the most expensive QA test. If --force
+ # is enabled, there's no point in wasting time on these since the
+ # user is intent on forcing the commit anyway.
+ continue
+
+ for keyword,arch,groups in arches:
+
+ if arch not in profiles:
+ # A missing profile will create an error further down
+ # during the KEYWORDS verification.
+ continue
+
+ for prof in profiles[arch]:
+
+ if prof.status not in ("stable", "dev") or \
+ prof.status == "dev" and not options.include_dev:
+ continue
+
+ dep_settings = arch_caches.get(prof.sub_path)
+ if dep_settings is None:
+ dep_settings = portage.config(
+ config_profile_path=prof.abs_path,
+ config_incrementals=repoman_incrementals,
+ local_config=False,
+ _unmatched_removal=options.unmatched_removal,
+ env=env)
+ if options.without_mask:
+ dep_settings._mask_manager = \
+ copy.deepcopy(dep_settings._mask_manager)
+ dep_settings._mask_manager._pmaskdict.clear()
+ arch_caches[prof.sub_path] = dep_settings
+
+ xmatch_cache_key = (prof.sub_path, tuple(groups))
+ xcache = arch_xmatch_caches.get(xmatch_cache_key)
+ if xcache is None:
+ portdb.melt()
+ portdb.freeze()
+ xcache = portdb.xcache
+ xcache.update(shared_xmatch_caches)
+ arch_xmatch_caches[xmatch_cache_key] = xcache
+
+ trees["/"]["porttree"].settings = dep_settings
+ portdb.settings = dep_settings
+ portdb.xcache = xcache
+ # for package.use.mask support inside dep_check
+ dep_settings.setcpv(pkg)
+ dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
+ # just in case, prevent config.reset() from nuking these.
+ dep_settings.backup_changes("ACCEPT_KEYWORDS")
+
+ if not baddepsyntax:
+ ismasked = not ebuild_archs or \
+ pkg.cpv not in portdb.xmatch("list-visible", pkg.cp)
+ if ismasked:
+ if not have_pmasked:
+ have_pmasked = bool(dep_settings._getMaskAtom(
+ pkg.cpv, pkg.metadata))
+ if options.ignore_masked:
+ continue
+ #we are testing deps for a masked package; give it some lee-way
+ suffix="masked"
+ matchmode = "minimum-all"
+ else:
+ suffix=""
+ matchmode = "minimum-visible"
+
+ if not have_dev_keywords:
+ have_dev_keywords = \
+ bool(dev_keywords.intersection(keywords))
+
+ if prof.status == "dev":
+ suffix=suffix+"indev"
+
+ for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
+
+ mykey=mytype+".bad"+suffix
+ myvalue = myaux[mytype]
+ if not myvalue:
+ continue
+
+ success, atoms = portage.dep_check(myvalue, portdb,
+ dep_settings, use="all", mode=matchmode,
+ trees=trees)
+
+ if success:
+ if atoms:
+ for atom in atoms:
+ if not atom.blocker:
+ # Don't bother with dependency.unknown
+ # for cases in which *DEPEND.bad is
+ # triggered.
+ unknown_pkgs.pop(atom.cp, None)
+
+ if not prof.sub_path:
+ # old-style virtuals currently aren't
+ # resolvable with empty profile, since
+ # 'virtuals' mappings are unavailable
+ # (it would be expensive to search
+ # for PROVIDE in all ebuilds)
+ atoms = [atom for atom in atoms if not \
+ (atom.cp.startswith('virtual/') and \
+ not portdb.cp_list(atom.cp))]
+
+ #we have some unsolvable deps
+ #remove ! deps, which always show up as unsatisfiable
+ atoms = [str(atom.unevaluated_atom) \
+ for atom in atoms if not atom.blocker]
+
+ #if we emptied out our list, continue:
+ if not atoms:
+ continue
+ stats[mykey]=stats[mykey]+1
+ fails[mykey].append("%s: %s(%s) %s" % \
+ (relative_path, keyword,
+ prof, repr(atoms)))
+ else:
+ stats[mykey]=stats[mykey]+1
+ fails[mykey].append("%s: %s(%s) %s" % \
+ (relative_path, keyword,
+ prof, repr(atoms)))
+
+ if not baddepsyntax and unknown_pkgs:
+ all_unknown = set()
+ all_unknown.update(*unknown_pkgs.values())
+ type_map = {}
+ for mytype, atom in all_unknown:
+ type_map.setdefault(mytype, set()).add(atom)
+ for mytype, atoms in type_map.items():
+ stats["dependency.unknown"] += 1
+ fails["dependency.unknown"].append("%s: %s: %s" %
+ (relative_path, mytype, ", ".join(sorted(atoms))))
+
+ # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
+ # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
+ #if not portage.portdb.xmatch("bestmatch-visible",x):
+ # stats["ebuild.nostable"]+=1
+ # fails["ebuild.nostable"].append(x)
+ if ebuildlist and allmasked and repolevel == 3:
+ stats["ebuild.allmasked"]+=1
+ fails["ebuild.allmasked"].append(x)
+
+ # check if there are unused local USE-descriptions in metadata.xml
+ # (unless there are any invalids, to avoid noise)
+ if allvalid:
+ for myflag in muselist.difference(used_useflags):
+ stats["metadata.warning"] += 1
+ fails["metadata.warning"].append(
+ "%s/metadata.xml: unused local USE-description: '%s'" % \
+ (x, myflag))
+
+if options.mode == "manifest":
+ sys.exit(dofail)
+
+#dofail will be set to 1 if we have failed in at least one non-warning category
+dofail=0
+#dowarn will be set to 1 if we tripped any warnings
+dowarn=0
+#dofull will be set if we should print a "repoman full" informational message
+dofull = options.mode != 'full'
+
+for x in qacats:
+ if not stats[x]:
+ continue
+ dowarn = 1
+ if x not in qawarnings:
+ dofail = 1
+
+if dofail or \
+ (dowarn and not (options.quiet or options.mode == "scan")):
+ dofull = 0
+
+# Save QA output so that it can be conveniently displayed
+# in $EDITOR while the user creates a commit message.
+# Otherwise, the user would not be able to see this output
+# once the editor has taken over the screen.
+qa_output = io.StringIO()
+style_file = ConsoleStyleFile(sys.stdout)
+if options.mode == 'commit' and \
+ (not commitmessage or not commitmessage.strip()):
+ style_file.write_listener = qa_output
+console_writer = StyleWriter(file=style_file, maxcol=9999)
+console_writer.style_listener = style_file.new_styles
+
+f = formatter.AbstractFormatter(console_writer)
+
+utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
+
+style_file.flush()
+del console_writer, f, style_file
+qa_output = qa_output.getvalue()
+qa_output = qa_output.splitlines(True)
+
+def grouplist(mylist,seperator="/"):
+ """(list,seperator="/") -- Takes a list of elements; groups them into
+ same initial element categories. Returns a dict of {base:[sublist]}
+ From: ["blah/foo","spork/spatula","blah/weee/splat"]
+ To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
+ mygroups={}
+ for x in mylist:
+ xs=x.split(seperator)
+ if xs[0]==".":
+ xs=xs[1:]
+ if xs[0] not in mygroups:
+ mygroups[xs[0]]=[seperator.join(xs[1:])]
+ else:
+ mygroups[xs[0]]+=[seperator.join(xs[1:])]
+ return mygroups
+
+suggest_ignore_masked = False
+suggest_include_dev = False
+
+if have_pmasked and not (options.without_mask or options.ignore_masked):
+ suggest_ignore_masked = True
+if have_dev_keywords and not options.include_dev:
+ suggest_include_dev = True
+
+if suggest_ignore_masked or suggest_include_dev:
+ print()
+ if suggest_ignore_masked:
+ print(bold("Note: use --without-mask to check " + \
+ "KEYWORDS on dependencies of masked packages"))
+
+ if suggest_include_dev:
+ print(bold("Note: use --include-dev (-d) to check " + \
+ "dependencies for 'dev' profiles"))
+ print()
+
+if options.mode != 'commit':
+ if dofull:
+ print(bold("Note: type \"repoman full\" for a complete listing."))
+ if dowarn and not dofail:
+ 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.\"")
+ elif not dofail:
+ print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
+ elif dofail:
+ print(bad("Please fix these important QA issues first."))
+ print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
+ sys.exit(1)
+else:
+ if dofail and can_force and options.force and not options.pretend:
+ print(green("RepoMan sez:") + \
+ " \"You want to commit even with these QA issues?\n" + \
+ " I'll take it this time, but I'm not happy.\"\n")
+ elif dofail:
+ if options.force and not can_force:
+ print(bad("The --force option has been disabled due to extraordinary issues."))
+ print(bad("Please fix these important QA issues first."))
+ print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n")
+ sys.exit(1)
+
+ if options.pretend:
+ print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n")
+
+ myunadded = []
+ if vcs == "cvs":
+ try:
+ myvcstree=portage.cvstree.getentries("./",recursive=1)
+ myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
+ except SystemExit as e:
+ raise # TODO propagate this
+ except:
+ err("Error retrieving CVS tree; exiting.")
+ if vcs == "svn":
+ try:
+ svnstatus=os.popen("svn status --no-ignore").readlines()
+ myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
+ except SystemExit as e:
+ raise # TODO propagate this
+ except:
+ err("Error retrieving SVN info; exiting.")
+ if vcs == "git":
+ # get list of files not under version control or missing
+ myf = os.popen("git ls-files --others")
+ myunadded = [ "./" + elem[:-1] for elem in myf ]
+ myf.close()
+ if vcs == "bzr":
+ try:
+ bzrstatus=os.popen("bzr status -S .").readlines()
+ myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ]
+ except SystemExit as e:
+ raise # TODO propagate this
+ except:
+ err("Error retrieving bzr info; exiting.")
+ if vcs == "hg":
+ myunadded = os.popen("hg status --no-status --unknown .").readlines()
+ myunadded = ["./" + elem.rstrip() for elem in myunadded]
+
+ # Mercurial doesn't handle manually deleted files as removed from
+ # the repository, so the user need to remove them before commit,
+ # using "hg remove [FILES]"
+ mydeleted = os.popen("hg status --no-status --deleted .").readlines()
+ mydeleted = ["./" + elem.rstrip() for elem in mydeleted]
+
+
+ myautoadd=[]
+ if myunadded:
+ for x in range(len(myunadded)-1,-1,-1):
+ xs=myunadded[x].split("/")
+ if xs[-1]=="files":
+ print("!!! files dir is not added! Please correct this.")
+ sys.exit(-1)
+ elif xs[-1]=="Manifest":
+ # It's a manifest... auto add
+ myautoadd+=[myunadded[x]]
+ del myunadded[x]
+
+ if myautoadd:
+ print(">>> Auto-Adding missing Manifest(s)...")
+ if options.pretend:
+ if vcs == "cvs":
+ print("(cvs add "+" ".join(myautoadd)+")")
+ elif vcs == "svn":
+ print("(svn add "+" ".join(myautoadd)+")")
+ elif vcs == "git":
+ print("(git add "+" ".join(myautoadd)+")")
+ elif vcs == "bzr":
+ print("(bzr add "+" ".join(myautoadd)+")")
+ elif vcs == "hg":
+ print("(hg add "+" ".join(myautoadd)+")")
+ retval=0
+ else:
+ if vcs == "cvs":
+ retval=os.system("cvs add "+" ".join(myautoadd))
+ elif vcs == "svn":
+ retval=os.system("svn add "+" ".join(myautoadd))
+ elif vcs == "git":
+ retval=os.system("git add "+" ".join(myautoadd))
+ elif vcs == "bzr":
+ retval=os.system("bzr add "+" ".join(myautoadd))
+ elif vcs == "hg":
+ retval=os.system("hg add "+" ".join(myautoadd))
+ if retval:
+ writemsg_level("!!! Exiting on %s (shell) error code: %s\n" % \
+ (vcs, retval), level=logging.ERROR, noiselevel=-1)
+ sys.exit(retval)
+
+ if myunadded:
+ print(red("!!! The following files are in your local tree but are not added to the master"))
+ print(red("!!! tree. Please remove them from the local tree or add them to the master tree."))
+ for x in myunadded:
+ print(" ",x)
+ print()
+ print()
+ sys.exit(1)
+
+ if vcs == "hg" and mydeleted:
+ print(red("!!! The following files are removed manually from your local tree but are not"))
+ print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\"."))
+ for x in mydeleted:
+ print(" ",x)
+ print()
+ print()
+ sys.exit(1)
+
+ if vcs == "cvs":
+ mycvstree = cvstree.getentries("./", recursive=1)
+ mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
+ mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
+ myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
+ bin_blob_pattern = re.compile("^-kb$")
+ no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
+ recursive=1, basedir="./"))
+
+
+ if vcs == "svn":
+ svnstatus = os.popen("svn status").readlines()
+ mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")]
+ mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")]
+ myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")]
+
+ # Subversion expands keywords specified in svn:keywords properties.
+ props = os.popen("svn propget -R svn:keywords").readlines()
+ expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \
+ for prop in props if " - " in prop)
+
+ elif vcs == "git":
+ mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines()
+ mychanged = ["./" + elem[:-1] for elem in mychanged]
+
+ mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines()
+ mynew = ["./" + elem[:-1] for elem in mynew]
+
+ myremoved = os.popen("git diff-index --name-only --relative --diff-filter=D HEAD").readlines()
+ myremoved = ["./" + elem[:-1] for elem in myremoved]
+
+ if vcs == "bzr":
+ bzrstatus = os.popen("bzr status -S .").readlines()
+ mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ]
+ 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" ) ]
+ myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ]
+ 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" ) ]
+ # Bazaar expands nothing.
+
+ if vcs == "hg":
+ mychanged = os.popen("hg status --no-status --modified .").readlines()
+ mychanged = ["./" + elem.rstrip() for elem in mychanged]
+ mynew = os.popen("hg status --no-status --added .").readlines()
+ mynew = ["./" + elem.rstrip() for elem in mynew]
+ myremoved = os.popen("hg status --no-status --removed .").readlines()
+ myremoved = ["./" + elem.rstrip() for elem in myremoved]
+
+ if vcs:
+ if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)):
+ print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"")
+ print()
+ print("(Didn't find any changed files...)")
+ print()
+ sys.exit(1)
+
+ # Manifests need to be regenerated after all other commits, so don't commit
+ # them now even if they have changed.
+ mymanifests = set()
+ myupdates = set()
+ for f in mychanged + mynew:
+ if "Manifest" == os.path.basename(f):
+ mymanifests.add(f)
+ else:
+ myupdates.add(f)
+ if vcs in ('git', 'hg'):
+ myupdates.difference_update(myremoved)
+ myupdates = list(myupdates)
+ mymanifests = list(mymanifests)
+ myheaders = []
+ mydirty = []
+
+ print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
+ if vcs not in ('cvs', 'svn'):
+ # With git, bzr and hg, there's never any keyword expansion, so
+ # there's no need to regenerate manifests and all files will be
+ # committed in one big commit at the end.
+ print()
+ else:
+ if vcs == 'cvs':
+ headerstring = "'\$(Header|Id).*\$'"
+ elif vcs == "svn":
+ svn_keywords = dict((k.lower(), k) for k in [
+ "Rev",
+ "Revision",
+ "LastChangedRevision",
+ "Date",
+ "LastChangedDate",
+ "Author",
+ "LastChangedBy",
+ "URL",
+ "HeadURL",
+ "Id",
+ "Header",
+ ])
+
+ for myfile in myupdates:
+
+ # for CVS, no_expansion contains files that are excluded from expansion
+ if vcs == "cvs":
+ if myfile in no_expansion:
+ continue
+
+ # for SVN, expansion contains files that are included in expansion
+ elif vcs == "svn":
+ if myfile not in expansion:
+ continue
+
+ # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files.
+ enabled_keywords = []
+ for k in expansion[myfile]:
+ keyword = svn_keywords.get(k.lower())
+ if keyword is not None:
+ enabled_keywords.append(keyword)
+
+ headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords)
+
+ myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile)
+ if myout[0] == 0:
+ myheaders.append(myfile)
+
+ print("%s have headers that will change." % green(str(len(myheaders))))
+ print("* Files with headers will cause the manifests to be changed and committed separately.")
+
+ logging.info("myupdates: %s", myupdates)
+ logging.info("myheaders: %s", myheaders)
+
+ commitmessage = options.commitmsg
+ if options.commitmsgfile:
+ try:
+ f = io.open(_unicode_encode(options.commitmsgfile,
+ encoding=_encodings['fs'], errors='strict'),
+ mode='r', encoding=_encodings['content'], errors='replace')
+ commitmessage = f.read()
+ f.close()
+ del f
+ except (IOError, OSError) as e:
+ if e.errno == errno.ENOENT:
+ portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
+ else:
+ raise
+ # We've read the content so the file is no longer needed.
+ commitmessagefile = None
+ if not commitmessage or not commitmessage.strip():
+ try:
+ editor = os.environ.get("EDITOR")
+ if editor and utilities.editor_is_executable(editor):
+ commitmessage = utilities.get_commit_message_with_editor(
+ editor, message=qa_output)
+ else:
+ commitmessage = utilities.get_commit_message_with_stdin()
+ except KeyboardInterrupt:
+ exithandler()
+ if not commitmessage or not commitmessage.strip():
+ print("* no commit message? aborting commit.")
+ sys.exit(1)
+ commitmessage = commitmessage.rstrip()
+ portage_version = getattr(portage, "VERSION", None)
+ if portage_version is None:
+ sys.stderr.write("Failed to insert portage version in message!\n")
+ sys.stderr.flush()
+ portage_version = "Unknown"
+ unameout = platform.system() + " "
+ if platform.system() in ["Darwin", "SunOS"]:
+ unameout += platform.processor()
+ else:
+ unameout += platform.machine()
+ commitmessage += "\n\n(Portage version: %s/%s/%s" % \
+ (portage_version, vcs, unameout)
+ if options.force:
+ commitmessage += ", RepoMan options: --force"
+ commitmessage += ")"
+
+ if options.ask and userquery('Commit changes?', True) != 'Yes':
+ print("* aborting commit.")
+ sys.exit(1)
+
+ if vcs in ('cvs', 'svn') and (myupdates or myremoved):
+ myfiles = myupdates + myremoved
+ if not myheaders and "sign" not in repoman_settings.features:
+ myfiles += mymanifests
+ fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
+ mymsg = os.fdopen(fd, "wb")
+ mymsg.write(_unicode_encode(commitmessage))
+ mymsg.close()
+
+ print()
+ print(green("Using commit message:"))
+ print(green("------------------------------------------------------------------------------"))
+ print(commitmessage)
+ print(green("------------------------------------------------------------------------------"))
+ print()
+
+ # Having a leading ./ prefix on file paths can trigger a bug in
+ # the cvs server when committing files to multiple directories,
+ # so strip the prefix.
+ myfiles = [f.lstrip("./") for f in myfiles]
+
+ commit_cmd = [vcs]
+ commit_cmd.extend(vcs_global_opts)
+ commit_cmd.append("commit")
+ commit_cmd.extend(vcs_local_opts)
+ commit_cmd.extend(["-F", commitmessagefile])
+ commit_cmd.extend(myfiles)
+
+ try:
+ if options.pretend:
+ print("(%s)" % (" ".join(commit_cmd),))
+ else:
+ retval = spawn(commit_cmd, env=os.environ)
+ if retval != os.EX_OK:
+ writemsg_level(("!!! Exiting on %s (shell) " + \
+ "error code: %s\n") % (vcs, retval),
+ level=logging.ERROR, noiselevel=-1)
+ sys.exit(retval)
+ finally:
+ try:
+ os.unlink(commitmessagefile)
+ except OSError:
+ pass
+
+ # Setup the GPG commands
+ def gpgsign(filename):
+ gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND")
+ if gpgcmd is None:
+ raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \
+ " Is make.globals missing?")
+ if "${PORTAGE_GPG_KEY}" in gpgcmd and \
+ "PORTAGE_GPG_KEY" not in repoman_settings:
+ raise MissingParameter("PORTAGE_GPG_KEY is unset!")
+ if "${PORTAGE_GPG_DIR}" in gpgcmd:
+ if "PORTAGE_GPG_DIR" not in repoman_settings:
+ repoman_settings["PORTAGE_GPG_DIR"] = \
+ os.path.expanduser("~/.gnupg")
+ logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \
+ % repoman_settings["PORTAGE_GPG_DIR"])
+ else:
+ repoman_settings["PORTAGE_GPG_DIR"] = \
+ os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"])
+ if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
+ raise portage.exception.InvalidLocation(
+ "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
+ repoman_settings["PORTAGE_GPG_DIR"])
+ gpgvars = {"FILE": filename}
+ for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"):
+ v = repoman_settings.get(k)
+ if v is not None:
+ gpgvars[k] = v
+ gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars)
+ if options.pretend:
+ print("("+gpgcmd+")")
+ else:
+ rValue = os.system(gpgcmd)
+ if rValue == os.EX_OK:
+ os.rename(filename+".asc", filename)
+ else:
+ raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
+
+ # When files are removed and re-added, the cvs server will put /Attic/
+ # inside the $Header path. This code detects the problem and corrects it
+ # so that the Manifest will generate correctly. See bug #169500.
+ # Use binary mode in order to avoid potential character encoding issues.
+ cvs_header_re = re.compile(br'^#\s*\$Header.*\$$')
+ attic_str = b'/Attic/'
+ attic_replace = b'/'
+ for x in myheaders:
+ f = open(_unicode_encode(x,
+ encoding=_encodings['fs'], errors='strict'),
+ mode='rb')
+ mylines = f.readlines()
+ f.close()
+ modified = False
+ for i, line in enumerate(mylines):
+ if cvs_header_re.match(line) is not None and \
+ attic_str in line:
+ mylines[i] = line.replace(attic_str, attic_replace)
+ modified = True
+ if modified:
+ portage.util.write_atomic(x, b''.join(mylines),
+ mode='wb')
+
+ manifest_commit_required = True
+ if vcs in ('cvs', 'svn') and (myupdates or myremoved):
+ myfiles = myupdates + myremoved
+ for x in range(len(myfiles)-1, -1, -1):
+ if myfiles[x].count("/") < 4-repolevel:
+ del myfiles[x]
+ mydone=[]
+ if repolevel==3: # In a package dir
+ repoman_settings["O"] = startdir
+ digestgen(mysettings=repoman_settings, myportdb=portdb)
+ elif repolevel==2: # In a category dir
+ for x in myfiles:
+ xs=x.split("/")
+ if len(xs) < 4-repolevel:
+ continue
+ if xs[0]==".":
+ xs=xs[1:]
+ if xs[0] in mydone:
+ continue
+ mydone.append(xs[0])
+ repoman_settings["O"] = os.path.join(startdir, xs[0])
+ if not os.path.isdir(repoman_settings["O"]):
+ continue
+ digestgen(mysettings=repoman_settings, myportdb=portdb)
+ elif repolevel==1: # repo-cvsroot
+ print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n")
+ for x in myfiles:
+ xs=x.split("/")
+ if len(xs) < 4-repolevel:
+ continue
+ if xs[0]==".":
+ xs=xs[1:]
+ if "/".join(xs[:2]) in mydone:
+ continue
+ mydone.append("/".join(xs[:2]))
+ repoman_settings["O"] = os.path.join(startdir, xs[0], xs[1])
+ if not os.path.isdir(repoman_settings["O"]):
+ continue
+ digestgen(mysettings=repoman_settings, myportdb=portdb)
+ else:
+ print(red("I'm confused... I don't know where I am!"))
+ sys.exit(1)
+
+ # Force an unsigned commit when more than one Manifest needs to be signed.
+ if repolevel < 3 and "sign" in repoman_settings.features:
+
+ fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
+ mymsg = os.fdopen(fd, "wb")
+ mymsg.write(_unicode_encode(commitmessage))
+ mymsg.write(b"\n (Unsigned Manifest commit)")
+ mymsg.close()
+
+ commit_cmd = [vcs]
+ commit_cmd.extend(vcs_global_opts)
+ commit_cmd.append("commit")
+ commit_cmd.extend(vcs_local_opts)
+ commit_cmd.extend(["-F", commitmessagefile])
+ commit_cmd.extend(f.lstrip("./") for f in mymanifests)
+
+ try:
+ if options.pretend:
+ print("(%s)" % (" ".join(commit_cmd),))
+ else:
+ retval = spawn(commit_cmd, env=os.environ)
+ if retval:
+ writemsg_level(("!!! Exiting on %s (shell) " + \
+ "error code: %s\n") % (vcs, retval),
+ level=logging.ERROR, noiselevel=-1)
+ sys.exit(retval)
+ finally:
+ try:
+ os.unlink(commitmessagefile)
+ except OSError:
+ pass
+ manifest_commit_required = False
+
+ signed = False
+ if "sign" in repoman_settings.features:
+ signed = True
+ myfiles = myupdates + myremoved + mymanifests
+ try:
+ if repolevel==3: # In a package dir
+ repoman_settings["O"] = "."
+ gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
+ elif repolevel==2: # In a category dir
+ mydone=[]
+ for x in myfiles:
+ xs=x.split("/")
+ if len(xs) < 4-repolevel:
+ continue
+ if xs[0]==".":
+ xs=xs[1:]
+ if xs[0] in mydone:
+ continue
+ mydone.append(xs[0])
+ repoman_settings["O"] = os.path.join(".", xs[0])
+ if not os.path.isdir(repoman_settings["O"]):
+ continue
+ gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
+ elif repolevel==1: # repo-cvsroot
+ print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n")
+ mydone=[]
+ for x in myfiles:
+ xs=x.split("/")
+ if len(xs) < 4-repolevel:
+ continue
+ if xs[0]==".":
+ xs=xs[1:]
+ if "/".join(xs[:2]) in mydone:
+ continue
+ mydone.append("/".join(xs[:2]))
+ repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
+ if not os.path.isdir(repoman_settings["O"]):
+ continue
+ gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
+ except portage.exception.PortageException as e:
+ portage.writemsg("!!! %s\n" % str(e))
+ portage.writemsg("!!! Disabled FEATURES='sign'\n")
+ signed = False
+
+ if vcs == 'git':
+ # It's not safe to use the git commit -a option since there might
+ # be some modified files elsewhere in the working tree that the
+ # user doesn't want to commit. Therefore, call git update-index
+ # in order to ensure that the index is updated with the latest
+ # versions of all new and modified files in the relevant portion
+ # of the working tree.
+ myfiles = mymanifests + myupdates
+ myfiles.sort()
+ update_index_cmd = ["git", "update-index"]
+ update_index_cmd.extend(f.lstrip("./") for f in myfiles)
+ if options.pretend:
+ print("(%s)" % (" ".join(update_index_cmd),))
+ else:
+ retval = spawn(update_index_cmd, env=os.environ)
+ if retval != os.EX_OK:
+ writemsg_level(("!!! Exiting on %s (shell) " + \
+ "error code: %s\n") % (vcs, retval),
+ level=logging.ERROR, noiselevel=-1)
+ sys.exit(retval)
+
+ if vcs in ['git', 'bzr', 'hg'] or manifest_commit_required or signed:
+
+ myfiles = mymanifests[:]
+ if vcs in ['git', 'bzr', 'hg']:
+ myfiles += myupdates
+ myfiles += myremoved
+ myfiles.sort()
+
+ fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
+ mymsg = os.fdopen(fd, "wb")
+ # strip the closing parenthesis
+ mymsg.write(_unicode_encode(commitmessage[:-1]))
+ if signed:
+ mymsg.write(_unicode_encode(
+ ", signed Manifest commit with key %s)" % \
+ repoman_settings["PORTAGE_GPG_KEY"]))
+ else:
+ mymsg.write(b", unsigned Manifest commit)")
+ mymsg.close()
+
+ commit_cmd = []
+ if options.pretend and vcs is None:
+ # substitute a bogus value for pretend output
+ commit_cmd.append("cvs")
+ else:
+ commit_cmd.append(vcs)
+ commit_cmd.extend(vcs_global_opts)
+ commit_cmd.append("commit")
+ commit_cmd.extend(vcs_local_opts)
+ if vcs == "hg":
+ commit_cmd.extend(["--logfile", commitmessagefile])
+ commit_cmd.extend(myfiles)
+ else:
+ commit_cmd.extend(["-F", commitmessagefile])
+ commit_cmd.extend(f.lstrip("./") for f in myfiles)
+
+ try:
+ if options.pretend:
+ print("(%s)" % (" ".join(commit_cmd),))
+ else:
+ retval = spawn(commit_cmd, env=os.environ)
+ if retval != os.EX_OK:
+ writemsg_level(("!!! Exiting on %s (shell) " + \
+ "error code: %s\n") % (vcs, retval),
+ level=logging.ERROR, noiselevel=-1)
+ sys.exit(retval)
+ finally:
+ try:
+ os.unlink(commitmessagefile)
+ except OSError:
+ pass
+
+ print()
+ if vcs:
+ print("Commit complete.")
+ else:
+ print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything")
+ print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n")
+sys.exit(0)
+