# Copyright 1998-2020 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 import logging import operator import sys import signal from _emerge.ProgressHandler import ProgressHandler import portage from portage import eapi_is_supported from portage import os from portage.cache.cache_errors import CacheError from portage.eclass_cache import hashed_path from portage.util import writemsg_level 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.\n" f"!!! This is ALMOST CERTAINLY NOT what you want: '{cachedir}'", ), file=sys.stderr, ) sys.exit(73) if not os.path.exists(cachedir): os.makedirs(cachedir) auxdbkeys = portdb._known_keys class TreeData: __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)) 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 = (tree_data.path for tree_data in porttrees_data) 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 = f"_{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. cache_is_identical = ( True for k in auxdbkeys if dest.get(k, "") != src.get(k, "") ) if any(cache_is_identical): dest = None 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 " f"'{tree_data.path}': {e}, continuing...\n" ), 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()