1import os 2import shlex 3import subprocess 4import copy 5import textwrap 6 7from pathlib import Path, PurePath 8 9from .. import mesonlib 10from .. import coredata 11from .. import build 12from .. import mlog 13 14from ..modules import ModuleReturnValue, ModuleObject, ModuleState, ExtensionModule 15from ..backend.backends import TestProtocol 16from ..interpreterbase import ( 17 ContainerTypeInfo, KwargInfo, MesonOperator, 18 InterpreterObject, MesonInterpreterObject, ObjectHolder, MutableInterpreterObject, 19 FeatureCheckBase, FeatureNewKwargs, FeatureNew, FeatureDeprecated, 20 typed_pos_args, typed_kwargs, typed_operator, permittedKwargs, 21 noArgsFlattening, noPosargs, noKwargs, unholder_return, TYPE_var, TYPE_kwargs, TYPE_nvar, TYPE_nkwargs, 22 flatten, resolve_second_level_holders, InterpreterException, InvalidArguments, InvalidCode) 23from ..interpreter.type_checking import NoneType 24from ..dependencies import Dependency, ExternalLibrary, InternalDependency 25from ..programs import ExternalProgram 26from ..mesonlib import HoldableObject, MesonException, OptionKey, listify, Popen_safe 27 28import typing as T 29 30if T.TYPE_CHECKING: 31 from . import kwargs 32 from .interpreter import Interpreter 33 from ..envconfig import MachineInfo 34 35 from typing_extensions import TypedDict 36 37 class EnvironmentSeparatorKW(TypedDict): 38 39 separator: str 40 41 42def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', 43 subproject: str, 44 feature_check: T.Optional[FeatureCheckBase] = None, 45 default: bool = True) -> T.Tuple[bool, bool, T.Optional[str]]: 46 val = kwargs.get('required', default) 47 disabled = False 48 required = False 49 feature: T.Optional[str] = None 50 if isinstance(val, coredata.UserFeatureOption): 51 if not feature_check: 52 feature_check = FeatureNew('User option "feature"', '0.47.0') 53 feature_check.use(subproject) 54 feature = val.name 55 if val.is_disabled(): 56 disabled = True 57 elif val.is_enabled(): 58 required = True 59 elif isinstance(val, bool): 60 required = val 61 else: 62 raise InterpreterException('required keyword argument must be boolean or a feature option') 63 64 # Keep boolean value in kwargs to simplify other places where this kwarg is 65 # checked. 66 # TODO: this should be removed, and those callers should learn about FeatureOptions 67 kwargs['required'] = required 68 69 return disabled, required, feature 70 71def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]: 72 search_dirs_str = mesonlib.stringlistify(kwargs.get('dirs', [])) 73 search_dirs = [Path(d).expanduser() for d in search_dirs_str] 74 for d in search_dirs: 75 if mesonlib.is_windows() and d.root.startswith('\\'): 76 # a Unix-path starting with `/` that is not absolute on Windows. 77 # discard without failing for end-user ease of cross-platform directory arrays 78 continue 79 if not d.is_absolute(): 80 raise InvalidCode(f'Search directory {d} is not an absolute path.') 81 return list(map(str, search_dirs)) 82 83class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): 84 def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'): 85 super().__init__(option, interpreter) 86 if option and option.is_auto(): 87 # TODO: we need to case here because options is not a TypedDict 88 self.held_object = T.cast(coredata.UserFeatureOption, self.env.coredata.options[OptionKey('auto_features')]) 89 self.held_object.name = option.name 90 self.methods.update({'enabled': self.enabled_method, 91 'disabled': self.disabled_method, 92 'allowed': self.allowed_method, 93 'auto': self.auto_method, 94 'require': self.require_method, 95 'disable_auto_if': self.disable_auto_if_method, 96 }) 97 98 @property 99 def value(self) -> str: 100 return 'disabled' if not self.held_object else self.held_object.value 101 102 def as_disabled(self) -> coredata.UserFeatureOption: 103 disabled = copy.deepcopy(self.held_object) 104 disabled.value = 'disabled' 105 return disabled 106 107 @noPosargs 108 @noKwargs 109 def enabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 110 return self.value == 'enabled' 111 112 @noPosargs 113 @noKwargs 114 def disabled_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 115 return self.value == 'disabled' 116 117 @noPosargs 118 @noKwargs 119 def allowed_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 120 return self.value != 'disabled' 121 122 @noPosargs 123 @noKwargs 124 def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 125 return self.value == 'auto' 126 127 @permittedKwargs({'error_message'}) 128 def require_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: 129 if len(args) != 1: 130 raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) 131 if not isinstance(args[0], bool): 132 raise InvalidArguments('boolean argument expected.') 133 error_message = kwargs.pop('error_message', '') 134 if error_message and not isinstance(error_message, str): 135 raise InterpreterException("Error message must be a string.") 136 if args[0]: 137 return copy.deepcopy(self.held_object) 138 139 assert isinstance(error_message, str) 140 if self.value == 'enabled': 141 prefix = f'Feature {self.held_object.name} cannot be enabled' 142 prefix = prefix + ': ' if error_message else '' 143 raise InterpreterException(prefix + error_message) 144 return self.as_disabled() 145 146 @noKwargs 147 def disable_auto_if_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: 148 if len(args) != 1: 149 raise InvalidArguments('Expected 1 argument, got %d.' % (len(args), )) 150 if not isinstance(args[0], bool): 151 raise InvalidArguments('boolean argument expected.') 152 return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() 153 154 155class RunProcess(MesonInterpreterObject): 156 157 def __init__(self, 158 cmd: ExternalProgram, 159 args: T.List[str], 160 env: build.EnvironmentVariables, 161 source_dir: str, 162 build_dir: str, 163 subdir: str, 164 mesonintrospect: T.List[str], 165 in_builddir: bool = False, 166 check: bool = False, 167 capture: bool = True) -> None: 168 super().__init__() 169 if not isinstance(cmd, ExternalProgram): 170 raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') 171 self.capture = capture 172 self.returncode, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) 173 self.methods.update({'returncode': self.returncode_method, 174 'stdout': self.stdout_method, 175 'stderr': self.stderr_method, 176 }) 177 178 def run_command(self, 179 cmd: ExternalProgram, 180 args: T.List[str], 181 env: build.EnvironmentVariables, 182 source_dir: str, 183 build_dir: str, 184 subdir: str, 185 mesonintrospect: T.List[str], 186 in_builddir: bool, 187 check: bool = False) -> T.Tuple[int, str, str]: 188 command_array = cmd.get_command() + args 189 menv = {'MESON_SOURCE_ROOT': source_dir, 190 'MESON_BUILD_ROOT': build_dir, 191 'MESON_SUBDIR': subdir, 192 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]), 193 } 194 if in_builddir: 195 cwd = os.path.join(build_dir, subdir) 196 else: 197 cwd = os.path.join(source_dir, subdir) 198 child_env = os.environ.copy() 199 child_env.update(menv) 200 child_env = env.get_env(child_env) 201 stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL 202 mlog.debug('Running command:', ' '.join(command_array)) 203 try: 204 p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd) 205 if self.capture: 206 mlog.debug('--- stdout ---') 207 mlog.debug(o) 208 else: 209 o = '' 210 mlog.debug('--- stdout disabled ---') 211 mlog.debug('--- stderr ---') 212 mlog.debug(e) 213 mlog.debug('') 214 215 if check and p.returncode != 0: 216 raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode)) 217 218 return p.returncode, o, e 219 except FileNotFoundError: 220 raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) 221 222 @noPosargs 223 @noKwargs 224 def returncode_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> int: 225 return self.returncode 226 227 @noPosargs 228 @noKwargs 229 def stdout_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 230 return self.stdout 231 232 @noPosargs 233 @noKwargs 234 def stderr_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 235 return self.stderr 236 237 238_ENV_SEPARATOR_KW = KwargInfo('separator', str, default=os.pathsep) 239 240 241class EnvironmentVariablesHolder(ObjectHolder[build.EnvironmentVariables], MutableInterpreterObject): 242 243 def __init__(self, obj: build.EnvironmentVariables, interpreter: 'Interpreter'): 244 super().__init__(obj, interpreter) 245 self.methods.update({'set': self.set_method, 246 'append': self.append_method, 247 'prepend': self.prepend_method, 248 }) 249 250 def __repr__(self) -> str: 251 repr_str = "<{0}: {1}>" 252 return repr_str.format(self.__class__.__name__, self.held_object.envvars) 253 254 def __deepcopy__(self, memo: T.Dict[str, object]) -> 'EnvironmentVariablesHolder': 255 # Avoid trying to copy the interpreter 256 return EnvironmentVariablesHolder(copy.deepcopy(self.held_object), self.interpreter) 257 258 def warn_if_has_name(self, name: str) -> None: 259 # Multiple append/prepend operations was not supported until 0.58.0. 260 if self.held_object.has_name(name): 261 m = f'Overriding previous value of environment variable {name!r} with a new one' 262 FeatureNew(m, '0.58.0').use(self.subproject) 263 264 @typed_pos_args('environment.set', str, varargs=str, min_varargs=1) 265 @typed_kwargs('environment.set', _ENV_SEPARATOR_KW) 266 def set_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: 267 name, values = args 268 self.held_object.set(name, values, kwargs['separator']) 269 270 @typed_pos_args('environment.append', str, varargs=str, min_varargs=1) 271 @typed_kwargs('environment.append', _ENV_SEPARATOR_KW) 272 def append_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: 273 name, values = args 274 self.warn_if_has_name(name) 275 self.held_object.append(name, values, kwargs['separator']) 276 277 @typed_pos_args('environment.prepend', str, varargs=str, min_varargs=1) 278 @typed_kwargs('environment.prepend', _ENV_SEPARATOR_KW) 279 def prepend_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'EnvironmentSeparatorKW') -> None: 280 name, values = args 281 self.warn_if_has_name(name) 282 self.held_object.prepend(name, values, kwargs['separator']) 283 284 285class ConfigurationDataObject(MutableInterpreterObject, MesonInterpreterObject): 286 def __init__(self, subproject: str, initial_values: T.Optional[T.Dict[str, T.Any]] = None) -> None: 287 self.used = False # These objects become immutable after use in configure_file. 288 super().__init__(subproject=subproject) 289 self.conf_data = build.ConfigurationData() 290 self.methods.update({'set': self.set_method, 291 'set10': self.set10_method, 292 'set_quoted': self.set_quoted_method, 293 'has': self.has_method, 294 'get': self.get_method, 295 'keys': self.keys_method, 296 'get_unquoted': self.get_unquoted_method, 297 'merge_from': self.merge_from_method, 298 }) 299 if isinstance(initial_values, dict): 300 for k, v in initial_values.items(): 301 self.set_method([k, v], {}) 302 elif initial_values: 303 raise AssertionError('Unsupported ConfigurationDataObject initial_values') 304 305 def is_used(self) -> bool: 306 return self.used 307 308 def mark_used(self) -> None: 309 self.used = True 310 311 def validate_args(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Tuple[str, T.Union[str, int, bool], T.Optional[str]]: 312 if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: 313 mlog.deprecation('Passing a list as the single argument to ' 314 'configuration_data.set is deprecated. This will ' 315 'become a hard error in the future.', 316 location=self.current_node) 317 args = args[0] 318 319 if len(args) != 2: 320 raise InterpreterException("Configuration set requires 2 arguments.") 321 if self.used: 322 raise InterpreterException("Can not set values on configuration object that has been used.") 323 name, val = args 324 if not isinstance(val, (int, str)): 325 msg = f'Setting a configuration data value to {val!r} is invalid, ' \ 326 'and will fail at configure_file(). If you are using it ' \ 327 'just to store some values, please use a dict instead.' 328 mlog.deprecation(msg, location=self.current_node) 329 desc = kwargs.get('description', None) 330 if not isinstance(name, str): 331 raise InterpreterException("First argument to set must be a string.") 332 if desc is not None and not isinstance(desc, str): 333 raise InterpreterException('Description must be a string.') 334 335 # TODO: Remove the cast once we get rid of the deprecation 336 return name, T.cast(T.Union[str, bool, int], val), desc 337 338 @noArgsFlattening 339 def set_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: 340 (name, val, desc) = self.validate_args(args, kwargs) 341 self.conf_data.values[name] = (val, desc) 342 343 def set_quoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: 344 (name, val, desc) = self.validate_args(args, kwargs) 345 if not isinstance(val, str): 346 raise InterpreterException("Second argument to set_quoted must be a string.") 347 escaped_val = '\\"'.join(val.split('"')) 348 self.conf_data.values[name] = ('"' + escaped_val + '"', desc) 349 350 def set10_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: 351 (name, val, desc) = self.validate_args(args, kwargs) 352 if val: 353 self.conf_data.values[name] = (1, desc) 354 else: 355 self.conf_data.values[name] = (0, desc) 356 357 def has_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 358 return args[0] in self.conf_data.values 359 360 @FeatureNew('configuration_data.get()', '0.38.0') 361 @noArgsFlattening 362 def get_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: 363 if len(args) < 1 or len(args) > 2: 364 raise InterpreterException('Get method takes one or two arguments.') 365 if not isinstance(args[0], str): 366 raise InterpreterException('The variable name must be a string.') 367 name = args[0] 368 if name in self.conf_data: 369 return self.conf_data.get(name)[0] 370 if len(args) > 1: 371 # Assertion does not work because setting other values is still 372 # supported, but deprecated. Use T.cast in the meantime (even though 373 # this is a lie). 374 # TODO: Fix this once the deprecation is removed 375 # assert isinstance(args[1], (int, str, bool)) 376 return T.cast(T.Union[str, int, bool], args[1]) 377 raise InterpreterException('Entry %s not in configuration data.' % name) 378 379 @FeatureNew('configuration_data.get_unquoted()', '0.44.0') 380 def get_unquoted_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[str, int, bool]: 381 if len(args) < 1 or len(args) > 2: 382 raise InterpreterException('Get method takes one or two arguments.') 383 if not isinstance(args[0], str): 384 raise InterpreterException('The variable name must be a string.') 385 name = args[0] 386 if name in self.conf_data: 387 val = self.conf_data.get(name)[0] 388 elif len(args) > 1: 389 assert isinstance(args[1], (str, int, bool)) 390 val = args[1] 391 else: 392 raise InterpreterException('Entry %s not in configuration data.' % name) 393 if isinstance(val, str) and val[0] == '"' and val[-1] == '"': 394 return val[1:-1] 395 return val 396 397 def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: 398 return self.conf_data.values[name] 399 400 @FeatureNew('configuration_data.keys()', '0.57.0') 401 @noPosargs 402 def keys_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[str]: 403 return sorted(self.keys()) 404 405 def keys(self) -> T.List[str]: 406 return list(self.conf_data.values.keys()) 407 408 def merge_from_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> None: 409 if len(args) != 1: 410 raise InterpreterException('Merge_from takes one positional argument.') 411 from_object_holder = args[0] 412 if not isinstance(from_object_holder, ConfigurationDataObject): 413 raise InterpreterException('Merge_from argument must be a configuration data object.') 414 from_object = from_object_holder.conf_data 415 for k, v in from_object.values.items(): 416 self.conf_data.values[k] = v 417 418 419_PARTIAL_DEP_KWARGS = [ 420 KwargInfo('compile_args', bool, default=False), 421 KwargInfo('link_args', bool, default=False), 422 KwargInfo('links', bool, default=False), 423 KwargInfo('includes', bool, default=False), 424 KwargInfo('sources', bool, default=False), 425] 426 427class DependencyHolder(ObjectHolder[Dependency]): 428 def __init__(self, dep: Dependency, interpreter: 'Interpreter'): 429 super().__init__(dep, interpreter) 430 self.methods.update({'found': self.found_method, 431 'type_name': self.type_name_method, 432 'version': self.version_method, 433 'name': self.name_method, 434 'get_pkgconfig_variable': self.pkgconfig_method, 435 'get_configtool_variable': self.configtool_method, 436 'get_variable': self.variable_method, 437 'partial_dependency': self.partial_dependency_method, 438 'include_type': self.include_type_method, 439 'as_system': self.as_system_method, 440 'as_link_whole': self.as_link_whole_method, 441 }) 442 443 def found(self) -> bool: 444 return self.found_method([], {}) 445 446 @noPosargs 447 @noKwargs 448 def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 449 return self.held_object.type_name 450 451 @noPosargs 452 @noKwargs 453 def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 454 if self.held_object.type_name == 'internal': 455 return True 456 return self.held_object.found() 457 458 @noPosargs 459 @noKwargs 460 def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 461 return self.held_object.get_version() 462 463 @noPosargs 464 @noKwargs 465 def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 466 return self.held_object.get_name() 467 468 @FeatureDeprecated('Dependency.get_pkgconfig_variable', '0.56.0', 469 'use Dependency.get_variable(pkgconfig : ...) instead') 470 @permittedKwargs({'define_variable', 'default'}) 471 def pkgconfig_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 472 args = listify(args) 473 if len(args) != 1: 474 raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') 475 varname = args[0] 476 if not isinstance(varname, str): 477 raise InterpreterException('Variable name must be a string.') 478 return self.held_object.get_pkgconfig_variable(varname, kwargs) 479 480 @FeatureNew('dep.get_configtool_variable', '0.44.0') 481 @FeatureDeprecated('Dependency.get_configtool_variable', '0.56.0', 482 'use Dependency.get_variable(configtool : ...) instead') 483 @noKwargs 484 def configtool_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 485 args = listify(args) 486 if len(args) != 1: 487 raise InterpreterException('get_configtool_variable takes exactly one argument.') 488 varname = args[0] 489 if not isinstance(varname, str): 490 raise InterpreterException('Variable name must be a string.') 491 return self.held_object.get_configtool_variable(varname) 492 493 @FeatureNew('dep.partial_dependency', '0.46.0') 494 @noPosargs 495 @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS) 496 def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: 497 pdep = self.held_object.get_partial_dependency(**kwargs) 498 return pdep 499 500 @FeatureNew('dep.get_variable', '0.51.0') 501 @typed_pos_args('dep.get_variable', optargs=[str]) 502 @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'}) 503 @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal']) 504 def variable_method(self, args: T.Tuple[T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Union[str, T.List[str]]: 505 default_varname = args[0] 506 if default_varname is not None: 507 FeatureNew('Positional argument to dep.get_variable()', '0.58.0').use(self.subproject) 508 for k in ['cmake', 'pkgconfig', 'configtool', 'internal']: 509 kwargs.setdefault(k, default_varname) 510 return self.held_object.get_variable(**kwargs) 511 512 @FeatureNew('dep.include_type', '0.52.0') 513 @noPosargs 514 @noKwargs 515 def include_type_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 516 return self.held_object.get_include_type() 517 518 @FeatureNew('dep.as_system', '0.52.0') 519 @noKwargs 520 def as_system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: 521 args = listify(args) 522 new_is_system = 'system' 523 if len(args) > 1: 524 raise InterpreterException('as_system takes only one optional value') 525 if len(args) == 1: 526 if not isinstance(args[0], str): 527 raise InterpreterException('as_system takes exactly one string parameter') 528 new_is_system = args[0] 529 new_dep = self.held_object.generate_system_dependency(new_is_system) 530 return new_dep 531 532 @FeatureNew('dep.as_link_whole', '0.56.0') 533 @noKwargs 534 @noPosargs 535 def as_link_whole_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> Dependency: 536 if not isinstance(self.held_object, InternalDependency): 537 raise InterpreterException('as_link_whole method is only supported on declare_dependency() objects') 538 new_dep = self.held_object.generate_link_whole_dependency() 539 return new_dep 540 541class ExternalProgramHolder(ObjectHolder[ExternalProgram]): 542 def __init__(self, ep: ExternalProgram, interpreter: 'Interpreter') -> None: 543 super().__init__(ep, interpreter) 544 self.methods.update({'found': self.found_method, 545 'path': self.path_method, 546 'full_path': self.full_path_method}) 547 548 @noPosargs 549 @noKwargs 550 def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 551 return self.found() 552 553 @noPosargs 554 @noKwargs 555 @FeatureDeprecated('ExternalProgram.path', '0.55.0', 556 'use ExternalProgram.full_path() instead') 557 def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 558 return self._full_path() 559 560 @noPosargs 561 @noKwargs 562 @FeatureNew('ExternalProgram.full_path', '0.55.0') 563 def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 564 return self._full_path() 565 566 def _full_path(self) -> str: 567 if not self.found(): 568 raise InterpreterException('Unable to get the path of a not-found external program') 569 path = self.held_object.get_path() 570 assert path is not None 571 return path 572 573 def found(self) -> bool: 574 return self.held_object.found() 575 576class ExternalLibraryHolder(ObjectHolder[ExternalLibrary]): 577 def __init__(self, el: ExternalLibrary, interpreter: 'Interpreter'): 578 super().__init__(el, interpreter) 579 self.methods.update({'found': self.found_method, 580 'type_name': self.type_name_method, 581 'partial_dependency': self.partial_dependency_method, 582 }) 583 584 @noPosargs 585 @noKwargs 586 def type_name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 587 return self.held_object.type_name 588 589 @noPosargs 590 @noKwargs 591 def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 592 return self.held_object.found() 593 594 @FeatureNew('dep.partial_dependency', '0.46.0') 595 @noPosargs 596 @typed_kwargs('dep.partial_dependency', *_PARTIAL_DEP_KWARGS) 597 def partial_dependency_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.DependencyMethodPartialDependency') -> Dependency: 598 pdep = self.held_object.get_partial_dependency(**kwargs) 599 return pdep 600 601# A machine that's statically known from the cross file 602class MachineHolder(ObjectHolder['MachineInfo']): 603 def __init__(self, machine_info: 'MachineInfo', interpreter: 'Interpreter'): 604 super().__init__(machine_info, interpreter) 605 self.methods.update({'system': self.system_method, 606 'cpu': self.cpu_method, 607 'cpu_family': self.cpu_family_method, 608 'endian': self.endian_method, 609 }) 610 611 @noPosargs 612 @noKwargs 613 def cpu_family_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 614 return self.held_object.cpu_family 615 616 @noPosargs 617 @noKwargs 618 def cpu_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 619 return self.held_object.cpu 620 621 @noPosargs 622 @noKwargs 623 def system_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 624 return self.held_object.system 625 626 @noPosargs 627 @noKwargs 628 def endian_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 629 return self.held_object.endian 630 631class IncludeDirsHolder(ObjectHolder[build.IncludeDirs]): 632 pass 633 634class FileHolder(ObjectHolder[mesonlib.File]): 635 pass 636 637class HeadersHolder(ObjectHolder[build.Headers]): 638 pass 639 640class DataHolder(ObjectHolder[build.Data]): 641 pass 642 643class InstallDirHolder(ObjectHolder[build.InstallDir]): 644 pass 645 646class ManHolder(ObjectHolder[build.Man]): 647 pass 648 649class EmptyDirHolder(ObjectHolder[build.EmptyDir]): 650 pass 651 652class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]): 653 pass 654 655class Test(MesonInterpreterObject): 656 def __init__(self, name: str, project: str, suite: T.List[str], 657 exe: T.Union[ExternalProgram, build.Executable, build.CustomTarget], 658 depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], 659 is_parallel: bool, 660 cmd_args: T.List[T.Union[str, mesonlib.File, build.Target]], 661 env: build.EnvironmentVariables, 662 should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, 663 priority: int): 664 super().__init__() 665 self.name = name 666 self.suite = listify(suite) 667 self.project_name = project 668 self.exe = exe 669 self.depends = depends 670 self.is_parallel = is_parallel 671 self.cmd_args = cmd_args 672 self.env = env 673 self.should_fail = should_fail 674 self.timeout = timeout 675 self.workdir = workdir 676 self.protocol = TestProtocol.from_str(protocol) 677 self.priority = priority 678 679 def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarget]: 680 return self.exe 681 682 def get_name(self) -> str: 683 return self.name 684 685class NullSubprojectInterpreter(HoldableObject): 686 pass 687 688# TODO: This should really be an `ObjectHolder`, but the additional stuff in this 689# class prevents this. Thus, this class should be split into a pure 690# `ObjectHolder` and a class specifically for storing in `Interpreter`. 691class SubprojectHolder(MesonInterpreterObject): 692 693 def __init__(self, subinterpreter: T.Union['Interpreter', NullSubprojectInterpreter], 694 subdir: str, 695 warnings: int = 0, 696 disabled_feature: T.Optional[str] = None, 697 exception: T.Optional[MesonException] = None) -> None: 698 super().__init__() 699 self.held_object = subinterpreter 700 self.warnings = warnings 701 self.disabled_feature = disabled_feature 702 self.exception = exception 703 self.subdir = PurePath(subdir).as_posix() 704 self.methods.update({'get_variable': self.get_variable_method, 705 'found': self.found_method, 706 }) 707 708 @noPosargs 709 @noKwargs 710 def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 711 return self.found() 712 713 def found(self) -> bool: 714 return not isinstance(self.held_object, NullSubprojectInterpreter) 715 716 @noKwargs 717 @noArgsFlattening 718 @unholder_return 719 def get_variable_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.Union[TYPE_var, InterpreterObject]: 720 if len(args) < 1 or len(args) > 2: 721 raise InterpreterException('Get_variable takes one or two arguments.') 722 if isinstance(self.held_object, NullSubprojectInterpreter): # == not self.found() 723 raise InterpreterException('Subproject "%s" disabled can\'t get_variable on it.' % (self.subdir)) 724 varname = args[0] 725 if not isinstance(varname, str): 726 raise InterpreterException('Get_variable first argument must be a string.') 727 try: 728 return self.held_object.variables[varname] 729 except KeyError: 730 pass 731 732 if len(args) == 2: 733 return self.held_object._holderify(args[1]) 734 735 raise InvalidArguments(f'Requested variable "{varname}" not found.') 736 737class ModuleObjectHolder(ObjectHolder[ModuleObject]): 738 def method_call(self, method_name: str, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> TYPE_var: 739 modobj = self.held_object 740 method = modobj.methods.get(method_name) 741 if not method: 742 raise InvalidCode(f'Unknown method {method_name!r} in object.') 743 if not getattr(method, 'no-args-flattening', False): 744 args = flatten(args) 745 if not getattr(method, 'no-second-level-holder-flattening', False): 746 args, kwargs = resolve_second_level_holders(args, kwargs) 747 state = ModuleState(self.interpreter) 748 # Many modules do for example self.interpreter.find_program_impl(), 749 # so we have to ensure they use the current interpreter and not the one 750 # that first imported that module, otherwise it will use outdated 751 # overrides. 752 if isinstance(modobj, ExtensionModule): 753 modobj.interpreter = self.interpreter 754 ret = method(state, args, kwargs) 755 if isinstance(ret, ModuleReturnValue): 756 self.interpreter.process_new_values(ret.new_objects) 757 ret = ret.return_value 758 return ret 759 760class MutableModuleObjectHolder(ModuleObjectHolder, MutableInterpreterObject): 761 def __deepcopy__(self, memo: T.Dict[int, T.Any]) -> 'MutableModuleObjectHolder': 762 # Deepcopy only held object, not interpreter 763 modobj = copy.deepcopy(self.held_object, memo) 764 return MutableModuleObjectHolder(modobj, self.interpreter) 765 766 767_BuildTarget = T.TypeVar('_BuildTarget', bound=T.Union[build.BuildTarget, build.BothLibraries]) 768 769class BuildTargetHolder(ObjectHolder[_BuildTarget]): 770 def __init__(self, target: _BuildTarget, interp: 'Interpreter'): 771 super().__init__(target, interp) 772 self.methods.update({'extract_objects': self.extract_objects_method, 773 'extract_all_objects': self.extract_all_objects_method, 774 'name': self.name_method, 775 'get_id': self.get_id_method, 776 'outdir': self.outdir_method, 777 'full_path': self.full_path_method, 778 'path': self.path_method, 779 'found': self.found_method, 780 'private_dir_include': self.private_dir_include_method, 781 }) 782 783 def __repr__(self) -> str: 784 r = '<{} {}: {}>' 785 h = self.held_object 786 assert isinstance(h, build.BuildTarget) 787 return r.format(self.__class__.__name__, h.get_id(), h.filename) 788 789 @property 790 def _target_object(self) -> build.BuildTarget: 791 if isinstance(self.held_object, build.BothLibraries): 792 return self.held_object.get_default_object() 793 assert isinstance(self.held_object, build.BuildTarget) 794 return self.held_object 795 796 def is_cross(self) -> bool: 797 return not self._target_object.environment.machines.matches_build_machine(self._target_object.for_machine) 798 799 @noPosargs 800 @noKwargs 801 def found_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: 802 if not (isinstance(self.held_object, build.Executable) and self.held_object.was_returned_by_find_program): 803 FeatureNew.single_use('BuildTarget.found', '0.59.0', subproject=self.held_object.subproject) 804 return True 805 806 @noPosargs 807 @noKwargs 808 def private_dir_include_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.IncludeDirs: 809 return build.IncludeDirs('', [], False, [self.interpreter.backend.get_target_private_dir(self._target_object)]) 810 811 @noPosargs 812 @noKwargs 813 def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 814 return self.interpreter.backend.get_target_filename_abs(self._target_object) 815 816 @noPosargs 817 @noKwargs 818 @FeatureDeprecated('BuildTarget.path', '0.55.0', 'Use BuildTarget.full_path instead') 819 def path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 820 return self.interpreter.backend.get_target_filename_abs(self._target_object) 821 822 @noPosargs 823 @noKwargs 824 def outdir_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 825 return self.interpreter.backend.get_target_dir(self._target_object) 826 827 @noKwargs 828 @typed_pos_args('extract_objects', varargs=(mesonlib.File, str)) 829 def extract_objects_method(self, args: T.Tuple[T.List[mesonlib.FileOrString]], kwargs: TYPE_nkwargs) -> build.ExtractedObjects: 830 return self._target_object.extract_objects(args[0]) 831 832 @noPosargs 833 @typed_kwargs( 834 'extract_all_objects', 835 KwargInfo( 836 'recursive', bool, default=False, since='0.46.0', 837 not_set_warning=textwrap.dedent('''\ 838 extract_all_objects called without setting recursive 839 keyword argument. Meson currently defaults to 840 non-recursive to maintain backward compatibility but 841 the default will be changed in the future. 842 ''') 843 ) 844 ) 845 def extract_all_objects_method(self, args: T.List[TYPE_nvar], kwargs: 'kwargs.BuildTargeMethodExtractAllObjects') -> build.ExtractedObjects: 846 return self._target_object.extract_all_objects(kwargs['recursive']) 847 848 @noPosargs 849 @noKwargs 850 def get_id_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 851 return self._target_object.get_id() 852 853 @FeatureNew('name', '0.54.0') 854 @noPosargs 855 @noKwargs 856 def name_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 857 return self._target_object.name 858 859class ExecutableHolder(BuildTargetHolder[build.Executable]): 860 pass 861 862class StaticLibraryHolder(BuildTargetHolder[build.StaticLibrary]): 863 pass 864 865class SharedLibraryHolder(BuildTargetHolder[build.SharedLibrary]): 866 pass 867 868class BothLibrariesHolder(BuildTargetHolder[build.BothLibraries]): 869 def __init__(self, libs: build.BothLibraries, interp: 'Interpreter'): 870 # FIXME: This build target always represents the shared library, but 871 # that should be configurable. 872 super().__init__(libs, interp) 873 self.methods.update({'get_shared_lib': self.get_shared_lib_method, 874 'get_static_lib': self.get_static_lib_method, 875 }) 876 877 def __repr__(self) -> str: 878 r = '<{} {}: {}, {}: {}>' 879 h1 = self.held_object.shared 880 h2 = self.held_object.static 881 return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) 882 883 @noPosargs 884 @noKwargs 885 def get_shared_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.SharedLibrary: 886 return self.held_object.shared 887 888 @noPosargs 889 @noKwargs 890 def get_static_lib_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> build.StaticLibrary: 891 return self.held_object.static 892 893class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): 894 pass 895 896class JarHolder(BuildTargetHolder[build.Jar]): 897 pass 898 899class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]): 900 def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): 901 super().__init__(target, interp) 902 self.methods.update({'full_path': self.full_path_method, 903 }) 904 905 @FeatureNew('custom_target[i].full_path', '0.54.0') 906 @noPosargs 907 @noKwargs 908 def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 909 assert self.interpreter.backend is not None 910 return self.interpreter.backend.get_target_filename_abs(self.held_object) 911 912class CustomTargetHolder(ObjectHolder[build.CustomTarget]): 913 def __init__(self, target: 'build.CustomTarget', interp: 'Interpreter'): 914 super().__init__(target, interp) 915 self.methods.update({'full_path': self.full_path_method, 916 'to_list': self.to_list_method, 917 }) 918 919 self.operators.update({ 920 MesonOperator.INDEX: self.op_index, 921 }) 922 923 def __repr__(self) -> str: 924 r = '<{} {}: {}>' 925 h = self.held_object 926 return r.format(self.__class__.__name__, h.get_id(), h.command) 927 928 @noPosargs 929 @noKwargs 930 def full_path_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: 931 return self.interpreter.backend.get_target_filename_abs(self.held_object) 932 933 @FeatureNew('custom_target.to_list', '0.54.0') 934 @noPosargs 935 @noKwargs 936 def to_list_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> T.List[build.CustomTargetIndex]: 937 result = [] 938 for i in self.held_object: 939 result.append(i) 940 return result 941 942 @noKwargs 943 @typed_operator(MesonOperator.INDEX, int) 944 def op_index(self, other: int) -> build.CustomTargetIndex: 945 try: 946 return self.held_object[other] 947 except IndexError: 948 raise InvalidArguments(f'Index {other} out of bounds of custom target {self.held_object.name} output of size {len(self.held_object)}.') 949 950class RunTargetHolder(ObjectHolder[build.RunTarget]): 951 pass 952 953class AliasTargetHolder(ObjectHolder[build.AliasTarget]): 954 pass 955 956class GeneratedListHolder(ObjectHolder[build.GeneratedList]): 957 pass 958 959class GeneratorHolder(ObjectHolder[build.Generator]): 960 def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): 961 super().__init__(gen, interpreter) 962 self.methods.update({'process': self.process_method}) 963 964 @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) 965 @typed_kwargs( 966 'generator.process', 967 KwargInfo('preserve_path_from', (str, NoneType), since='0.45.0'), 968 KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), 969 ) 970 def process_method(self, 971 args: T.Tuple[T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]], 972 kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList: 973 preserve_path_from = kwargs['preserve_path_from'] 974 if preserve_path_from is not None: 975 preserve_path_from = os.path.normpath(preserve_path_from) 976 if not os.path.isabs(preserve_path_from): 977 # This is a bit of a hack. Fix properly before merging. 978 raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') 979 980 if any(isinstance(a, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) for a in args[0]): 981 FeatureNew.single_use( 982 'Calling generator.process with CustomTaget or Index of CustomTarget.', 983 '0.57.0', self.interpreter.subproject) 984 985 gl = self.held_object.process_files(args[0], self.interpreter, 986 preserve_path_from, extra_args=kwargs['extra_args']) 987 988 return gl 989