__save_ebuild_env: filter __repo_key
[portage.git] / bin / egencache
index 6d18f66e554a448f638e017cdfceadaa39df8004..2c5dddce9a7a53813326513976e350976c75ea70 100755 (executable)
@@ -1,9 +1,11 @@
 #!/usr/bin/python
-# Copyright 2009-2011 Gentoo Foundation
+# Copyright 2009-2013 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
-from __future__ import print_function
+# unicode_literals for compat with TextIOWrapper in Python 2
+from __future__ import print_function, unicode_literals
 
+import platform
 import signal
 import sys
 # This block ensures that ^C interrupts are handled quietly.
@@ -20,6 +22,17 @@ try:
 except KeyboardInterrupt:
        sys.exit(128 + signal.SIGINT)
 
+def debug_signal(signum, frame):
+       import pdb
+       pdb.set_trace()
+
+if platform.python_implementation() == 'Jython':
+       debug_signum = signal.SIGUSR2 # bug #424259
+else:
+       debug_signum = signal.SIGUSR1
+
+signal.signal(debug_signum, debug_signal)
+
 import io
 import logging
 import optparse
@@ -28,21 +41,22 @@ import time
 import textwrap
 import re
 
-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
-
+from os import path as osp
+pym_path = osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")
+sys.path.insert(0, pym_path)
+import portage
+portage._internal_caller = True
 from portage import os, _encodings, _unicode_encode, _unicode_decode
 from _emerge.MetadataRegen import MetadataRegen
 from portage.cache.cache_errors import CacheError, StatCollision
 from portage.manifest import guessManifestFileType
+from portage.package.ebuild._parallel_manifest.ManifestScheduler import ManifestScheduler
 from portage.util import cmp_sort_key, writemsg_level
+from portage.util._async.run_main_scheduler import run_main_scheduler
+from portage.util._eventloop.global_event_loop import global_event_loop
 from portage import cpv_getkey
 from portage.dep import Atom, isjustname
-from portage.versions import pkgcmp, pkgsplit, vercmp
+from portage.versions import pkgsplit, vercmp
 
 try:
        from xml.etree import ElementTree
@@ -68,13 +82,16 @@ def parse_args(args):
        actions = optparse.OptionGroup(parser, 'Actions')
        actions.add_option("--update",
                action="store_true",
-               help="update metadata/cache/ (generate as necessary)")
+               help="update metadata/md5-cache/ (generate as necessary)")
        actions.add_option("--update-use-local-desc",
                action="store_true",
                help="update the use.local.desc file from metadata.xml")
        actions.add_option("--update-changelogs",
                action="store_true",
                help="update the ChangeLog files from SCM logs")
+       actions.add_option("--update-manifests",
+               action="store_true",
+               help="update manifests")
        parser.add_option_group(actions)
 
        common = optparse.OptionGroup(parser, 'Common options')
@@ -84,12 +101,36 @@ def parse_args(args):
        common.add_option("--config-root",
                help="location of portage config files",
                dest="portage_configroot")
+       common.add_option("--gpg-dir",
+               help="override the PORTAGE_GPG_DIR variable",
+               dest="gpg_dir")
+       common.add_option("--gpg-key",
+               help="override the PORTAGE_GPG_KEY variable",
+               dest="gpg_key")
        common.add_option("--portdir",
-               help="override the portage tree location",
+               help="override the portage tree location (deprecated in favor of --repositories-configuration)",
                dest="portdir")
        common.add_option("--portdir-overlay",
-               help="override the PORTDIR_OVERLAY variable (requires that --repo is also specified)",
+               help="override the PORTDIR_OVERLAY variable (requires that --repo is also specified) (deprecated in favor of --repositories-configuration)",
                dest="portdir_overlay")
+       common.add_option("--repositories-configuration",
+               help="override configuration of repositories (in format of repos.conf) (requires that --repo is also specified)",
+               dest="repositories_configuration")
+       common.add_option("--sign-manifests",
+               type="choice",
+               choices=('y', 'n'),
+               metavar="<y|n>",
+               help="manually override layout.conf sign-manifests setting")
+       common.add_option("--strict-manifests",
+               type="choice",
+               choices=('y', 'n'),
+               metavar="<y|n>",
+               help="manually override \"strict\" FEATURES setting")
+       common.add_option("--thin-manifests",
+               type="choice",
+               choices=('y', 'n'),
+               metavar="<y|n>",
+               help="manually override layout.conf thin-manifests setting")
        common.add_option("--tolerant",
                action="store_true",
                help="exit successfully if only minor errors occurred")
@@ -102,7 +143,7 @@ def parse_args(args):
        update.add_option("--cache-dir",
                help="location of the metadata cache",
                dest="cache_dir")
-       update.add_option("--jobs",
+       update.add_option("-j", "--jobs",
                action="store",
                help="max ebuild processes to spawn")
        update.add_option("--load-average",
@@ -171,9 +212,11 @@ def parse_args(args):
                        parser.error("Write access denied: --cache-dir='%s'" % \
                                (options.cache_dir,))
 
-       if options.portdir_overlay is not None and \
-               options.repo is None:
-               parser.error("--portdir-overlay option requires --repo option")
+       if options.repo is None:
+               if options.repositories_configuration is not None:
+                       parser.error("--repositories-configuration option requires --repo option")
+               if options.portdir_overlay is not None:
+                       parser.error("--portdir-overlay option requires --repo option")
 
        for atom in args:
                try:
@@ -215,9 +258,11 @@ class GenCache(object):
                else:
                        self._cp_set = None
                        self._cp_missing = set()
+               write_auxdb = "metadata-transfer" in portdb.settings.features
                self._regen = MetadataRegen(portdb, cp_iter=cp_iter,
                        consumer=self._metadata_callback,
-                       max_jobs=max_jobs, max_load=max_load)
+                       max_jobs=max_jobs, max_load=max_load,
+                       write_auxdb=write_auxdb, main=True)
                self.returncode = os.EX_OK
                conf = portdb.repositories.get_repo_for_location(tree)
                self._trg_caches = tuple(conf.iter_pregenerated_caches(
@@ -238,10 +283,15 @@ class GenCache(object):
 
                self._existing_nodes = set()
 
-       def _metadata_callback(self, cpv, repo_path, metadata, ebuild_hash):
+       def _metadata_callback(self, cpv, repo_path, metadata,
+               ebuild_hash, eapi_supported):
                self._existing_nodes.add(cpv)
                self._cp_missing.discard(cpv_getkey(cpv))
-               if metadata is not None:
+
+               # Since we're supposed to be able to efficiently obtain the
+               # EAPI from _parse_eapi_ebuild_head, we don't write cache
+               # entries for unsupported EAPIs.
+               if metadata is not None and eapi_supported:
                        if metadata.get('EAPI') == '0':
                                del metadata['EAPI']
                        for trg_cache in self._trg_caches:
@@ -315,33 +365,9 @@ class GenCache(object):
                                        level=logging.ERROR, noiselevel=-1)
 
        def run(self):
-
-               received_signal = []
-
-               def sighandler(signum, frame):
-                       signal.signal(signal.SIGINT, signal.SIG_IGN)
-                       signal.signal(signal.SIGTERM, signal.SIG_IGN)
-                       self._regen.terminate()
-                       received_signal.append(128 + signum)
-
-               earlier_sigint_handler = signal.signal(signal.SIGINT, sighandler)
-               earlier_sigterm_handler = signal.signal(signal.SIGTERM, sighandler)
-
-               try:
-                       self._regen.run()
-               finally:
-                       # Restore previous handlers
-                       if earlier_sigint_handler is not None:
-                               signal.signal(signal.SIGINT, earlier_sigint_handler)
-                       else:
-                               signal.signal(signal.SIGINT, signal.SIG_DFL)
-                       if earlier_sigterm_handler is not None:
-                               signal.signal(signal.SIGTERM, earlier_sigterm_handler)
-                       else:
-                               signal.signal(signal.SIGTERM, signal.SIG_DFL)
-
-               if received_signal:
-                       sys.exit(received_signal[0])
+               signum = run_main_scheduler(self._regen)
+               if signum is not None:
+                       sys.exit(128 + signum)
 
                self.returncode |= self._regen.returncode
 
@@ -366,8 +392,8 @@ class GenCache(object):
                                self.returncode |= 1
                                writemsg_level(
                                        "Error listing cache entries for " + \
-                                       "'%s/metadata/cache': %s, continuing...\n" % \
-                                       (self._portdb.porttree_root, ce),
+                                       "'%s': %s, continuing...\n" % \
+                                       (trg_cache.location, ce),
                                        level=logging.ERROR, noiselevel=-1)
 
                else:
@@ -388,8 +414,8 @@ class GenCache(object):
                                self.returncode |= 1
                                writemsg_level(
                                        "Error listing cache entries for " + \
-                                       "'%s/metadata/cache': %s, continuing...\n" % \
-                                       (self._portdb.porttree_root, ce),
+                                       "'%s': %s, continuing...\n" % \
+                                       (trg_cache.location, ce),
                                        level=logging.ERROR, noiselevel=-1)
 
                if cp_missing:
@@ -421,6 +447,9 @@ class GenCache(object):
                                        "committing target: %s\n" % (ce,),
                                        level=logging.ERROR, noiselevel=-1)
 
+               if hasattr(trg_cache, '_prune_empty_dirs'):
+                       trg_cache._prune_empty_dirs()
+
 class GenUseLocalDesc(object):
        def __init__(self, portdb, output=None,
                        preserve_comments=False):
@@ -428,7 +457,7 @@ class GenUseLocalDesc(object):
                self._portdb = portdb
                self._output = output
                self._preserve_comments = preserve_comments
-       
+
        def run(self):
                repo_path = self._portdb.porttrees[0]
                ops = {'<':0, '<=':1, '=':2, '>=':3, '>':4}
@@ -501,14 +530,14 @@ class GenUseLocalDesc(object):
                                encoding=_encodings['fs'], errors='strict'),
                                mode='a', encoding=_encodings['repo.content'],
                                errors='backslashreplace')
-                       output.write(_unicode_decode('\n'))
+                       output.write('\n')
                else:
-                       output.write(_unicode_decode('''
-# This file is deprecated as per GLEP 56 in favor of metadata.xml. Please add
-# your descriptions to your package's metadata.xml ONLY.
-# * generated automatically using egencache *
+                       output.write(textwrap.dedent('''\
+                               # This file is deprecated as per GLEP 56 in favor of metadata.xml. Please add
+                               # your descriptions to your package's metadata.xml ONLY.
+                               # * generated automatically using egencache *
 
-'''.lstrip()))
+                               '''))
 
                # The cmp function no longer exists in python3, so we'll
                # implement our own here under a slightly different name
@@ -536,7 +565,8 @@ class GenUseLocalDesc(object):
                for cp in self._portdb.cp_all():
                        metadata_path = os.path.join(repo_path, cp, 'metadata.xml')
                        try:
-                               metadata = ElementTree.parse(metadata_path,
+                               metadata = ElementTree.parse(_unicode_encode(metadata_path,
+                                       encoding=_encodings['fs'], errors='strict'),
                                        parser=ElementTree.XMLParser(
                                        target=_MetadataTreeBuilder()))
                        except IOError:
@@ -565,7 +595,7 @@ class GenUseLocalDesc(object):
                                                                return cmp_func(atomb.operator, atoma.operator)
                                                        # Version matching
                                                        elif atoma.cpv != atomb.cpv:
-                                                               return pkgcmp(pkgsplit(atoma.cpv), pkgsplit(atomb.cpv))
+                                                               return vercmp(atoma.version, atomb.version)
                                                        # Versions match, let's fallback to operator matching
                                                        else:
                                                                return cmp_func(ops.get(atoma.operator, -1),
@@ -592,8 +622,7 @@ class GenUseLocalDesc(object):
                                                                resatoms = sorted(reskeys, key=cmp_sort_key(atomcmp))
                                                                resdesc = resdict[reskeys[resatoms[-1]]]
 
-                                               output.write(_unicode_decode(
-                                                       '%s:%s - %s\n' % (cp, flag, resdesc)))
+                                               output.write('%s:%s - %s\n' % (cp, flag, resdesc))
 
                output.close()
 
@@ -690,12 +719,12 @@ class GenChangeLogs(object):
                        self.returncode |= 2
                        return
 
-               output.write(_unicode_decode('''
-# ChangeLog for %s
-# Copyright 1999-%s Gentoo Foundation; Distributed under the GPL v2
-# $Header: $
+               output.write(textwrap.dedent('''\
+                       # ChangeLog for %s
+                       # Copyright 1999-%s Gentoo Foundation; Distributed under the GPL v2
+                       # $Header: $
 
-''' % (cp, time.strftime('%Y'))).lstrip())
+                       ''' % (cp, time.strftime('%Y'))))
 
                # now grab all the commits
                commits = self.grab(['git', 'rev-list', 'HEAD', '--', '.']).split()
@@ -759,11 +788,10 @@ class GenChangeLogs(object):
                        # Reverse the sort order for headers.
                        for c in reversed(changed):
                                if c.startswith('+') and c.endswith('.ebuild'):
-                                       output.write(_unicode_decode(
-                                               '*%s (%s)\n' % (c[1:-7], date)))
+                                       output.write('*%s (%s)\n' % (c[1:-7], date))
                                        wroteheader = True
                        if wroteheader:
-                               output.write(_unicode_decode('\n'))
+                               output.write('\n')
 
                        # strip '<cp>: ', '[<cp>] ', and similar
                        body[0] = re.sub(r'^\W*' + re.escape(cp) + r'\W+', '', body[0])
@@ -783,13 +811,12 @@ class GenChangeLogs(object):
 
                        # don't break filenames on hyphens
                        self._wrapper.break_on_hyphens = False
-                       output.write(_unicode_decode(
-                               self._wrapper.fill(
-                               '%s; %s %s:' % (date, author, ', '.join(changed)))))
+                       output.write(self._wrapper.fill(
+                               '%s; %s %s:' % (date, author, ', '.join(changed))))
                        # but feel free to break commit messages there
                        self._wrapper.break_on_hyphens = True
-                       output.write(_unicode_decode(
-                               '\n%s\n\n' % '\n'.join(self._wrapper.fill(x) for x in body)))
+                       output.write(
+                               '\n%s\n\n' % '\n'.join(self._wrapper.fill(x) for x in body))
 
                output.close()
 
@@ -822,16 +849,23 @@ class GenChangeLogs(object):
                                self.generate_changelog(cp)
 
 def egencache_main(args):
-       parser, options, atoms = parse_args(args)
-
-       config_root = options.config_root
 
        # The calling environment is ignored, so the program is
        # completely controlled by commandline arguments.
        env = {}
 
+       if not sys.stdout.isatty():
+               portage.output.nocolor()
+               env['NOCOLOR'] = 'true'
+
+       parser, options, atoms = parse_args(args)
+
+       config_root = options.config_root
+
        if options.repo is None:
                env['PORTDIR_OVERLAY'] = ''
+       elif options.repositories_configuration is not None:
+               env['PORTAGE_REPOSITORIES'] = options.repositories_configuration
        elif options.portdir_overlay:
                env['PORTDIR_OVERLAY'] = options.portdir_overlay
 
@@ -841,14 +875,13 @@ def egencache_main(args):
        if options.portdir is not None:
                env['PORTDIR'] = options.portdir
 
-       eprefix = portage.const.EPREFIX
-
        settings = portage.config(config_root=config_root,
-               local_config=False, env=env, eprefix=eprefix)
+               local_config=False, env=env)
 
        default_opts = None
        if not options.ignore_default_opts:
-               default_opts = settings.get('EGENCACHE_DEFAULT_OPTS', '').split()
+               default_opts = portage.util.shlex_split(
+                       settings.get('EGENCACHE_DEFAULT_OPTS', ''))
 
        if default_opts:
                parser, options, args = parse_args(default_opts + args)
@@ -857,20 +890,50 @@ def egencache_main(args):
                        env['PORTAGE_DEPCACHEDIR'] = options.cache_dir
 
                settings = portage.config(config_root=config_root,
-                       local_config=False, env=env, eprefix=eprefix)
+                       local_config=False, env=env)
 
-       if not options.update and not options.update_use_local_desc \
-                       and not options.update_changelogs:
+       if not (options.update or options.update_use_local_desc or
+                       options.update_changelogs or options.update_manifests):
                parser.error('No action specified')
                return 1
 
+       repo_path = None
+       if options.repo is not None:
+               repo_path = settings.repositories.treemap.get(options.repo)
+               if repo_path is None:
+                       parser.error("Unable to locate repository named '%s'" % \
+                               (options.repo,))
+                       return 1
+       else:
+               repo_path = settings.repositories.mainRepoLocation()
+               if not repo_path:
+                       parser.error("PORTDIR is undefined")
+                       return 1
+
+       repo_config = settings.repositories.get_repo_for_location(repo_path)
+
+       if options.strict_manifests is not None:
+               if options.strict_manifests == "y":
+                       settings.features.add("strict")
+               else:
+                       settings.features.discard("strict")
+
        if options.update and 'metadata-transfer' not in settings.features:
-               settings.features.add('metadata-transfer')
+               # Forcibly enable metadata-transfer if portdbapi has a pregenerated
+               # cache that does not support eclass validation.
+               cache = repo_config.get_pregenerated_cache(
+                       portage.dbapi.dbapi._known_keys, readonly=True)
+               if cache is not None and not cache.complete_eclass_entries:
+                       settings.features.add('metadata-transfer')
+               cache = None
 
        settings.lock()
 
        portdb = portage.portdbapi(mysettings=settings)
 
+       # Limit ebuilds to the specified repo.
+       portdb.porttrees = [repo_path]
+
        if options.update:
                if options.cache_dir is not None:
                        # already validated earlier
@@ -886,17 +949,71 @@ def egencache_main(args):
                                        level=logging.ERROR, noiselevel=-1)
                                return 1
 
-       if options.repo is not None:
-               repo_path = portdb.getRepositoryPath(options.repo)
-               if repo_path is None:
-                       parser.error("Unable to locate repository named '%s'" % \
-                               (options.repo,))
-                       return 1
+       if options.sign_manifests is not None:
+               repo_config.sign_manifest = options.sign_manifests == 'y'
 
-               # Limit ebuilds to the specified repo.
-               portdb.porttrees = [repo_path]
-       else:
-               portdb.porttrees = [portdb.porttree_root]
+       if options.thin_manifests is not None:
+               repo_config.thin_manifest = options.thin_manifests == 'y'
+
+       gpg_cmd = None
+       gpg_vars = None
+       force_sign_key = None
+
+       if options.update_manifests:
+               if repo_config.sign_manifest:
+
+                       sign_problem = False
+                       gpg_dir = None
+                       gpg_cmd = settings.get("PORTAGE_GPG_SIGNING_COMMAND")
+                       if gpg_cmd is None:
+                               writemsg_level("egencache: error: "
+                                       "PORTAGE_GPG_SIGNING_COMMAND is unset! "
+                                       "Is make.globals missing?\n",
+                                       level=logging.ERROR, noiselevel=-1)
+                               sign_problem = True
+                       elif "${PORTAGE_GPG_KEY}" in gpg_cmd and \
+                               options.gpg_key is None and \
+                               "PORTAGE_GPG_KEY" not in settings:
+                               writemsg_level("egencache: error: "
+                                       "PORTAGE_GPG_KEY is unset!\n",
+                                       level=logging.ERROR, noiselevel=-1)
+                               sign_problem = True
+                       elif "${PORTAGE_GPG_DIR}" in gpg_cmd:
+                               if options.gpg_dir is not None:
+                                       gpg_dir = options.gpg_dir
+                               elif "PORTAGE_GPG_DIR" not in settings:
+                                       gpg_dir = os.path.expanduser("~/.gnupg")
+                               else:
+                                       gpg_dir = os.path.expanduser(settings["PORTAGE_GPG_DIR"])
+                               if not os.access(gpg_dir, os.X_OK):
+                                       writemsg_level(("egencache: error: "
+                                               "Unable to access directory: "
+                                               "PORTAGE_GPG_DIR='%s'\n") % gpg_dir,
+                                               level=logging.ERROR, noiselevel=-1)
+                                       sign_problem = True
+
+                       if sign_problem:
+                               writemsg_level("egencache: You may disable manifest "
+                                       "signatures with --sign-manifests=n or by setting "
+                                       "\"sign-manifests = false\" in metadata/layout.conf\n",
+                                       level=logging.ERROR, noiselevel=-1)
+                               return 1
+
+                       gpg_vars = {}
+                       if gpg_dir is not None:
+                               gpg_vars["PORTAGE_GPG_DIR"] = gpg_dir
+                       gpg_var_names = []
+                       if options.gpg_key is None:
+                               gpg_var_names.append("PORTAGE_GPG_KEY")
+                       else:
+                               gpg_vars["PORTAGE_GPG_KEY"] = options.gpg_key
+
+                       for k in gpg_var_names:
+                               v = settings.get(k)
+                               if v is not None:
+                                       gpg_vars[k] = v
+
+                       force_sign_key = gpg_vars.get("PORTAGE_GPG_KEY")
 
        ret = [os.EX_OK]
 
@@ -915,6 +1032,29 @@ def egencache_main(args):
                else:
                        ret.append(gen_cache.returncode)
 
+       if options.update_manifests:
+
+               cp_iter = None
+               if atoms:
+                       cp_iter = iter(atoms)
+
+               event_loop = global_event_loop()
+               scheduler = ManifestScheduler(portdb, cp_iter=cp_iter,
+                       gpg_cmd=gpg_cmd, gpg_vars=gpg_vars,
+                       force_sign_key=force_sign_key,
+                       max_jobs=options.jobs,
+                       max_load=options.load_average,
+                       event_loop=event_loop)
+
+               signum = run_main_scheduler(scheduler)
+               if signum is not None:
+                       sys.exit(128 + signum)
+
+               if options.tolerant:
+                       ret.append(os.EX_OK)
+               else:
+                       ret.append(scheduler.returncode)
+
        if options.update_use_local_desc:
                gen_desc = GenUseLocalDesc(portdb,
                        output=options.uld_output,