aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/dependencies/factory.py
blob: d50ce0f6b25332cce0ddd8fe6cc33cf1c4feb4f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 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.
from __future__ import annotations

import functools
import typing as T

from .base import DependencyException, DependencyMethods
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 .base import ExternalDependency
    from .configtool import ConfigToolDependency
    from ..environment import Environment
    from ..mesonlib import MachineChoice

    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]
    ]

    # This should be str, Environment, T.Dict[str, T.Any], T.Optional[str]
    # But if you try that, you get error: Cannot infer type of lambda
    CmakeDependencyFunc = T.Callable[..., CMakeDependency]

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 corresponding 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.Union[T.Type[CMakeDependency], CmakeDependencyFunc]' = 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: functools.partial(framework_class, framework_name or name),
            DependencyMethods.PKGCONFIG: functools.partial(pkgconfig_class, pkgconfig_name or name),
            DependencyMethods.CMAKE: functools.partial(cmake_class, cmake_name or name),
            DependencyMethods.SYSTEM: functools.partial(system_class, name),
            DependencyMethods.BUILTIN: functools.partial(builtin_class, name),
            DependencyMethods.CONFIG_TOOL: None,
        }
        if configtool_class is not None:
            self.classes[DependencyMethods.CONFIG_TOOL] = functools.partial(configtool_class, name)

    @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