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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
|
from __future__ import annotations
from .interpreterobjects import extract_required_kwarg
from .. import mlog
from .. import dependencies
from .. import build
from ..wrap import WrapMode
from ..mesonlib import OptionKey, extract_as_list, stringlistify, version_compare_many, listify
from ..dependencies import Dependency, DependencyException, NotFoundDependency
from ..interpreterbase import (MesonInterpreterObject, FeatureNew,
InterpreterException, InvalidArguments)
import typing as T
if T.TYPE_CHECKING:
from .interpreter import Interpreter
from ..interpreterbase import TYPE_nkwargs, TYPE_nvar
from .interpreterobjects import SubprojectHolder
class DependencyFallbacksHolder(MesonInterpreterObject):
def __init__(self, interpreter: 'Interpreter', names: T.List[str], allow_fallback: T.Optional[bool] = None,
default_options: T.Optional[T.List[str]] = None) -> None:
super().__init__(subproject=interpreter.subproject)
self.interpreter = interpreter
self.subproject = interpreter.subproject
self.coredata = interpreter.coredata
self.build = interpreter.build
self.environment = interpreter.environment
self.wrap_resolver = interpreter.environment.wrap_resolver
self.allow_fallback = allow_fallback
self.subproject_name: T.Optional[str] = None
self.subproject_varname: T.Optional[str] = None
self.subproject_kwargs = {'default_options': default_options or []}
self.names: T.List[str] = []
self.forcefallback: bool = False
self.nofallback: bool = False
for name in names:
if not name:
raise InterpreterException('dependency_fallbacks empty name \'\' is not allowed')
if '<' in name or '>' in name or '=' in name:
raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify'
'version\n requirements use the \'version\' keyword argument instead.')
if name in self.names:
raise InterpreterException(f'dependency_fallbacks name {name!r} is duplicated')
self.names.append(name)
self._display_name = self.names[0] if self.names else '(anonymous)'
def set_fallback(self, fbinfo: T.Optional[T.Union[T.List[str], str]]) -> None:
# Legacy: This converts dependency()'s fallback kwargs.
if fbinfo is None:
return
if self.allow_fallback is not None:
raise InvalidArguments('"fallback" and "allow_fallback" arguments are mutually exclusive')
fbinfo = stringlistify(fbinfo)
if len(fbinfo) == 0:
# dependency('foo', fallback: []) is the same as dependency('foo', allow_fallback: false)
self.allow_fallback = False
return
if len(fbinfo) == 1:
FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject)
subp_name, varname = fbinfo[0], None
elif len(fbinfo) == 2:
subp_name, varname = fbinfo
else:
raise InterpreterException('Fallback info must have one or two items.')
self._subproject_impl(subp_name, varname)
def _subproject_impl(self, subp_name: str, varname: str) -> None:
assert self.subproject_name is None
self.subproject_name = subp_name
self.subproject_varname = varname
def _do_dependency_cache(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
name = func_args[0]
cached_dep = self._get_cached_dep(name, kwargs)
if cached_dep:
self._verify_fallback_consistency(cached_dep)
return cached_dep
def _do_dependency(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
# Note that there is no df.dependency() method, this is called for names
# given as positional arguments to dependency_fallbacks(name1, ...).
# We use kwargs from the dependency() function, for things like version,
# module, etc.
name = func_args[0]
self._handle_featurenew_dependencies(name)
dep = dependencies.find_external_dependency(name, self.environment, kwargs)
if dep.found():
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = dependencies.get_dep_identifier(name, kwargs)
self.coredata.deps[for_machine].put(identifier, dep)
return dep
return None
def _do_existing_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
subp_name = func_args[0]
varname = self.subproject_varname
if subp_name and self._get_subproject(subp_name):
return self._get_subproject_dep(subp_name, varname, kwargs)
return None
def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
if self.forcefallback:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(self._display_name), 'because:\nUse of fallback dependencies is forced.')
elif self.nofallback:
mlog.log('Not looking for a fallback subproject for the dependency',
mlog.bold(self._display_name), 'because:\nUse of fallback dependencies is disabled.')
return None
else:
mlog.log('Looking for a fallback subproject for the dependency',
mlog.bold(self._display_name))
# dependency('foo', static: true) should implicitly add
# default_options: ['default_library=static']
static = kwargs.get('static')
default_options = stringlistify(func_kwargs.get('default_options', []))
if static is not None and not any('default_library' in i for i in default_options):
default_library = 'static' if static else 'shared'
opt = f'default_library={default_library}'
mlog.log(f'Building fallback subproject with {opt}')
default_options.append(opt)
func_kwargs['default_options'] = default_options
# Configure the subproject
subp_name = self.subproject_name
varname = self.subproject_varname
func_kwargs.setdefault('version', [])
if 'default_options' in kwargs and isinstance(kwargs['default_options'], str):
func_kwargs['default_options'] = listify(kwargs['default_options'])
self.interpreter.do_subproject(subp_name, 'meson', func_kwargs)
return self._get_subproject_dep(subp_name, varname, kwargs)
def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
sub = self.interpreter.subprojects.get(subp_name)
if sub and sub.found():
return sub
return None
def _get_subproject_dep(self, subp_name: str, varname: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
# Verify the subproject is found
subproject = self._get_subproject(subp_name)
if not subproject:
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subp_name), 'found:', mlog.red('NO'),
mlog.blue('(subproject failed to configure)'))
return None
# The subproject has been configured. If for any reason the dependency
# cannot be found in this subproject we have to return not-found object
# instead of None, because we don't want to continue the lookup on the
# system.
# Check if the subproject overridden at least one of the names we got.
cached_dep = None
for name in self.names:
cached_dep = self._get_cached_dep(name, kwargs)
if cached_dep:
break
# If we have cached_dep we did all the checks and logging already in
# self._get_cached_dep().
if cached_dep:
self._verify_fallback_consistency(cached_dep)
return cached_dep
# Legacy: Use the variable name if provided instead of relying on the
# subproject to override one of our dependency names
if not varname:
# If no variable name is specified, check if the wrap file has one.
# If the wrap file has a variable name, better use it because the
# subproject most probably is not using meson.override_dependency().
for name in self.names:
varname = self.wrap_resolver.get_varname(subp_name, name)
if varname:
break
if not varname:
mlog.warning(f'Subproject {subp_name!r} did not override {self._display_name!r} dependency and no variable name specified')
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return self._notfound_dependency()
var_dep = self._get_subproject_variable(subproject, varname) or self._notfound_dependency()
if not var_dep.found():
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'))
return var_dep
wanted = stringlistify(kwargs.get('version', []))
found = var_dep.get_version()
if not self._check_version(wanted, found):
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted])))
return self._notfound_dependency()
mlog.log('Dependency', mlog.bold(self._display_name), 'from subproject',
mlog.bold(subproject.subdir), 'found:', mlog.green('YES'),
mlog.normal_cyan(found) if found else None)
return var_dep
def _get_cached_dep(self, name: str, kwargs: TYPE_nkwargs) -> T.Optional[Dependency]:
# Unlike other methods, this one returns not-found dependency instead
# of None in the case the dependency is cached as not-found, or if cached
# version does not match. In that case we don't want to continue with
# other candidates.
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = dependencies.get_dep_identifier(name, kwargs)
wanted_vers = stringlistify(kwargs.get('version', []))
override = self.build.dependency_overrides[for_machine].get(identifier)
if override:
info = [mlog.blue('(overridden)' if override.explicit else '(cached)')]
cached_dep = override.dep
# We don't implicitly override not-found dependencies, but user could
# have explicitly called meson.override_dependency() with a not-found
# dep.
if not cached_dep.found():
mlog.log('Dependency', mlog.bold(self._display_name),
'found:', mlog.red('NO'), *info)
return cached_dep
else:
info = [mlog.blue('(cached)')]
cached_dep = self.coredata.deps[for_machine].get(identifier)
if cached_dep:
found_vers = cached_dep.get_version()
if not self._check_version(wanted_vers, found_vers):
if not override:
# We cached this dependency on disk from a previous run,
# but it could got updated on the system in the meantime.
return None
mlog.log('Dependency', mlog.bold(name),
'found:', mlog.red('NO'),
'found', mlog.normal_cyan(found_vers), 'but need:',
mlog.bold(', '.join([f"'{e}'" for e in wanted_vers])),
*info)
return self._notfound_dependency()
if found_vers:
info = [mlog.normal_cyan(found_vers), *info]
mlog.log('Dependency', mlog.bold(self._display_name),
'found:', mlog.green('YES'), *info)
return cached_dep
return None
def _get_subproject_variable(self, subproject: SubprojectHolder, varname: str) -> T.Optional[Dependency]:
try:
var_dep = subproject.get_variable_method([varname], {})
except InvalidArguments:
var_dep = None
if not isinstance(var_dep, Dependency):
mlog.warning(f'Variable {varname!r} in the subproject {subproject.subdir!r} is',
'not found' if var_dep is None else 'not a dependency object')
return None
return var_dep
def _verify_fallback_consistency(self, cached_dep: Dependency) -> None:
subp_name = self.subproject_name
varname = self.subproject_varname
subproject = self._get_subproject(subp_name)
if subproject and varname:
var_dep = self._get_subproject_variable(subproject, varname)
if var_dep and cached_dep.found() and var_dep != cached_dep:
mlog.warning(f'Inconsistency: Subproject has overridden the dependency with another variable than {varname!r}')
def _handle_featurenew_dependencies(self, name: str) -> None:
'Do a feature check on dependencies used by this subproject'
if name == 'mpi':
FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
elif name == 'pcap':
FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
elif name == 'vulkan':
FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
elif name == 'libwmf':
FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
elif name == 'openmp':
FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
def _notfound_dependency(self) -> NotFoundDependency:
return NotFoundDependency(self.names[0] if self.names else '', self.environment)
@staticmethod
def _check_version(wanted: T.List[str], found: str) -> bool:
if not wanted:
return True
return not (found == 'undefined' or not version_compare_many(found, wanted)[0])
def _get_candidates(self) -> T.List[T.Tuple[T.Callable[[TYPE_nkwargs, TYPE_nvar, TYPE_nkwargs], T.Optional[Dependency]], TYPE_nvar, TYPE_nkwargs]]:
candidates = []
# 1. check if any of the names is cached already.
for name in self.names:
candidates.append((self._do_dependency_cache, [name], {}))
# 2. check if the subproject fallback has already been configured.
if self.subproject_name:
candidates.append((self._do_existing_subproject, [self.subproject_name], self.subproject_kwargs))
# 3. check external dependency if we are not forced to use subproject
if not self.forcefallback or not self.subproject_name:
for name in self.names:
candidates.append((self._do_dependency, [name], {}))
# 4. configure the subproject
if self.subproject_name:
candidates.append((self._do_subproject, [self.subproject_name], self.subproject_kwargs))
return candidates
def lookup(self, kwargs: TYPE_nkwargs, force_fallback: bool = False) -> Dependency:
mods = extract_as_list(kwargs, 'modules')
if mods:
self._display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods))
disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
if disabled:
mlog.log('Dependency', mlog.bold(self._display_name), 'skipped: feature', mlog.bold(feature), 'disabled')
return self._notfound_dependency()
# Check if usage of the subproject fallback is forced
wrap_mode = self.coredata.get_option(OptionKey('wrap_mode'))
assert isinstance(wrap_mode, WrapMode), 'for mypy'
force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for'))
assert isinstance(force_fallback_for, list), 'for mypy'
self.nofallback = wrap_mode == WrapMode.nofallback
self.forcefallback = (force_fallback or
wrap_mode == WrapMode.forcefallback or
any(name in force_fallback_for for name in self.names) or
self.subproject_name in force_fallback_for)
# Add an implicit subproject fallback if none has been set explicitly,
# unless implicit fallback is not allowed.
# Legacy: self.allow_fallback can be None when that kwarg is not defined
# in dependency('name'). In that case we don't want to use implicit
# fallback when required is false because user will typically fallback
# manually using cc.find_library() for example.
if not self.subproject_name and self.allow_fallback is not False:
for name in self.names:
subp_name, varname = self.wrap_resolver.find_dep_provider(name)
if subp_name:
self.forcefallback |= subp_name in force_fallback_for
if self.forcefallback or self.allow_fallback is True or required or self._get_subproject(subp_name):
self._subproject_impl(subp_name, varname)
break
candidates = self._get_candidates()
# writing just "dependency('')" is an error, because it can only fail
if not candidates and required:
raise InvalidArguments('Dependency is required but has no candidates.')
# Try all candidates, only the last one is really required.
last = len(candidates) - 1
for i, item in enumerate(candidates):
func, func_args, func_kwargs = item
func_kwargs['required'] = required and (i == last)
kwargs['required'] = required and (i == last)
dep = func(kwargs, func_args, func_kwargs)
if dep and dep.found():
# Override this dependency to have consistent results in subsequent
# dependency lookups.
for name in self.names:
for_machine = self.interpreter.machine_from_native_kwarg(kwargs)
identifier = dependencies.get_dep_identifier(name, kwargs)
if identifier not in self.build.dependency_overrides[for_machine]:
self.build.dependency_overrides[for_machine][identifier] = \
build.DependencyOverride(dep, self.interpreter.current_node, explicit=False)
return dep
elif required and (dep or i == last):
# This was the last candidate or the dependency has been cached
# as not-found, or cached dependency version does not match,
# otherwise func() would have returned None instead.
raise DependencyException(f'Dependency {self._display_name!r} is required but not found.')
elif dep:
# Same as above, but the dependency is not required.
return dep
return self._notfound_dependency()
|