| |
| from __future__ import print_function |
| |
| import sys |
| import signal |
| import logging |
| import operator |
| |
| import portage |
| from portage import os |
| from portage import eapi_is_supported |
| from portage.util import writemsg_level |
| from portage.cache.cache_errors import CacheError |
| from _emerge.ProgressHandler import ProgressHandler |
| from portage.eclass_cache import hashed_path |
| |
| |
| def action_metadata(settings, portdb, myopts, porttrees=None): |
| if porttrees is None: |
| porttrees = portdb.porttrees |
| portage.writemsg_stdout("\n>>> Updating Portage cache\n") |
| cachedir = os.path.normpath(settings.depcachedir) |
| if cachedir in ["/", "/bin", "/dev", "/etc", "/home", |
| "/lib", "/opt", "/proc", "/root", "/sbin", |
| "/sys", "/tmp", "/usr", "/var"]: |
| print("!!! PORTAGE_DEPCACHEDIR IS SET TO A PRIMARY " + \ |
| "ROOT DIRECTORY ON YOUR SYSTEM.", file=sys.stderr) |
| print("!!! This is ALMOST CERTAINLY NOT what you want: '%s'" % cachedir, file=sys.stderr) |
| sys.exit(73) |
| if not os.path.exists(cachedir): |
| os.makedirs(cachedir) |
| |
| auxdbkeys = portdb._known_keys |
| |
| class TreeData(object): |
| __slots__ = ('dest_db', 'eclass_db', 'path', 'src_db', 'valid_nodes') |
| def __init__(self, dest_db, eclass_db, path, src_db): |
| self.dest_db = dest_db |
| self.eclass_db = eclass_db |
| self.path = path |
| self.src_db = src_db |
| self.valid_nodes = set() |
| |
| porttrees_data = [] |
| for path in porttrees: |
| src_db = portdb._pregen_auxdb.get(path) |
| if src_db is None: |
| # portdbapi does not populate _pregen_auxdb |
| # when FEATURES=metadata-transfer is enabled |
| src_db = portdb._create_pregen_cache(path) |
| |
| if src_db is not None: |
| eclass_db = portdb.repositories.get_repo_for_location(path).eclass_db |
| # Update eclass data which may be stale after sync. |
| eclass_db.update_eclasses() |
| porttrees_data.append(TreeData(portdb.auxdb[path], eclass_db, path, src_db)) |
| |
| porttrees = [tree_data.path for tree_data in porttrees_data] |
| |
| quiet = settings.get('TERM') == 'dumb' or \ |
| '--quiet' in myopts or \ |
| not sys.stdout.isatty() |
| |
| onProgress = None |
| if not quiet: |
| progressBar = portage.output.TermProgressBar() |
| progressHandler = ProgressHandler() |
| onProgress = progressHandler.onProgress |
| def display(): |
| progressBar.set(progressHandler.curval, progressHandler.maxval) |
| progressHandler.display = display |
| def sigwinch_handler(signum, frame): |
| lines, progressBar.term_columns = \ |
| portage.output.get_term_size() |
| signal.signal(signal.SIGWINCH, sigwinch_handler) |
| |
| # Temporarily override portdb.porttrees so portdb.cp_all() |
| # will only return the relevant subset. |
| portdb_porttrees = portdb.porttrees |
| portdb.porttrees = porttrees |
| try: |
| cp_all = portdb.cp_all() |
| finally: |
| portdb.porttrees = portdb_porttrees |
| |
| curval = 0 |
| maxval = len(cp_all) |
| if onProgress is not None: |
| onProgress(maxval, curval) |
| |
| # TODO: Display error messages, but do not interfere with the progress bar. |
| # Here's how: |
| # 1) erase the progress bar |
| # 2) show the error message |
| # 3) redraw the progress bar on a new line |
| |
| for cp in cp_all: |
| for tree_data in porttrees_data: |
| |
| src_chf = tree_data.src_db.validation_chf |
| dest_chf = tree_data.dest_db.validation_chf |
| dest_chf_key = '_%s_' % dest_chf |
| dest_chf_getter = operator.attrgetter(dest_chf) |
| |
| for cpv in portdb.cp_list(cp, mytree=tree_data.path): |
| tree_data.valid_nodes.add(cpv) |
| try: |
| src = tree_data.src_db[cpv] |
| except (CacheError, KeyError): |
| continue |
| |
| ebuild_location = portdb.findname(cpv, mytree=tree_data.path) |
| if ebuild_location is None: |
| continue |
| ebuild_hash = hashed_path(ebuild_location) |
| |
| try: |
| if not tree_data.src_db.validate_entry(src, |
| ebuild_hash, tree_data.eclass_db): |
| continue |
| except CacheError: |
| continue |
| |
| eapi = src.get('EAPI') |
| if not eapi: |
| eapi = '0' |
| eapi_supported = eapi_is_supported(eapi) |
| if not eapi_supported: |
| continue |
| |
| dest = None |
| try: |
| dest = tree_data.dest_db[cpv] |
| except (KeyError, CacheError): |
| pass |
| |
| for d in (src, dest): |
| if d is not None and d.get('EAPI') in ('', '0'): |
| del d['EAPI'] |
| |
| if src_chf != 'mtime': |
| # src may contain an irrelevant _mtime_ which corresponds |
| # to the time that the cache entry was written |
| src.pop('_mtime_', None) |
| |
| if src_chf != dest_chf: |
| # populate src entry with dest_chf_key |
| # (the validity of the dest_chf that we generate from the |
| # ebuild here relies on the fact that we already used |
| # validate_entry to validate the ebuild with src_chf) |
| src[dest_chf_key] = dest_chf_getter(ebuild_hash) |
| |
| if dest is not None: |
| if not (dest.get(dest_chf_key) == src[dest_chf_key] and \ |
| tree_data.eclass_db.validate_and_rewrite_cache( |
| dest['_eclasses_'], tree_data.dest_db.validation_chf, |
| tree_data.dest_db.store_eclass_paths) is not None and \ |
| set(dest['_eclasses_']) == set(src['_eclasses_'])): |
| dest = None |
| else: |
| # We don't want to skip the write unless we're really |
| # sure that the existing cache is identical, so don't |
| # trust _mtime_ and _eclasses_ alone. |
| for k in auxdbkeys: |
| if dest.get(k, '') != src.get(k, ''): |
| dest = None |
| break |
| |
| if dest is not None: |
| # The existing data is valid and identical, |
| # so there's no need to overwrite it. |
| continue |
| |
| try: |
| tree_data.dest_db[cpv] = src |
| except CacheError: |
| # ignore it; can't do anything about it. |
| pass |
| |
| curval += 1 |
| if onProgress is not None: |
| onProgress(maxval, curval) |
| |
| if onProgress is not None: |
| onProgress(maxval, curval) |
| |
| for tree_data in porttrees_data: |
| try: |
| dead_nodes = set(tree_data.dest_db) |
| except CacheError as e: |
| writemsg_level("Error listing cache entries for " + \ |
| "'%s': %s, continuing...\n" % (tree_data.path, e), |
| level=logging.ERROR, noiselevel=-1) |
| del e |
| else: |
| dead_nodes.difference_update(tree_data.valid_nodes) |
| for cpv in dead_nodes: |
| try: |
| del tree_data.dest_db[cpv] |
| except (KeyError, CacheError): |
| pass |
| |
| if not quiet: |
| # make sure the final progress is displayed |
| progressHandler.display() |
| print() |
| signal.signal(signal.SIGWINCH, signal.SIG_DFL) |
| |
| portdb.flush_cache() |
| sys.stdout.flush() |