1# Copyright 2013-2021 The Meson development team
2# Copyright © 2021 Intel Corporation
3
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7
8#     http://www.apache.org/licenses/LICENSE-2.0
9
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import functools
17import typing as T
18
19from ..mesonlib import MachineChoice
20from .base import DependencyException, DependencyMethods
21from .base import ExternalDependency
22from .base import process_method_kw
23from .base import BuiltinDependency, SystemDependency
24from .cmake import CMakeDependency
25from .framework import ExtraFrameworkDependency
26from .pkgconfig import PkgConfigDependency
27
28if T.TYPE_CHECKING:
29    from ..environment import Environment
30    from .configtool import ConfigToolDependency
31
32    DependencyGenerator = T.Callable[[], ExternalDependency]
33    FactoryFunc = T.Callable[
34        [
35            'Environment',
36            MachineChoice,
37            T.Dict[str, T.Any],
38            T.List[DependencyMethods]
39        ],
40        T.List[DependencyGenerator]
41    ]
42
43    WrappedFactoryFunc = T.Callable[
44        [
45            'Environment',
46            MachineChoice,
47            T.Dict[str, T.Any]
48        ],
49        T.List[DependencyGenerator]
50    ]
51
52class DependencyFactory:
53
54    """Factory to get dependencies from multiple sources.
55
56    This class provides an initializer that takes a set of names and classes
57    for various kinds of dependencies. When the initialized object is called
58    it returns a list of callables return Dependency objects to try in order.
59
60    :name: The name of the dependency. This will be passed as the name
61        parameter of the each dependency unless it is overridden on a per
62        type basis.
63    :methods: An ordered list of DependencyMethods. This is the order
64        dependencies will be returned in unless they are removed by the
65        _process_method function
66    :*_name: This will overwrite the name passed to the coresponding class.
67        For example, if the name is 'zlib', but cmake calls the dependency
68        'Z', then using `cmake_name='Z'` will pass the name as 'Z' to cmake.
69    :*_class: A *type* or callable that creates a class, and has the
70        signature of an ExternalDependency
71    :system_class: If you pass DependencyMethods.SYSTEM in methods, you must
72        set this argument.
73    """
74
75    def __init__(self, name: str, methods: T.List[DependencyMethods], *,
76                 extra_kwargs: T.Optional[T.Dict[str, T.Any]] = None,
77                 pkgconfig_name: T.Optional[str] = None,
78                 pkgconfig_class: 'T.Type[PkgConfigDependency]' = PkgConfigDependency,
79                 cmake_name: T.Optional[str] = None,
80                 cmake_class: 'T.Type[CMakeDependency]' = CMakeDependency,
81                 configtool_class: 'T.Optional[T.Type[ConfigToolDependency]]' = None,
82                 framework_name: T.Optional[str] = None,
83                 framework_class: 'T.Type[ExtraFrameworkDependency]' = ExtraFrameworkDependency,
84                 builtin_class: 'T.Type[BuiltinDependency]' = BuiltinDependency,
85                 system_class: 'T.Type[SystemDependency]' = SystemDependency):
86
87        if DependencyMethods.CONFIG_TOOL in methods and not configtool_class:
88            raise DependencyException('A configtool must have a custom class')
89
90        self.extra_kwargs = extra_kwargs or {}
91        self.methods = methods
92        self.classes: T.Dict[
93            DependencyMethods,
94            T.Callable[['Environment', T.Dict[str, T.Any]], ExternalDependency]
95        ] = {
96            # Just attach the correct name right now, either the generic name
97            # or the method specific name.
98            DependencyMethods.EXTRAFRAMEWORK: lambda env, kwargs: framework_class(framework_name or name, env, kwargs),
99            DependencyMethods.PKGCONFIG:      lambda env, kwargs: pkgconfig_class(pkgconfig_name or name, env, kwargs),
100            DependencyMethods.CMAKE:          lambda env, kwargs: cmake_class(cmake_name or name, env, kwargs),
101            DependencyMethods.SYSTEM:         lambda env, kwargs: system_class(name, env, kwargs),
102            DependencyMethods.BUILTIN:        lambda env, kwargs: builtin_class(name, env, kwargs),
103            DependencyMethods.CONFIG_TOOL:    None,
104        }
105        if configtool_class is not None:
106            self.classes[DependencyMethods.CONFIG_TOOL] = lambda env, kwargs: configtool_class(name, env, kwargs)
107
108    @staticmethod
109    def _process_method(method: DependencyMethods, env: 'Environment', for_machine: MachineChoice) -> bool:
110        """Report whether a method is valid or not.
111
112        If the method is valid, return true, otherwise return false. This is
113        used in a list comprehension to filter methods that are not possible.
114
115        By default this only remove EXTRAFRAMEWORK dependencies for non-mac platforms.
116        """
117        # Extra frameworks are only valid for macOS and other apple products
118        if (method is DependencyMethods.EXTRAFRAMEWORK and
119                not env.machines[for_machine].is_darwin()):
120            return False
121        return True
122
123    def __call__(self, env: 'Environment', for_machine: MachineChoice,
124                 kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']:
125        """Return a list of Dependencies with the arguments already attached."""
126        methods = process_method_kw(self.methods, kwargs)
127        nwargs = self.extra_kwargs.copy()
128        nwargs.update(kwargs)
129
130        return [functools.partial(self.classes[m], env, nwargs) for m in methods
131                if self._process_method(m, env, for_machine)]
132
133
134def factory_methods(methods: T.Set[DependencyMethods]) -> T.Callable[['FactoryFunc'], 'WrappedFactoryFunc']:
135    """Decorator for handling methods for dependency factory functions.
136
137    This helps to make factory functions self documenting
138    >>> @factory_methods([DependencyMethods.PKGCONFIG, DependencyMethods.CMAKE])
139    >>> def factory(env: Environment, for_machine: MachineChoice, kwargs: T.Dict[str, T.Any], methods: T.List[DependencyMethods]) -> T.List['DependencyGenerator']:
140    >>>     pass
141    """
142
143    def inner(func: 'FactoryFunc') -> 'WrappedFactoryFunc':
144
145        @functools.wraps(func)
146        def wrapped(env: 'Environment', for_machine: MachineChoice, kwargs: T.Dict[str, T.Any]) -> T.List['DependencyGenerator']:
147            return func(env, for_machine, kwargs, process_method_kw(methods, kwargs))
148
149        return wrapped
150
151    return inner
152