coredata: Introduce a class to act as the dependency cache

Handling the PKG_CONFIG_PATH variable in meson introduces a new problem
for caching dependencies. We want to encode the pkg_config_path (or
cross_pkg_config_path if we're cross compiling) to be part of the key,
but we don't want to put that into the key for non-pkg-config
dependencies to avoid spurious cache misses (since pkg_config_path isn't
relevant to cmake, for example). However, on a cache lookup we can't
know that a dependency is a pkg-config dependency until we've looked in
the cache.

My solution is a two layer cache, the first layer remains the same as
before, the second layer is a dict-like object that encapsulates the
dependency type information and uses pkg_config_path and
cross_pkg_config_path as a sub key (and could be extended easily for
other types). A new object type is introduced to encapsulate this so
that callers don't need to be aware of the implementation details.
pull/5276/head
Dylan Baker 6 years ago
parent d941ca1b19
commit 5df6823bd8
  1. 111
      mesonbuild/coredata.py
  2. 2
      mesonbuild/environment.py

@ -27,6 +27,11 @@ import ast
import argparse import argparse
import configparser import configparser
from typing import Optional, Any, TypeVar, Generic, Type, List, Union from typing import Optional, Any, TypeVar, Generic, Type, List, Union
import typing
import enum
if typing.TYPE_CHECKING:
from . import dependencies
version = '0.50.999' version = '0.50.999'
backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode'] backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode']
@ -221,6 +226,112 @@ def load_configs(filenames: List[str]) -> configparser.ConfigParser:
return config return config
if typing.TYPE_CHECKING:
CacheKeyType = typing.Tuple[typing.Tuple[typing.Any, ...], ...]
SubCacheKeyType = typing.Tuple[typing.Any, ...]
class DependencyCacheType(enum.Enum):
OTHER = 0
PKG_CONFIG = 1
@classmethod
def from_type(cls, dep: 'dependencies.Dependency') -> 'DependencyCacheType':
from . import dependencies
# As more types gain search overrides they'll need to be added here
if isinstance(dep, dependencies.PkgConfigDependency):
return cls.PKG_CONFIG
return cls.OTHER
class DependencySubCache:
def __init__(self, type_: DependencyCacheType):
self.types = [type_]
self.__cache = {} # type: typing.Dict[SubCacheKeyType, dependencies.Dependency]
def __getitem__(self, key: 'SubCacheKeyType') -> 'dependencies.Dependency':
return self.__cache[key]
def __setitem__(self, key: 'SubCacheKeyType', value: 'dependencies.Dependency') -> None:
self.__cache[key] = value
def __contains__(self, key: 'SubCacheKeyType') -> bool:
return key in self.__cache
def values(self) -> typing.Iterable['dependencies.Dependency']:
return self.__cache.values()
class DependencyCache:
"""Class that stores a cache of dependencies.
This class is meant to encapsulate the fact that we need multiple keys to
successfully lookup by providing a simple get/put interface.
"""
def __init__(self, builtins: typing.Dict[str, UserOption[typing.Any]], cross: bool):
self.__cache = OrderedDict() # type: typing.MutableMapping[CacheKeyType, DependencySubCache]
self.__builtins = builtins
self.__is_cross = cross
def __calculate_subkey(self, type_: DependencyCacheType) -> typing.Tuple[typing.Any, ...]:
if type_ is DependencyCacheType.PKG_CONFIG:
if self.__is_cross:
return tuple(self.__builtins['cross_pkg_config_path'].value)
return tuple(self.__builtins['pkg_config_path'].value)
assert type_ is DependencyCacheType.OTHER, 'Someone forgot to update subkey calculations for a new type'
return tuple()
def __iter__(self) -> typing.Iterator['CacheKeyType']:
return self.keys()
def put(self, key: 'CacheKeyType', dep: 'dependencies.Dependency') -> None:
t = DependencyCacheType.from_type(dep)
if key not in self.__cache:
self.__cache[key] = DependencySubCache(t)
subkey = self.__calculate_subkey(t)
self.__cache[key][subkey] = dep
def get(self, key: 'CacheKeyType') -> typing.Optional['dependencies.Dependency']:
"""Get a value from the cache.
If there is no cache entry then None will be returned.
"""
try:
val = self.__cache[key]
except KeyError:
return None
for t in val.types:
subkey = self.__calculate_subkey(t)
try:
return val[subkey]
except KeyError:
pass
return None
def values(self) -> typing.Iterator['dependencies.Dependency']:
for c in self.__cache.values():
yield from c.values()
def keys(self) -> typing.Iterator['CacheKeyType']:
return iter(self.__cache.keys())
def items(self) -> typing.Iterator[typing.Tuple['CacheKeyType', typing.List['dependencies.Dependency']]]:
for k, v in self.__cache.items():
vs = []
for t in v.types:
subkey = self.__calculate_subkey(t)
if subkey in v:
vs.append(v[subkey])
yield k, vs
def clear(self) -> None:
self.__cache.clear()
# This class contains all data that must persist over multiple # This class contains all data that must persist over multiple
# invocations of Meson. It is roughly the same thing as # invocations of Meson. It is roughly the same thing as
# cmakecache. # cmakecache.

@ -535,7 +535,7 @@ class Environment:
self.coredata.meson_command = mesonlib.meson_command self.coredata.meson_command = mesonlib.meson_command
self.first_invocation = True self.first_invocation = True
def is_cross_build(self): def is_cross_build(self) -> bool:
return not self.machines.matches_build_machine(MachineChoice.HOST) return not self.machines.matches_build_machine(MachineChoice.HOST)
def dump_coredata(self): def dump_coredata(self):

Loading…
Cancel
Save