# Copyright 2013-2021 The Meson development team # Copyright © 2021 Intel Corporation # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import functools import typing as T from ..mesonlib import MachineChoice from .base import DependencyException, DependencyMethods from .base import ExternalDependency from .base import process_method_kw from .base import BuiltinDependency, SystemDependency from .cmake import CMakeDependency from .framework import ExtraFrameworkDependency from .pkgconfig import PkgConfigDependency if T.TYPE_CHECKING: from ..environment import Environment from .configtool import ConfigToolDependency DependencyGenerator = T.Callable[[], ExternalDependency] FactoryFunc = T.Callable[ [ 'Environment', MachineChoice, T.Dict[str, T.Any], T.List[DependencyMethods] ], T.List[DependencyGenerator] ] WrappedFactoryFunc = T.Callable[ [ 'Environment', MachineChoice, T.Dict[str, T.Any] ], T.List[DependencyGenerator] ] class DependencyFactory: """Factory to get dependencies from multiple sources. This class provides an initializer that takes a set of names and classes for various kinds of dependencies. When the initialized object is called it returns a list of callables return Dependency objects to try in order. :name: The name of the dependency. This will be passed as the name parameter of the each dependency unless it is overridden on a per type basis. :methods: An ordered list of DependencyMethods. This is the order dependencies will be returned in unless they are removed by the _process_method function :*_name: This will overwrite the name passed to the coresponding class. For example, if the name is 'zlib', but cmake calls the dependency 'Z', then using `cmake_name='Z'` will pass the name as 'Z' to cmake. :*_class: A *type* or callable that creates a class, and has the signature of an ExternalDependency :system_class: If you pass DependencyMethods.SYSTEM in methods, you must set this argument. """ def __init__(self, name: str, methods: T.List[DependencyMethods], *, extra_kwargs: T.Optional[T.Dict[str, T.Any]] = None, pkgconfig_name: T.Optional[str] = None, pkgconfig_class: 'T.Type[PkgConfigDependency]' = PkgConfigDependency, cmake_name: T.Optional[str] = None, cmake_class: 'T.Type[CMakeDependency]' = CMakeDependency, configtool_class: 'T.Optional[T.Type[ConfigToolDependency]]' = None, framework_name: T.Optional[str] = None, framework_class: 'T.Type[ExtraFrameworkDependency]' = ExtraFrameworkDependency, builtin_class: 'T.Type[BuiltinDependency]' = BuiltinDependency, system_class: 'T.Type[SystemDependency]' = SystemDependency): if DependencyMethods.CONFIG_TOOL in methods and not configtool_class: raise DependencyException('A configtool must have a custom class') self.extra_kwargs = extra_kwargs or {} self.methods = methods self.classes: T.Dict[ DependencyMethods, T.Callable[['Environment', T.Dict[str, T.Any]], ExternalDependency] ] = { # Just attach the correct name right now, either the generic name # or the method specific name. DependencyMethods.EXTRAFRAMEWORK: lambda env, kwargs: framework_class(framework_name or name, env, kwargs), DependencyMethods.PKGCONFIG: lambda env, kwargs: pkgconfig_class(pkgconfig_name or name, env, kwargs), DependencyMethods.CMAKE: lambda env, kwargs: cmake_class(cmake_name or name, env, kwargs), DependencyMethods.SYSTEM: lambda env, kwargs: system_class(name, env, kwargs), DependencyMethods.BUILTIN: lambda env, kwargs: builtin_class(name, env, kwargs), DependencyMethods.CONFIG_TOOL: None, } if configtool_class is not None: self.classes[DependencyMethods.CONFIG_TOOL] = lambda env, kwargs: configtool_class(name, env, kwargs) @staticmethod def _process_method(method: DependencyMethods, env: 'Environment', for_machine: MachineChoice) -> bool: """Report whether a method is valid or not. If the method is valid, return true, otherwise return false. This is used in a list comprehension to filter methods that are not possible. By default this only remove EXTRAFRAMEWORK dependencies for non-mac platforms. """ # Extra frameworks are only valid for macOS and other apple products if (method is DependencyMethods.EXTRAFRAMEWORK and not env.machines[for_machine].is_darwin()): return False return True def __call__(self, env: 'Environment', for_machine: MachineChoice, kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: """Return a list of Dependencies with the arguments already attached.""" methods = process_method_kw(self.methods, kwargs) nwargs = self.extra_kwargs.copy() nwargs.update(kwargs) return [functools.partial(self.classes[m], env, nwargs) for m in methods if self._process_method(m, env, for_machine)] def factory_methods(methods: T.Set[DependencyMethods]) -> T.Callable[['FactoryFunc'], 'WrappedFactoryFunc']: """Decorator for handling methods for dependency factory functions. This helps to make factory functions self documenting >>> @factory_methods([DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE]) >>> def factory(env: Environment, for_machine: MachineChoice, kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']: >>> pass """ def inner(func: 'FactoryFunc') -> 'WrappedFactoryFunc': @functools.wraps(func) def wrapped(env: 'Environment', for_machine: MachineChoice, kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']: return func(env, for_machine, kwargs, process_method_kw(methods, kwargs)) return wrapped return inner