Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/python/mach/mach/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 7 kB image not shown  

Quelle  func_cache.py

  Sprache: Python
 

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import functools
import hashlib
import inspect
import os
import sys
from pathlib import Path

from mozfile import json

from mach.util import get_state_dir

_topsrcdir = Path(__file__).resolve().parent.parent.parent.parent
_cache_dir = Path(get_state_dir()) / "mach_func_cache"
_debug = "MACH_FUNC_CACHE_DEBUG" in os.environ
_INPUTS_FILENAME = "_dynamic_inputs.json"


def _log(msg):
    if _debug:
        print(f"mach_func_cache> {msg}", file=sys.stderr)


def _derive_cache_name(fn):
    """Derive a human-readable cache directory name from a function's
    file path (relative to topsrcdir) and qualified name.

    For example, a function ``toolchain_task_definitions`` in
    ``python/mozbuild/mozbuild/toolchains.py`` produces:
    ``python-mozbuild-mozbuild-toolchains.py-toolchain_task_definitions``
    """
    fn_file = Path(inspect.getfile(fn)).resolve()
    try:
        rel = fn_file.relative_to(_topsrcdir)
    except ValueError:
        rel = fn_file
    return "-".join(list(rel.parts) + [fn.__qualname__])


def _is_glob_pattern(path):
    return "*" in path or "?" in path


def _resolve_inputs(inputs):
    """Resolve input paths to individual files, expanding globs and
    recursing into directories.
    """
    result = set()
    for input_path in sorted(inputs):
        if _is_glob_pattern(input_path):
            for filepath in _topsrcdir.glob(input_path):
                if filepath.is_file() and not filepath.name.endswith((
                    ".pyc",
                    ".pyd",
                    ".pyo",
                )):
                    result.add(filepath)
            continue

        full_path = _topsrcdir / input_path
        if full_path.is_dir():
            for root, _dirs, files in os.walk(full_path):
                for f in files:
                    if f.endswith((".pyc"".pyd"".pyo")):
                        continue
                    result.add(Path(root) / f)
        elif full_path.is_file():
            result.add(full_path)
    return sorted(result)


def _hash_inputs(inputs, env_vars=None, python_version=False, arg_key=None):
    """Compute a SHA-256 hash over the contents of input files/directories,
    plus optional environment variables, Python version, and argument key.

    Args:
        inputs: List of file or directory paths relative to topsrcdir.
            Paths may contain glob patterns (``*`` or ``?``), which will
            be expanded against topsrcdir.
        env_vars: Optional list of environment variable names whose values
            should be included in the hash.
        python_version: If True, include sys.version in the hash.
        arg_key: Optional JSON-serialized argument string to include in
            the hash.
    """
    h = hashlib.sha256()

    for filepath in _resolve_inputs(inputs):
        rel = filepath.relative_to(_topsrcdir)
        h.update(str(rel).encode())
        with open(filepath, "rb"as fh:
            h.update(fh.read())

    if env_vars:
        for var in sorted(env_vars):
            val = os.environ.get(var, "")
            h.update(f"env\0{var}\0{val}".encode())

    if python_version:
        h.update(f"python\0{sys.version}".encode())

    if arg_key:
        h.update(f"args\0{arg_key}".encode())

    return h.hexdigest()


def _prune_cache_dir(cache_dir, max_entries):
    """Remove the oldest entries beyond max_entries, based on mtime."""
    entries = sorted(
        (p for p in cache_dir.iterdir() if p.name != _INPUTS_FILENAME),
        key=lambda p: p.stat().st_mtime,
        reverse=True,
    )
    while len(entries) > max_entries:
        entries.pop().unlink()


def mach_func_cache(
    inputs, dynamic_inputs=None, env_vars=None, python_version=False, max_entries=10
):
    """Decorator that caches a function's return value on disk, keyed by the
    content hash of the specified input files/directories.

    Also caches in-memory for the lifetime of the process, like
    ``functools.cache``.

    The cached result is stored as JSON. Function arguments, if any, must be
    both hashable (for in-memory caching) and JSON-serializable (for disk
    caching), and are included in the cache key. A ``TypeError`` will be
    raised at call time if these requirements are not met.

    Args:
        inputs: List of file or directory paths relative to topsrcdir whose
            contents determine the cache key.
        dynamic_inputs: Optional callable that takes the function's result and
            returns a list of additional file paths (relative to topsrcdir,
            may contain glob patterns) to include in the cache key. These are
            saved to ``_dynamic_inputs.json`` so that subsequent runs can
            include their contents in the hash and skip recomputation when
            none of the inputs have changed.
        env_vars: Optional list of environment variable names to include in
            the cache key.
        python_version: If True, include the Python version in the cache key.
        max_entries: Maximum number of cached results to keep per function.
            Oldest entries are pruned on write. Defaults to 10.
    """

    def decorator(fn):
        if "MACH_NO_FUNC_CACHE" in os.environ:
            return fn

        cache_name = _derive_cache_name(fn)

        @functools.cache
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            cache_dir = _cache_dir / cache_name
            inputs_file = cache_dir / _INPUTS_FILENAME

            arg_key = None
            if args or kwargs:
                try:
                    arg_key = json.dumps((args, kwargs), sort_keys=True)
                except TypeError as e:
                    raise TypeError(
                        "mach_func_cache: arguments must be "
                        f"JSON-serializable, got: {e}"
                    )

            have_dynamic = dynamic_inputs and inputs_file.is_file()
            if not dynamic_inputs or have_dynamic:
                all_inputs = list(inputs)
                if have_dynamic:
                    with open(inputs_file) as f:
                        all_inputs.extend(json.load(f))
                    _log(
                        f"{fn.__qualname__}: loaded "
                        f"{len(all_inputs) - len(inputs)} dynamic inputs"
                    )

                full_hash = _hash_inputs(all_inputs, env_vars, python_version, arg_key)

                cache_file = cache_dir / f"{full_hash}.json"
                if cache_file.is_file():
                    _log(f"{fn.__qualname__}: cache hit ({full_hash[:12]})")
                    os.utime(cache_file)
                    with open(cache_file) as f:
                        return json.load(f)

                _log(f"{fn.__qualname__}: cache miss ({full_hash[:12]})")
            else:
                _log(
                    f"{fn.__qualname__}: no saved dynamic inputs, skipping cache lookup"
                )

            result = fn(*args, **kwargs)

            all_inputs = list(inputs)
            if dynamic_inputs:
                new_extra = dynamic_inputs(result)
                all_inputs.extend(new_extra)
                _log(f"{fn.__qualname__}: extracted {len(new_extra)} dynamic inputs")

            full_hash = _hash_inputs(all_inputs, env_vars, python_version, arg_key)

            cache_dir.mkdir(parents=True, exist_ok=True)
            cache_file = cache_dir / f"{full_hash}.json"
            with open(cache_file, "w"as f:
                json.dump(result, f)
            _prune_cache_dir(cache_dir, max_entries)

            if dynamic_inputs:
                with open(inputs_file, "w"as f:
                    json.dump(new_extra, f)

            _log(f"{fn.__qualname__}: saved result ({full_hash[:12]})")

            return result

        return wrapper

    return decorator

Messung V0.5 in Prozent
C=97 H=83 G=90

¤ Dauer der Verarbeitung: 0.4 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.