1# SPDX-License-Identifier: Apache-2.0 2# Copyright 2012-2021 The Meson development team 3# Copyright © 2021 Intel Corporation 4 5import os 6import typing as T 7 8from .. import mesonlib 9from .. import dependencies 10from .. import build 11from .. import mlog 12 13from ..mesonlib import MachineChoice, OptionKey 14from ..programs import OverrideProgram, ExternalProgram 15from ..interpreter.type_checking import ENV_KW 16from ..interpreterbase import (MesonInterpreterObject, FeatureNew, FeatureDeprecated, 17 typed_pos_args, noArgsFlattening, noPosargs, noKwargs, 18 typed_kwargs, KwargInfo, InterpreterException) 19from .primitives import MesonVersionString 20from .type_checking import NATIVE_KW, NoneType 21 22if T.TYPE_CHECKING: 23 from ..backend.backends import ExecutableSerialisation 24 from ..compilers import Compiler 25 from ..interpreterbase import TYPE_kwargs, TYPE_var 26 from .interpreter import Interpreter 27 28 from typing_extensions import TypedDict 29 30 class FuncOverrideDependency(TypedDict): 31 32 native: mesonlib.MachineChoice 33 static: T.Optional[bool] 34 35 class AddInstallScriptKW(TypedDict): 36 37 skip_if_destdir: bool 38 install_tag: str 39 40 class NativeKW(TypedDict): 41 42 native: mesonlib.MachineChoice 43 44 45class MesonMain(MesonInterpreterObject): 46 def __init__(self, build: 'build.Build', interpreter: 'Interpreter'): 47 super().__init__(subproject=interpreter.subproject) 48 self.build = build 49 self.interpreter = interpreter 50 self.methods.update({'get_compiler': self.get_compiler_method, 51 'is_cross_build': self.is_cross_build_method, 52 'has_exe_wrapper': self.has_exe_wrapper_method, 53 'can_run_host_binaries': self.can_run_host_binaries_method, 54 'is_unity': self.is_unity_method, 55 'is_subproject': self.is_subproject_method, 56 'current_source_dir': self.current_source_dir_method, 57 'current_build_dir': self.current_build_dir_method, 58 'source_root': self.source_root_method, 59 'build_root': self.build_root_method, 60 'project_source_root': self.project_source_root_method, 61 'project_build_root': self.project_build_root_method, 62 'global_source_root': self.global_source_root_method, 63 'global_build_root': self.global_build_root_method, 64 'add_install_script': self.add_install_script_method, 65 'add_postconf_script': self.add_postconf_script_method, 66 'add_dist_script': self.add_dist_script_method, 67 'install_dependency_manifest': self.install_dependency_manifest_method, 68 'override_dependency': self.override_dependency_method, 69 'override_find_program': self.override_find_program_method, 70 'project_version': self.project_version_method, 71 'project_license': self.project_license_method, 72 'version': self.version_method, 73 'project_name': self.project_name_method, 74 'get_cross_property': self.get_cross_property_method, 75 'get_external_property': self.get_external_property_method, 76 'has_external_property': self.has_external_property_method, 77 'backend': self.backend_method, 78 'add_devenv': self.add_devenv_method, 79 }) 80 81 def _find_source_script( 82 self, prog: T.Union[str, mesonlib.File, build.Executable, ExternalProgram], 83 args: T.List[str]) -> 'ExecutableSerialisation': 84 largs: T.List[T.Union[str, build.Executable, ExternalProgram]] = [] 85 if isinstance(prog, (build.Executable, ExternalProgram)): 86 largs.append(prog) 87 largs.extend(args) 88 return self.interpreter.backend.get_executable_serialisation(largs) 89 found = self.interpreter.func_find_program({}, prog, {}) 90 largs.append(found) 91 largs.extend(args) 92 es = self.interpreter.backend.get_executable_serialisation(largs) 93 es.subproject = self.interpreter.subproject 94 return es 95 96 def _process_script_args( 97 self, name: str, args: T.Sequence[T.Union[ 98 str, mesonlib.File, build.BuildTarget, build.CustomTarget, 99 build.CustomTargetIndex, 100 ExternalProgram, 101 ]], allow_built: bool = False) -> T.List[str]: 102 script_args = [] # T.List[str] 103 new = False 104 for a in args: 105 if isinstance(a, str): 106 script_args.append(a) 107 elif isinstance(a, mesonlib.File): 108 new = True 109 script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) 110 elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): 111 if not allow_built: 112 raise InterpreterException(f'Arguments to {name} cannot be built') 113 new = True 114 script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) 115 116 # This feels really hacky, but I'm not sure how else to fix 117 # this without completely rewriting install script handling. 118 # This is complicated by the fact that the install target 119 # depends on all. 120 if isinstance(a, build.CustomTargetIndex): 121 a.target.build_by_default = True 122 else: 123 a.build_by_default = True 124 else: 125 script_args.extend(a.command) 126 new = True 127 128 if new: 129 FeatureNew.single_use( 130 f'Calling "{name}" with File, CustomTaget, Index of CustomTarget, ' 131 'Executable, or ExternalProgram', 132 '0.55.0', self.interpreter.subproject) 133 return script_args 134 135 @typed_pos_args( 136 'meson.add_install_script', 137 (str, mesonlib.File, build.Executable, ExternalProgram), 138 varargs=(str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, ExternalProgram) 139 ) 140 @typed_kwargs( 141 'meson.add_install_script', 142 KwargInfo('skip_if_destdir', bool, default=False, since='0.57.0'), 143 KwargInfo('install_tag', (str, NoneType), since='0.60.0'), 144 ) 145 def add_install_script_method( 146 self, 147 args: T.Tuple[T.Union[str, mesonlib.File, build.Executable, ExternalProgram], 148 T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, ExternalProgram]]], 149 kwargs: 'AddInstallScriptKW') -> None: 150 if isinstance(args[0], mesonlib.File): 151 FeatureNew.single_use('Passing file object to script parameter of add_install_script', 152 '0.57.0', self.interpreter.subproject) 153 154 script_args = self._process_script_args('add_install_script', args[1], allow_built=True) 155 script = self._find_source_script(args[0], script_args) 156 script.skip_if_destdir = kwargs['skip_if_destdir'] 157 script.tag = kwargs['install_tag'] 158 self.build.install_scripts.append(script) 159 160 @typed_pos_args( 161 'meson.add_postconf_script', 162 (str, mesonlib.File, ExternalProgram), 163 varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex) 164 ) 165 @noKwargs 166 def add_postconf_script_method( 167 self, 168 args: T.Tuple[T.Union[str, mesonlib.File, ExternalProgram], 169 T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]], 170 kwargs: 'TYPE_kwargs') -> None: 171 if isinstance(args[0], mesonlib.File): 172 FeatureNew.single_use('Passing file object to script parameter of add_postconf_script', 173 '0.57.0', self.interpreter.subproject) 174 script_args = self._process_script_args('add_postconf_script', args[1], allow_built=True) 175 script = self._find_source_script(args[0], script_args) 176 self.build.postconf_scripts.append(script) 177 178 @typed_pos_args( 179 'meson.add_dist_script', 180 (str, mesonlib.File, build.Executable, ExternalProgram), 181 varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex) 182 ) 183 @noKwargs 184 def add_dist_script_method( 185 self, 186 args: T.Tuple[T.Union[str, mesonlib.File, build.Executable, ExternalProgram], 187 T.List[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]]], 188 kwargs: 'TYPE_kwargs') -> None: 189 if args[1]: 190 FeatureNew.single_use('Calling "add_dist_script" with multiple arguments', 191 '0.49.0', self.interpreter.subproject) 192 if isinstance(args[0], mesonlib.File): 193 FeatureNew.single_use('Passing file object to script parameter of add_dist_script', 194 '0.57.0', self.interpreter.subproject) 195 if self.interpreter.subproject != '': 196 FeatureNew.single_use('Calling "add_dist_script" in a subproject', 197 '0.58.0', self.interpreter.subproject) 198 script_args = self._process_script_args('add_dist_script', args[1], allow_built=True) 199 script = self._find_source_script(args[0], script_args) 200 self.build.dist_scripts.append(script) 201 202 @noPosargs 203 @noKwargs 204 def current_source_dir_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 205 src = self.interpreter.environment.source_dir 206 sub = self.interpreter.subdir 207 if sub == '': 208 return src 209 return os.path.join(src, sub) 210 211 @noPosargs 212 @noKwargs 213 def current_build_dir_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 214 src = self.interpreter.environment.build_dir 215 sub = self.interpreter.subdir 216 if sub == '': 217 return src 218 return os.path.join(src, sub) 219 220 @noPosargs 221 @noKwargs 222 def backend_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 223 return self.interpreter.backend.name 224 225 @noPosargs 226 @noKwargs 227 @FeatureDeprecated('meson.source_root', '0.56.0', 'use meson.project_source_root() or meson.global_source_root() instead.') 228 def source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 229 return self.interpreter.environment.source_dir 230 231 @noPosargs 232 @noKwargs 233 @FeatureDeprecated('meson.build_root', '0.56.0', 'use meson.project_build_root() or meson.global_build_root() instead.') 234 def build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 235 return self.interpreter.environment.build_dir 236 237 @noPosargs 238 @noKwargs 239 @FeatureNew('meson.project_source_root', '0.56.0') 240 def project_source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 241 src = self.interpreter.environment.source_dir 242 sub = self.interpreter.root_subdir 243 if sub == '': 244 return src 245 return os.path.join(src, sub) 246 247 @noPosargs 248 @noKwargs 249 @FeatureNew('meson.project_build_root', '0.56.0') 250 def project_build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 251 src = self.interpreter.environment.build_dir 252 sub = self.interpreter.root_subdir 253 if sub == '': 254 return src 255 return os.path.join(src, sub) 256 257 @noPosargs 258 @noKwargs 259 @FeatureNew('meson.global_source_root', '0.58.0') 260 def global_source_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 261 return self.interpreter.environment.source_dir 262 263 @noPosargs 264 @noKwargs 265 @FeatureNew('meson.global_build_root', '0.58.0') 266 def global_build_root_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 267 return self.interpreter.environment.build_dir 268 269 @noPosargs 270 @noKwargs 271 @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') 272 def has_exe_wrapper_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: 273 return self._can_run_host_binaries_impl() 274 275 @noPosargs 276 @noKwargs 277 @FeatureNew('meson.can_run_host_binaries', '0.55.0') 278 def can_run_host_binaries_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: 279 return self._can_run_host_binaries_impl() 280 281 def _can_run_host_binaries_impl(self) -> bool: 282 return not ( 283 self.build.environment.is_cross_build() and 284 self.build.environment.need_exe_wrapper() and 285 self.build.environment.exe_wrapper is None 286 ) 287 288 @noPosargs 289 @noKwargs 290 def is_cross_build_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: 291 return self.build.environment.is_cross_build() 292 293 @typed_pos_args('meson.get_compiler', str) 294 @typed_kwargs('meson.get_compiler', NATIVE_KW) 295 def get_compiler_method(self, args: T.Tuple[str], kwargs: 'NativeKW') -> 'Compiler': 296 cname = args[0] 297 for_machine = kwargs['native'] 298 clist = self.interpreter.coredata.compilers[for_machine] 299 try: 300 return clist[cname] 301 except KeyError: 302 raise InterpreterException(f'Tried to access compiler for language "{cname}", not specified for {for_machine.get_lower_case_name()} machine.') 303 304 @noPosargs 305 @noKwargs 306 def is_unity_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: 307 optval = self.interpreter.environment.coredata.get_option(OptionKey('unity')) 308 return optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()) 309 310 @noPosargs 311 @noKwargs 312 def is_subproject_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> bool: 313 return self.interpreter.is_subproject() 314 315 @typed_pos_args('meson.install_dependency_manifest', str) 316 @noKwargs 317 def install_dependency_manifest_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> None: 318 self.build.dep_manifest_name = args[0] 319 320 @FeatureNew('meson.override_find_program', '0.46.0') 321 @typed_pos_args('meson.override_find_program', str, (mesonlib.File, ExternalProgram, build.Executable)) 322 @noKwargs 323 def override_find_program_method(self, args: T.Tuple[str, T.Union[mesonlib.File, ExternalProgram, build.Executable]], kwargs: 'TYPE_kwargs') -> None: 324 name, exe = args 325 if isinstance(exe, mesonlib.File): 326 abspath = exe.absolute_path(self.interpreter.environment.source_dir, 327 self.interpreter.environment.build_dir) 328 if not os.path.exists(abspath): 329 raise InterpreterException(f'Tried to override {name} with a file that does not exist.') 330 exe = OverrideProgram(name, [abspath]) 331 self.interpreter.add_find_program_override(name, exe) 332 333 @typed_kwargs( 334 'meson.override_dependency', 335 NATIVE_KW, 336 KwargInfo('static', (bool, NoneType), since='0.60.0'), 337 ) 338 @typed_pos_args('meson.override_dependency', str, dependencies.Dependency) 339 @FeatureNew('meson.override_dependency', '0.54.0') 340 def override_dependency_method(self, args: T.Tuple[str, dependencies.Dependency], kwargs: 'FuncOverrideDependency') -> None: 341 name, dep = args 342 if not name: 343 raise InterpreterException('First argument must be a string and cannot be empty') 344 345 optkey = OptionKey('default_library', subproject=self.interpreter.subproject) 346 default_library = self.interpreter.coredata.get_option(optkey) 347 assert isinstance(default_library, str), 'for mypy' 348 static = kwargs['static'] 349 if static is None: 350 # We don't know if dep represents a static or shared library, could 351 # be a mix of both. We assume it is following default_library 352 # value. 353 self._override_dependency_impl(name, dep, kwargs, static=None) 354 if default_library == 'static': 355 self._override_dependency_impl(name, dep, kwargs, static=True) 356 elif default_library == 'shared': 357 self._override_dependency_impl(name, dep, kwargs, static=False) 358 else: 359 self._override_dependency_impl(name, dep, kwargs, static=True) 360 self._override_dependency_impl(name, dep, kwargs, static=False) 361 else: 362 # dependency('foo') without specifying static kwarg should find this 363 # override regardless of the static value here. But do not raise error 364 # if it has already been overridden, which would happen when overriding 365 # static and shared separately: 366 # meson.override_dependency('foo', shared_dep, static: false) 367 # meson.override_dependency('foo', static_dep, static: true) 368 # In that case dependency('foo') would return the first override. 369 self._override_dependency_impl(name, dep, kwargs, static=None, permissive=True) 370 self._override_dependency_impl(name, dep, kwargs, static=static) 371 372 def _override_dependency_impl(self, name: str, dep: dependencies.Dependency, kwargs: 'FuncOverrideDependency', 373 static: T.Optional[bool], permissive: bool = False) -> None: 374 # We need the cast here as get_dep_identifier works on such a dict, 375 # which FuncOverrideDependency is, but mypy can't fgure that out 376 nkwargs = T.cast(T.Dict[str, T.Any], kwargs.copy()) 377 if static is None: 378 del nkwargs['static'] 379 else: 380 nkwargs['static'] = static 381 identifier = dependencies.get_dep_identifier(name, nkwargs) 382 for_machine = kwargs['native'] 383 override = self.build.dependency_overrides[for_machine].get(identifier) 384 if override: 385 if permissive: 386 return 387 m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}' 388 location = mlog.get_error_location_string(override.node.filename, override.node.lineno) 389 raise InterpreterException(m.format(name, location)) 390 self.build.dependency_overrides[for_machine][identifier] = \ 391 build.DependencyOverride(dep, self.interpreter.current_node) 392 393 @noPosargs 394 @noKwargs 395 def project_version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 396 return self.build.dep_manifest[self.interpreter.active_projectname].version 397 398 @FeatureNew('meson.project_license()', '0.45.0') 399 @noPosargs 400 @noKwargs 401 def project_license_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> T.List[str]: 402 return self.build.dep_manifest[self.interpreter.active_projectname].license 403 404 @noPosargs 405 @noKwargs 406 def version_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> MesonVersionString: 407 return MesonVersionString(self.interpreter.coredata.version) 408 409 @noPosargs 410 @noKwargs 411 def project_name_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: 412 return self.interpreter.active_projectname 413 414 def __get_external_property_impl(self, propname: str, fallback: T.Optional[object], machine: MachineChoice) -> object: 415 """Shared implementation for get_cross_property and get_external_property.""" 416 try: 417 return self.interpreter.environment.properties[machine][propname] 418 except KeyError: 419 if fallback is not None: 420 return fallback 421 raise InterpreterException(f'Unknown property for {machine.get_lower_case_name()} machine: {propname}') 422 423 @noArgsFlattening 424 @FeatureDeprecated('meson.get_cross_property', '0.58.0', 'Use meson.get_external_property() instead') 425 @typed_pos_args('meson.get_cross_property', str, optargs=[object]) 426 @noKwargs 427 def get_cross_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: 'TYPE_kwargs') -> object: 428 propname, fallback = args 429 return self.__get_external_property_impl(propname, fallback, MachineChoice.HOST) 430 431 @noArgsFlattening 432 @FeatureNew('meson.get_external_property', '0.54.0') 433 @typed_pos_args('meson.get_external_property', str, optargs=[object]) 434 @typed_kwargs('meson.get_external_property', NATIVE_KW) 435 def get_external_property_method(self, args: T.Tuple[str, T.Optional[object]], kwargs: 'NativeKW') -> object: 436 propname, fallback = args 437 return self.__get_external_property_impl(propname, fallback, kwargs['native']) 438 439 @FeatureNew('meson.has_external_property', '0.58.0') 440 @typed_pos_args('meson.has_external_property', str) 441 @typed_kwargs('meson.has_external_property', NATIVE_KW) 442 def has_external_property_method(self, args: T.Tuple[str], kwargs: 'NativeKW') -> bool: 443 prop_name = args[0] 444 return prop_name in self.interpreter.environment.properties[kwargs['native']] 445 446 @FeatureNew('add_devenv', '0.58.0') 447 @noKwargs 448 @typed_pos_args('add_devenv', (str, list, dict, build.EnvironmentVariables)) 449 def add_devenv_method(self, args: T.Tuple[T.Union[str, list, dict, build.EnvironmentVariables]], kwargs: 'TYPE_kwargs') -> None: 450 env = args[0] 451 msg = ENV_KW.validator(env) 452 if msg: 453 raise build.InvalidArguments(f'"add_devenv": {msg}') 454 converted = ENV_KW.convertor(env) 455 assert isinstance(converted, build.EnvironmentVariables) 456 self.build.devenv.append(converted) 457