1# Copyright 2012-2019 The Meson development team 2# Licensed under the Apache License, Version 2.0 (the "License"); 3# you may not use this file except in compliance with the License. 4# You may obtain a copy of the License at 5 6# http://www.apache.org/licenses/LICENSE-2.0 7 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, 10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14from . import mparser 15from . import environment 16from . import coredata 17from . import dependencies 18from . import mlog 19from . import build 20from . import optinterpreter 21from . import compilers 22from .wrap import wrap, WrapMode 23from . import mesonlib 24from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep, unholder 25from .dependencies import ExternalProgram 26from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException 27from .depfile import DepFile 28from .interpreterbase import InterpreterBase 29from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening 30from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest 31from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound 32from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs 33from .interpreterbase import ObjectHolder 34from .modules import ModuleReturnValue 35from .cmake import CMakeInterpreter 36from .backend.backends import TestProtocol 37 38from pathlib import Path, PurePath 39import os 40import shutil 41import uuid 42import re 43import shlex 44import stat 45import subprocess 46import collections 47import functools 48import typing as T 49 50import importlib 51 52permitted_method_kwargs = { 53 'partial_dependency': {'compile_args', 'link_args', 'links', 'includes', 54 'sources'}, 55} 56 57def stringifyUserArguments(args): 58 if isinstance(args, list): 59 return '[%s]' % ', '.join([stringifyUserArguments(x) for x in args]) 60 elif isinstance(args, dict): 61 return '{%s}' % ', '.join(['%s : %s' % (stringifyUserArguments(k), stringifyUserArguments(v)) for k, v in args.items()]) 62 elif isinstance(args, int): 63 return str(args) 64 elif isinstance(args, str): 65 return "'%s'" % args 66 raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') 67 68 69class OverrideProgram(dependencies.ExternalProgram): 70 pass 71 72 73class FeatureOptionHolder(InterpreterObject, ObjectHolder): 74 def __init__(self, env, name, option): 75 InterpreterObject.__init__(self) 76 ObjectHolder.__init__(self, option) 77 if option.is_auto(): 78 self.held_object = env.coredata.builtins['auto_features'] 79 self.name = name 80 self.methods.update({'enabled': self.enabled_method, 81 'disabled': self.disabled_method, 82 'auto': self.auto_method, 83 }) 84 85 @noPosargs 86 @permittedKwargs({}) 87 def enabled_method(self, args, kwargs): 88 return self.held_object.is_enabled() 89 90 @noPosargs 91 @permittedKwargs({}) 92 def disabled_method(self, args, kwargs): 93 return self.held_object.is_disabled() 94 95 @noPosargs 96 @permittedKwargs({}) 97 def auto_method(self, args, kwargs): 98 return self.held_object.is_auto() 99 100def extract_required_kwarg(kwargs, subproject, feature_check=None, default=True): 101 val = kwargs.get('required', default) 102 disabled = False 103 required = False 104 feature = None 105 if isinstance(val, FeatureOptionHolder): 106 if not feature_check: 107 feature_check = FeatureNew('User option "feature"', '0.47.0') 108 feature_check.use(subproject) 109 option = val.held_object 110 feature = val.name 111 if option.is_disabled(): 112 disabled = True 113 elif option.is_enabled(): 114 required = True 115 elif isinstance(val, bool): 116 required = val 117 else: 118 raise InterpreterException('required keyword argument must be boolean or a feature option') 119 120 # Keep boolean value in kwargs to simplify other places where this kwarg is 121 # checked. 122 kwargs['required'] = required 123 124 return disabled, required, feature 125 126def extract_search_dirs(kwargs): 127 search_dirs = mesonlib.stringlistify(kwargs.get('dirs', [])) 128 search_dirs = [Path(d).expanduser() for d in search_dirs] 129 for d in search_dirs: 130 if mesonlib.is_windows() and d.root.startswith('\\'): 131 # a Unix-path starting with `/` that is not absolute on Windows. 132 # discard without failing for end-user ease of cross-platform directory arrays 133 continue 134 if not d.is_absolute(): 135 raise InvalidCode('Search directory {} is not an absolute path.'.format(d)) 136 return list(map(str, search_dirs)) 137 138class TryRunResultHolder(InterpreterObject): 139 def __init__(self, res): 140 super().__init__() 141 self.res = res 142 self.methods.update({'returncode': self.returncode_method, 143 'compiled': self.compiled_method, 144 'stdout': self.stdout_method, 145 'stderr': self.stderr_method, 146 }) 147 148 @noPosargs 149 @permittedKwargs({}) 150 def returncode_method(self, args, kwargs): 151 return self.res.returncode 152 153 @noPosargs 154 @permittedKwargs({}) 155 def compiled_method(self, args, kwargs): 156 return self.res.compiled 157 158 @noPosargs 159 @permittedKwargs({}) 160 def stdout_method(self, args, kwargs): 161 return self.res.stdout 162 163 @noPosargs 164 @permittedKwargs({}) 165 def stderr_method(self, args, kwargs): 166 return self.res.stderr 167 168class RunProcess(InterpreterObject): 169 170 def __init__(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True): 171 super().__init__() 172 if not isinstance(cmd, ExternalProgram): 173 raise AssertionError('BUG: RunProcess must be passed an ExternalProgram') 174 self.capture = capture 175 pc, self.stdout, self.stderr = self.run_command(cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check) 176 self.returncode = pc.returncode 177 self.methods.update({'returncode': self.returncode_method, 178 'stdout': self.stdout_method, 179 'stderr': self.stderr_method, 180 }) 181 182 def run_command(self, cmd, args, env, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check=False): 183 command_array = cmd.get_command() + args 184 menv = {'MESON_SOURCE_ROOT': source_dir, 185 'MESON_BUILD_ROOT': build_dir, 186 'MESON_SUBDIR': subdir, 187 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in mesonintrospect]), 188 } 189 if in_builddir: 190 cwd = os.path.join(build_dir, subdir) 191 else: 192 cwd = os.path.join(source_dir, subdir) 193 child_env = os.environ.copy() 194 child_env.update(menv) 195 child_env = env.get_env(child_env) 196 stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL 197 mlog.debug('Running command:', ' '.join(command_array)) 198 try: 199 p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd) 200 if self.capture: 201 mlog.debug('--- stdout ---') 202 mlog.debug(o) 203 else: 204 o = '' 205 mlog.debug('--- stdout disabled ---') 206 mlog.debug('--- stderr ---') 207 mlog.debug(e) 208 mlog.debug('') 209 210 if check and p.returncode != 0: 211 raise InterpreterException('Command "{}" failed with status {}.'.format(' '.join(command_array), p.returncode)) 212 213 return p, o, e 214 except FileNotFoundError: 215 raise InterpreterException('Could not execute command "%s".' % ' '.join(command_array)) 216 217 @noPosargs 218 @permittedKwargs({}) 219 def returncode_method(self, args, kwargs): 220 return self.returncode 221 222 @noPosargs 223 @permittedKwargs({}) 224 def stdout_method(self, args, kwargs): 225 return self.stdout 226 227 @noPosargs 228 @permittedKwargs({}) 229 def stderr_method(self, args, kwargs): 230 return self.stderr 231 232class ConfigureFileHolder(InterpreterObject, ObjectHolder): 233 234 def __init__(self, subdir, sourcename, targetname, configuration_data): 235 InterpreterObject.__init__(self) 236 obj = build.ConfigureFile(subdir, sourcename, targetname, configuration_data) 237 ObjectHolder.__init__(self, obj) 238 239 240class EnvironmentVariablesHolder(MutableInterpreterObject, ObjectHolder): 241 def __init__(self, initial_values=None): 242 MutableInterpreterObject.__init__(self) 243 ObjectHolder.__init__(self, build.EnvironmentVariables()) 244 self.methods.update({'set': self.set_method, 245 'append': self.append_method, 246 'prepend': self.prepend_method, 247 }) 248 if isinstance(initial_values, dict): 249 for k, v in initial_values.items(): 250 self.set_method([k, v], {}) 251 elif isinstance(initial_values, list): 252 for e in initial_values: 253 if '=' not in e: 254 raise InterpreterException('Env var definition must be of type key=val.') 255 (k, val) = e.split('=', 1) 256 k = k.strip() 257 val = val.strip() 258 if ' ' in k: 259 raise InterpreterException('Env var key must not have spaces in it.') 260 self.set_method([k, val], {}) 261 elif initial_values: 262 raise AssertionError('Unsupported EnvironmentVariablesHolder initial_values') 263 264 def __repr__(self): 265 repr_str = "<{0}: {1}>" 266 return repr_str.format(self.__class__.__name__, self.held_object.envvars) 267 268 def add_var(self, method, args, kwargs): 269 if not isinstance(kwargs.get("separator", ""), str): 270 raise InterpreterException("EnvironmentVariablesHolder methods 'separator'" 271 " argument needs to be a string.") 272 if len(args) < 2: 273 raise InterpreterException("EnvironmentVariablesHolder methods require at least" 274 "2 arguments, first is the name of the variable and" 275 " following one are values") 276 # Warn when someone tries to use append() or prepend() on an env var 277 # which already has an operation set on it. People seem to think that 278 # multiple append/prepend operations stack, but they don't. 279 if method != self.held_object.set and self.held_object.has_name(args[0]): 280 mlog.warning('Overriding previous value of environment variable {!r} with a new one' 281 .format(args[0]), location=self.current_node) 282 self.held_object.add_var(method, args[0], args[1:], kwargs) 283 284 @stringArgs 285 @permittedKwargs({'separator'}) 286 def set_method(self, args, kwargs): 287 self.add_var(self.held_object.set, args, kwargs) 288 289 @stringArgs 290 @permittedKwargs({'separator'}) 291 def append_method(self, args, kwargs): 292 self.add_var(self.held_object.append, args, kwargs) 293 294 @stringArgs 295 @permittedKwargs({'separator'}) 296 def prepend_method(self, args, kwargs): 297 self.add_var(self.held_object.prepend, args, kwargs) 298 299 300class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): 301 def __init__(self, pv, initial_values=None): 302 MutableInterpreterObject.__init__(self) 303 self.used = False # These objects become immutable after use in configure_file. 304 ObjectHolder.__init__(self, build.ConfigurationData(), pv) 305 self.methods.update({'set': self.set_method, 306 'set10': self.set10_method, 307 'set_quoted': self.set_quoted_method, 308 'has': self.has_method, 309 'get': self.get_method, 310 'get_unquoted': self.get_unquoted_method, 311 'merge_from': self.merge_from_method, 312 }) 313 if isinstance(initial_values, dict): 314 for k, v in initial_values.items(): 315 self.set_method([k, v], {}) 316 elif initial_values: 317 raise AssertionError('Unsupported ConfigurationDataHolder initial_values') 318 319 def is_used(self): 320 return self.used 321 322 def mark_used(self): 323 self.used = True 324 325 def validate_args(self, args, kwargs): 326 if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: 327 mlog.deprecation('Passing a list as the single argument to ' 328 'configuration_data.set is deprecated. This will ' 329 'become a hard error in the future.', 330 location=self.current_node) 331 args = args[0] 332 333 if len(args) != 2: 334 raise InterpreterException("Configuration set requires 2 arguments.") 335 if self.used: 336 raise InterpreterException("Can not set values on configuration object that has been used.") 337 name, val = args 338 if not isinstance(val, (int, str)): 339 msg = 'Setting a configuration data value to {!r} is invalid, ' \ 340 'and will fail at configure_file(). If you are using it ' \ 341 'just to store some values, please use a dict instead.' 342 mlog.deprecation(msg.format(val), location=self.current_node) 343 desc = kwargs.get('description', None) 344 if not isinstance(name, str): 345 raise InterpreterException("First argument to set must be a string.") 346 if desc is not None and not isinstance(desc, str): 347 raise InterpreterException('Description must be a string.') 348 349 return name, val, desc 350 351 @noArgsFlattening 352 def set_method(self, args, kwargs): 353 (name, val, desc) = self.validate_args(args, kwargs) 354 self.held_object.values[name] = (val, desc) 355 356 def set_quoted_method(self, args, kwargs): 357 (name, val, desc) = self.validate_args(args, kwargs) 358 if not isinstance(val, str): 359 raise InterpreterException("Second argument to set_quoted must be a string.") 360 escaped_val = '\\"'.join(val.split('"')) 361 self.held_object.values[name] = ('"' + escaped_val + '"', desc) 362 363 def set10_method(self, args, kwargs): 364 (name, val, desc) = self.validate_args(args, kwargs) 365 if val: 366 self.held_object.values[name] = (1, desc) 367 else: 368 self.held_object.values[name] = (0, desc) 369 370 def has_method(self, args, kwargs): 371 return args[0] in self.held_object.values 372 373 @FeatureNew('configuration_data.get()', '0.38.0') 374 @noArgsFlattening 375 def get_method(self, args, kwargs): 376 if len(args) < 1 or len(args) > 2: 377 raise InterpreterException('Get method takes one or two arguments.') 378 name = args[0] 379 if name in self.held_object: 380 return self.held_object.get(name)[0] 381 if len(args) > 1: 382 return args[1] 383 raise InterpreterException('Entry %s not in configuration data.' % name) 384 385 @FeatureNew('configuration_data.get_unquoted()', '0.44.0') 386 def get_unquoted_method(self, args, kwargs): 387 if len(args) < 1 or len(args) > 2: 388 raise InterpreterException('Get method takes one or two arguments.') 389 name = args[0] 390 if name in self.held_object: 391 val = self.held_object.get(name)[0] 392 elif len(args) > 1: 393 val = args[1] 394 else: 395 raise InterpreterException('Entry %s not in configuration data.' % name) 396 if val[0] == '"' and val[-1] == '"': 397 return val[1:-1] 398 return val 399 400 def get(self, name): 401 return self.held_object.values[name] # (val, desc) 402 403 def keys(self): 404 return self.held_object.values.keys() 405 406 def merge_from_method(self, args, kwargs): 407 if len(args) != 1: 408 raise InterpreterException('Merge_from takes one positional argument.') 409 from_object = args[0] 410 if not isinstance(from_object, ConfigurationDataHolder): 411 raise InterpreterException('Merge_from argument must be a configuration data object.') 412 from_object = from_object.held_object 413 for k, v in from_object.values.items(): 414 self.held_object.values[k] = v 415 416# Interpreter objects can not be pickled so we must have 417# these wrappers. 418 419class DependencyHolder(InterpreterObject, ObjectHolder): 420 def __init__(self, dep, pv): 421 InterpreterObject.__init__(self) 422 ObjectHolder.__init__(self, dep, pv) 423 self.methods.update({'found': self.found_method, 424 'type_name': self.type_name_method, 425 'version': self.version_method, 426 'name': self.name_method, 427 'get_pkgconfig_variable': self.pkgconfig_method, 428 'get_configtool_variable': self.configtool_method, 429 'get_variable': self.variable_method, 430 'partial_dependency': self.partial_dependency_method, 431 'include_type': self.include_type_method, 432 'as_system': self.as_system_method, 433 }) 434 435 def found(self): 436 return self.found_method([], {}) 437 438 @noPosargs 439 @permittedKwargs({}) 440 def type_name_method(self, args, kwargs): 441 return self.held_object.type_name 442 443 @noPosargs 444 @permittedKwargs({}) 445 def found_method(self, args, kwargs): 446 if self.held_object.type_name == 'internal': 447 return True 448 return self.held_object.found() 449 450 @noPosargs 451 @permittedKwargs({}) 452 def version_method(self, args, kwargs): 453 return self.held_object.get_version() 454 455 @noPosargs 456 @permittedKwargs({}) 457 def name_method(self, args, kwargs): 458 return self.held_object.get_name() 459 460 @permittedKwargs({'define_variable', 'default'}) 461 def pkgconfig_method(self, args, kwargs): 462 args = listify(args) 463 if len(args) != 1: 464 raise InterpreterException('get_pkgconfig_variable takes exactly one argument.') 465 varname = args[0] 466 if not isinstance(varname, str): 467 raise InterpreterException('Variable name must be a string.') 468 return self.held_object.get_pkgconfig_variable(varname, kwargs) 469 470 @FeatureNew('dep.get_configtool_variable', '0.44.0') 471 @permittedKwargs({}) 472 def configtool_method(self, args, kwargs): 473 args = listify(args) 474 if len(args) != 1: 475 raise InterpreterException('get_configtool_variable takes exactly one argument.') 476 varname = args[0] 477 if not isinstance(varname, str): 478 raise InterpreterException('Variable name must be a string.') 479 return self.held_object.get_configtool_variable(varname) 480 481 @FeatureNew('dep.partial_dependency', '0.46.0') 482 @noPosargs 483 @permittedKwargs(permitted_method_kwargs['partial_dependency']) 484 def partial_dependency_method(self, args, kwargs): 485 pdep = self.held_object.get_partial_dependency(**kwargs) 486 return DependencyHolder(pdep, self.subproject) 487 488 @FeatureNew('dep.get_variable', '0.51.0') 489 @noPosargs 490 @permittedKwargs({'cmake', 'pkgconfig', 'configtool', 'internal', 'default_value', 'pkgconfig_define'}) 491 @FeatureNewKwargs('dep.get_variable', '0.54.0', ['internal']) 492 def variable_method(self, args, kwargs): 493 return self.held_object.get_variable(**kwargs) 494 495 @FeatureNew('dep.include_type', '0.52.0') 496 @noPosargs 497 @permittedKwargs({}) 498 def include_type_method(self, args, kwargs): 499 return self.held_object.get_include_type() 500 501 @FeatureNew('dep.as_system', '0.52.0') 502 @permittedKwargs({}) 503 def as_system_method(self, args, kwargs): 504 args = listify(args) 505 new_is_system = 'system' 506 if len(args) > 1: 507 raise InterpreterException('as_system takes only one optional value') 508 if len(args) == 1: 509 new_is_system = args[0] 510 new_dep = self.held_object.generate_system_dependency(new_is_system) 511 return DependencyHolder(new_dep, self.subproject) 512 513class ExternalProgramHolder(InterpreterObject, ObjectHolder): 514 def __init__(self, ep, subproject, backend=None): 515 InterpreterObject.__init__(self) 516 ObjectHolder.__init__(self, ep) 517 self.subproject = subproject 518 self.backend = backend 519 self.methods.update({'found': self.found_method, 520 'path': self.path_method, 521 'full_path': self.full_path_method}) 522 self.cached_version = None 523 524 @noPosargs 525 @permittedKwargs({}) 526 def found_method(self, args, kwargs): 527 return self.found() 528 529 @noPosargs 530 @permittedKwargs({}) 531 @FeatureDeprecated('ExternalProgram.path', '0.55.0', 532 'use ExternalProgram.full_path() instead') 533 def path_method(self, args, kwargs): 534 return self._full_path() 535 536 @noPosargs 537 @permittedKwargs({}) 538 @FeatureNew('ExternalProgram.full_path', '0.55.0') 539 def full_path_method(self, args, kwargs): 540 return self._full_path() 541 542 def _full_path(self): 543 exe = self.held_object 544 if isinstance(exe, build.Executable): 545 return self.backend.get_target_filename_abs(exe) 546 return exe.get_path() 547 548 def found(self): 549 return isinstance(self.held_object, build.Executable) or self.held_object.found() 550 551 def get_command(self): 552 return self.held_object.get_command() 553 554 def get_name(self): 555 exe = self.held_object 556 if isinstance(exe, build.Executable): 557 return exe.name 558 return exe.get_name() 559 560 def get_version(self, interpreter): 561 if isinstance(self.held_object, build.Executable): 562 return self.held_object.project_version 563 if not self.cached_version: 564 raw_cmd = self.get_command() + ['--version'] 565 cmd = [self, '--version'] 566 res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) 567 if res.returncode != 0: 568 m = 'Running {!r} failed' 569 raise InterpreterException(m.format(raw_cmd)) 570 output = res.stdout.strip() 571 if not output: 572 output = res.stderr.strip() 573 match = re.search(r'([0-9][0-9\.]+)', output) 574 if not match: 575 m = 'Could not find a version number in output of {!r}' 576 raise InterpreterException(m.format(raw_cmd)) 577 self.cached_version = match.group(1) 578 return self.cached_version 579 580class ExternalLibraryHolder(InterpreterObject, ObjectHolder): 581 def __init__(self, el, pv): 582 InterpreterObject.__init__(self) 583 ObjectHolder.__init__(self, el, pv) 584 self.methods.update({'found': self.found_method, 585 'type_name': self.type_name_method, 586 'partial_dependency': self.partial_dependency_method, 587 }) 588 589 def found(self): 590 return self.held_object.found() 591 592 @noPosargs 593 @permittedKwargs({}) 594 def type_name_method(self, args, kwargs): 595 return self.held_object.type_name 596 597 @noPosargs 598 @permittedKwargs({}) 599 def found_method(self, args, kwargs): 600 return self.found() 601 602 def get_name(self): 603 return self.held_object.name 604 605 def get_compile_args(self): 606 return self.held_object.get_compile_args() 607 608 def get_link_args(self): 609 return self.held_object.get_link_args() 610 611 def get_exe_args(self): 612 return self.held_object.get_exe_args() 613 614 @FeatureNew('dep.partial_dependency', '0.46.0') 615 @noPosargs 616 @permittedKwargs(permitted_method_kwargs['partial_dependency']) 617 def partial_dependency_method(self, args, kwargs): 618 pdep = self.held_object.get_partial_dependency(**kwargs) 619 return DependencyHolder(pdep, self.subproject) 620 621class GeneratorHolder(InterpreterObject, ObjectHolder): 622 @FeatureNewKwargs('generator', '0.43.0', ['capture']) 623 def __init__(self, interp, args, kwargs): 624 self.interpreter = interp 625 InterpreterObject.__init__(self) 626 ObjectHolder.__init__(self, build.Generator(args, kwargs), interp.subproject) 627 self.methods.update({'process': self.process_method}) 628 629 @FeatureNewKwargs('generator.process', '0.45.0', ['preserve_path_from']) 630 @permittedKwargs({'extra_args', 'preserve_path_from'}) 631 def process_method(self, args, kwargs): 632 extras = mesonlib.stringlistify(kwargs.get('extra_args', [])) 633 if 'preserve_path_from' in kwargs: 634 preserve_path_from = kwargs['preserve_path_from'] 635 if not isinstance(preserve_path_from, str): 636 raise InvalidArguments('Preserve_path_from must be a string.') 637 preserve_path_from = os.path.normpath(preserve_path_from) 638 if not os.path.isabs(preserve_path_from): 639 # This is a bit of a hack. Fix properly before merging. 640 raise InvalidArguments('Preserve_path_from must be an absolute path for now. Sorry.') 641 else: 642 preserve_path_from = None 643 gl = self.held_object.process_files('Generator', args, self.interpreter, 644 preserve_path_from, extra_args=extras) 645 return GeneratedListHolder(gl) 646 647 648class GeneratedListHolder(InterpreterObject, ObjectHolder): 649 def __init__(self, arg1, extra_args=None): 650 InterpreterObject.__init__(self) 651 if isinstance(arg1, GeneratorHolder): 652 ObjectHolder.__init__(self, build.GeneratedList(arg1.held_object, extra_args if extra_args is not None else [])) 653 else: 654 ObjectHolder.__init__(self, arg1) 655 656 def __repr__(self): 657 r = '<{}: {!r}>' 658 return r.format(self.__class__.__name__, self.held_object.get_outputs()) 659 660 def add_file(self, a): 661 self.held_object.add_file(a) 662 663# A machine that's statically known from the cross file 664class MachineHolder(InterpreterObject, ObjectHolder): 665 def __init__(self, machine_info): 666 InterpreterObject.__init__(self) 667 ObjectHolder.__init__(self, machine_info) 668 self.methods.update({'system': self.system_method, 669 'cpu': self.cpu_method, 670 'cpu_family': self.cpu_family_method, 671 'endian': self.endian_method, 672 }) 673 674 @noPosargs 675 @permittedKwargs({}) 676 def cpu_family_method(self, args, kwargs): 677 return self.held_object.cpu_family 678 679 @noPosargs 680 @permittedKwargs({}) 681 def cpu_method(self, args, kwargs): 682 return self.held_object.cpu 683 684 @noPosargs 685 @permittedKwargs({}) 686 def system_method(self, args, kwargs): 687 return self.held_object.system 688 689 @noPosargs 690 @permittedKwargs({}) 691 def endian_method(self, args, kwargs): 692 return self.held_object.endian 693 694class IncludeDirsHolder(InterpreterObject, ObjectHolder): 695 def __init__(self, idobj): 696 InterpreterObject.__init__(self) 697 ObjectHolder.__init__(self, idobj) 698 699class Headers(InterpreterObject): 700 701 def __init__(self, sources, kwargs): 702 InterpreterObject.__init__(self) 703 self.sources = sources 704 self.install_subdir = kwargs.get('subdir', '') 705 if os.path.isabs(self.install_subdir): 706 mlog.deprecation('Subdir keyword must not be an absolute path. This will be a hard error in the next release.') 707 self.custom_install_dir = kwargs.get('install_dir', None) 708 self.custom_install_mode = kwargs.get('install_mode', None) 709 if self.custom_install_dir is not None: 710 if not isinstance(self.custom_install_dir, str): 711 raise InterpreterException('Custom_install_dir must be a string.') 712 713 def set_install_subdir(self, subdir): 714 self.install_subdir = subdir 715 716 def get_install_subdir(self): 717 return self.install_subdir 718 719 def get_sources(self): 720 return self.sources 721 722 def get_custom_install_dir(self): 723 return self.custom_install_dir 724 725 def get_custom_install_mode(self): 726 return self.custom_install_mode 727 728class DataHolder(InterpreterObject, ObjectHolder): 729 def __init__(self, data): 730 InterpreterObject.__init__(self) 731 ObjectHolder.__init__(self, data) 732 733 def get_source_subdir(self): 734 return self.held_object.source_subdir 735 736 def get_sources(self): 737 return self.held_object.sources 738 739 def get_install_dir(self): 740 return self.held_object.install_dir 741 742class InstallDir(InterpreterObject): 743 def __init__(self, src_subdir, inst_subdir, install_dir, install_mode, exclude, strip_directory): 744 InterpreterObject.__init__(self) 745 self.source_subdir = src_subdir 746 self.installable_subdir = inst_subdir 747 self.install_dir = install_dir 748 self.install_mode = install_mode 749 self.exclude = exclude 750 self.strip_directory = strip_directory 751 752class Man(InterpreterObject): 753 754 def __init__(self, sources, kwargs): 755 InterpreterObject.__init__(self) 756 self.sources = sources 757 self.validate_sources() 758 self.custom_install_dir = kwargs.get('install_dir', None) 759 self.custom_install_mode = kwargs.get('install_mode', None) 760 if self.custom_install_dir is not None and not isinstance(self.custom_install_dir, str): 761 raise InterpreterException('Custom_install_dir must be a string.') 762 763 def validate_sources(self): 764 for s in self.sources: 765 try: 766 num = int(s.split('.')[-1]) 767 except (IndexError, ValueError): 768 num = 0 769 if num < 1 or num > 8: 770 raise InvalidArguments('Man file must have a file extension of a number between 1 and 8') 771 772 def get_custom_install_dir(self): 773 return self.custom_install_dir 774 775 def get_custom_install_mode(self): 776 return self.custom_install_mode 777 778 def get_sources(self): 779 return self.sources 780 781class GeneratedObjectsHolder(InterpreterObject, ObjectHolder): 782 def __init__(self, held_object): 783 InterpreterObject.__init__(self) 784 ObjectHolder.__init__(self, held_object) 785 786class TargetHolder(InterpreterObject, ObjectHolder): 787 def __init__(self, target, interp): 788 InterpreterObject.__init__(self) 789 ObjectHolder.__init__(self, target, interp.subproject) 790 self.interpreter = interp 791 792class BuildTargetHolder(TargetHolder): 793 def __init__(self, target, interp): 794 super().__init__(target, interp) 795 self.methods.update({'extract_objects': self.extract_objects_method, 796 'extract_all_objects': self.extract_all_objects_method, 797 'name': self.name_method, 798 'get_id': self.get_id_method, 799 'outdir': self.outdir_method, 800 'full_path': self.full_path_method, 801 'private_dir_include': self.private_dir_include_method, 802 }) 803 804 def __repr__(self): 805 r = '<{} {}: {}>' 806 h = self.held_object 807 return r.format(self.__class__.__name__, h.get_id(), h.filename) 808 809 def is_cross(self): 810 return not self.held_object.environment.machines.matches_build_machine(self.held_object.for_machine) 811 812 @noPosargs 813 @permittedKwargs({}) 814 def private_dir_include_method(self, args, kwargs): 815 return IncludeDirsHolder(build.IncludeDirs('', [], False, 816 [self.interpreter.backend.get_target_private_dir(self.held_object)])) 817 818 @noPosargs 819 @permittedKwargs({}) 820 def full_path_method(self, args, kwargs): 821 return self.interpreter.backend.get_target_filename_abs(self.held_object) 822 823 @noPosargs 824 @permittedKwargs({}) 825 def outdir_method(self, args, kwargs): 826 return self.interpreter.backend.get_target_dir(self.held_object) 827 828 @permittedKwargs({}) 829 def extract_objects_method(self, args, kwargs): 830 gobjs = self.held_object.extract_objects(args) 831 return GeneratedObjectsHolder(gobjs) 832 833 @FeatureNewKwargs('extract_all_objects', '0.46.0', ['recursive']) 834 @noPosargs 835 @permittedKwargs({'recursive'}) 836 def extract_all_objects_method(self, args, kwargs): 837 recursive = kwargs.get('recursive', False) 838 gobjs = self.held_object.extract_all_objects(recursive) 839 if gobjs.objlist and 'recursive' not in kwargs: 840 mlog.warning('extract_all_objects called without setting recursive ' 841 'keyword argument. Meson currently defaults to ' 842 'non-recursive to maintain backward compatibility but ' 843 'the default will be changed in the future.', 844 location=self.current_node) 845 return GeneratedObjectsHolder(gobjs) 846 847 @noPosargs 848 @permittedKwargs({}) 849 def get_id_method(self, args, kwargs): 850 return self.held_object.get_id() 851 852 @FeatureNew('name', '0.54.0') 853 @noPosargs 854 @permittedKwargs({}) 855 def name_method(self, args, kwargs): 856 return self.held_object.name 857 858class ExecutableHolder(BuildTargetHolder): 859 def __init__(self, target, interp): 860 super().__init__(target, interp) 861 862class StaticLibraryHolder(BuildTargetHolder): 863 def __init__(self, target, interp): 864 super().__init__(target, interp) 865 866class SharedLibraryHolder(BuildTargetHolder): 867 def __init__(self, target, interp): 868 super().__init__(target, interp) 869 # Set to True only when called from self.func_shared_lib(). 870 target.shared_library_only = False 871 872class BothLibrariesHolder(BuildTargetHolder): 873 def __init__(self, shared_holder, static_holder, interp): 874 # FIXME: This build target always represents the shared library, but 875 # that should be configurable. 876 super().__init__(shared_holder.held_object, interp) 877 self.shared_holder = shared_holder 878 self.static_holder = static_holder 879 self.methods.update({'get_shared_lib': self.get_shared_lib_method, 880 'get_static_lib': self.get_static_lib_method, 881 }) 882 883 def __repr__(self): 884 r = '<{} {}: {}, {}: {}>' 885 h1 = self.shared_holder.held_object 886 h2 = self.static_holder.held_object 887 return r.format(self.__class__.__name__, h1.get_id(), h1.filename, h2.get_id(), h2.filename) 888 889 @noPosargs 890 @permittedKwargs({}) 891 def get_shared_lib_method(self, args, kwargs): 892 return self.shared_holder 893 894 @noPosargs 895 @permittedKwargs({}) 896 def get_static_lib_method(self, args, kwargs): 897 return self.static_holder 898 899class SharedModuleHolder(BuildTargetHolder): 900 def __init__(self, target, interp): 901 super().__init__(target, interp) 902 903class JarHolder(BuildTargetHolder): 904 def __init__(self, target, interp): 905 super().__init__(target, interp) 906 907class CustomTargetIndexHolder(TargetHolder): 908 def __init__(self, target, interp): 909 super().__init__(target, interp) 910 self.methods.update({'full_path': self.full_path_method, 911 }) 912 913 @FeatureNew('custom_target[i].full_path', '0.54.0') 914 @noPosargs 915 @permittedKwargs({}) 916 def full_path_method(self, args, kwargs): 917 return self.interpreter.backend.get_target_filename_abs(self.held_object) 918 919class CustomTargetHolder(TargetHolder): 920 def __init__(self, target, interp): 921 super().__init__(target, interp) 922 self.methods.update({'full_path': self.full_path_method, 923 'to_list': self.to_list_method, 924 }) 925 926 def __repr__(self): 927 r = '<{} {}: {}>' 928 h = self.held_object 929 return r.format(self.__class__.__name__, h.get_id(), h.command) 930 931 @noPosargs 932 @permittedKwargs({}) 933 def full_path_method(self, args, kwargs): 934 return self.interpreter.backend.get_target_filename_abs(self.held_object) 935 936 @FeatureNew('custom_target.to_list', '0.54.0') 937 @noPosargs 938 @permittedKwargs({}) 939 def to_list_method(self, args, kwargs): 940 result = [] 941 for i in self.held_object: 942 result.append(CustomTargetIndexHolder(i, self.interpreter)) 943 return result 944 945 def __getitem__(self, index): 946 return CustomTargetIndexHolder(self.held_object[index], self.interpreter) 947 948 def __setitem__(self, index, value): # lgtm[py/unexpected-raise-in-special-method] 949 raise InterpreterException('Cannot set a member of a CustomTarget') 950 951 def __delitem__(self, index): # lgtm[py/unexpected-raise-in-special-method] 952 raise InterpreterException('Cannot delete a member of a CustomTarget') 953 954 def outdir_include(self): 955 return IncludeDirsHolder(build.IncludeDirs('', [], False, 956 [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))])) 957 958class RunTargetHolder(TargetHolder): 959 def __init__(self, target, interp): 960 super().__init__(target, interp) 961 962 def __repr__(self): 963 r = '<{} {}: {}>' 964 h = self.held_object 965 return r.format(self.__class__.__name__, h.get_id(), h.command) 966 967class Test(InterpreterObject): 968 def __init__(self, name: str, project: str, suite: T.List[str], exe: build.Executable, 969 depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], 970 is_parallel: bool, cmd_args: T.List[str], env: build.EnvironmentVariables, 971 should_fail: bool, timeout: int, workdir: T.Optional[str], protocol: str, 972 priority: int): 973 InterpreterObject.__init__(self) 974 self.name = name 975 self.suite = suite 976 self.project_name = project 977 self.exe = exe 978 self.depends = depends 979 self.is_parallel = is_parallel 980 self.cmd_args = cmd_args 981 self.env = env 982 self.should_fail = should_fail 983 self.timeout = timeout 984 self.workdir = workdir 985 self.protocol = TestProtocol.from_str(protocol) 986 self.priority = priority 987 988 def get_exe(self): 989 return self.exe 990 991 def get_name(self): 992 return self.name 993 994class SubprojectHolder(InterpreterObject, ObjectHolder): 995 996 def __init__(self, subinterpreter, subproject_dir, name, warnings=0, disabled_feature=None, 997 exception=None): 998 InterpreterObject.__init__(self) 999 ObjectHolder.__init__(self, subinterpreter) 1000 self.name = name 1001 self.warnings = warnings 1002 self.disabled_feature = disabled_feature 1003 self.exception = exception 1004 self.subproject_dir = subproject_dir 1005 self.methods.update({'get_variable': self.get_variable_method, 1006 'found': self.found_method, 1007 }) 1008 1009 @noPosargs 1010 @permittedKwargs({}) 1011 def found_method(self, args, kwargs): 1012 return self.found() 1013 1014 def found(self): 1015 return self.held_object is not None 1016 1017 @permittedKwargs({}) 1018 @noArgsFlattening 1019 def get_variable_method(self, args, kwargs): 1020 if len(args) < 1 or len(args) > 2: 1021 raise InterpreterException('Get_variable takes one or two arguments.') 1022 if not self.found(): 1023 raise InterpreterException('Subproject "%s/%s" disabled can\'t get_variable on it.' % ( 1024 self.subproject_dir, self.name)) 1025 varname = args[0] 1026 if not isinstance(varname, str): 1027 raise InterpreterException('Get_variable first argument must be a string.') 1028 try: 1029 return self.held_object.variables[varname] 1030 except KeyError: 1031 pass 1032 1033 if len(args) == 2: 1034 return args[1] 1035 1036 raise InvalidArguments('Requested variable "{0}" not found.'.format(varname)) 1037 1038header_permitted_kwargs = set([ 1039 'required', 1040 'prefix', 1041 'no_builtin_args', 1042 'include_directories', 1043 'args', 1044 'dependencies', 1045]) 1046 1047find_library_permitted_kwargs = set([ 1048 'has_headers', 1049 'required', 1050 'dirs', 1051 'static', 1052]) 1053 1054find_library_permitted_kwargs |= set(['header_' + k for k in header_permitted_kwargs]) 1055 1056class CompilerHolder(InterpreterObject): 1057 def __init__(self, compiler, env, subproject): 1058 InterpreterObject.__init__(self) 1059 self.compiler = compiler 1060 self.environment = env 1061 self.subproject = subproject 1062 self.methods.update({'compiles': self.compiles_method, 1063 'links': self.links_method, 1064 'get_id': self.get_id_method, 1065 'get_linker_id': self.get_linker_id_method, 1066 'compute_int': self.compute_int_method, 1067 'sizeof': self.sizeof_method, 1068 'get_define': self.get_define_method, 1069 'check_header': self.check_header_method, 1070 'has_header': self.has_header_method, 1071 'has_header_symbol': self.has_header_symbol_method, 1072 'run': self.run_method, 1073 'has_function': self.has_function_method, 1074 'has_member': self.has_member_method, 1075 'has_members': self.has_members_method, 1076 'has_type': self.has_type_method, 1077 'alignment': self.alignment_method, 1078 'version': self.version_method, 1079 'cmd_array': self.cmd_array_method, 1080 'find_library': self.find_library_method, 1081 'has_argument': self.has_argument_method, 1082 'has_function_attribute': self.has_func_attribute_method, 1083 'get_supported_function_attributes': self.get_supported_function_attributes_method, 1084 'has_multi_arguments': self.has_multi_arguments_method, 1085 'get_supported_arguments': self.get_supported_arguments_method, 1086 'first_supported_argument': self.first_supported_argument_method, 1087 'has_link_argument': self.has_link_argument_method, 1088 'has_multi_link_arguments': self.has_multi_link_arguments_method, 1089 'get_supported_link_arguments': self.get_supported_link_arguments_method, 1090 'first_supported_link_argument': self.first_supported_link_argument_method, 1091 'unittest_args': self.unittest_args_method, 1092 'symbols_have_underscore_prefix': self.symbols_have_underscore_prefix_method, 1093 'get_argument_syntax': self.get_argument_syntax_method, 1094 }) 1095 1096 def _dep_msg(self, deps, endl): 1097 msg_single = 'with dependency {}' 1098 msg_many = 'with dependencies {}' 1099 if not deps: 1100 return endl 1101 if endl is None: 1102 endl = '' 1103 tpl = msg_many if len(deps) > 1 else msg_single 1104 names = [] 1105 for d in deps: 1106 if isinstance(d, dependencies.ExternalLibrary): 1107 name = '-l' + d.name 1108 else: 1109 name = d.name 1110 names.append(name) 1111 return tpl.format(', '.join(names)) + endl 1112 1113 @noPosargs 1114 @permittedKwargs({}) 1115 def version_method(self, args, kwargs): 1116 return self.compiler.version 1117 1118 @noPosargs 1119 @permittedKwargs({}) 1120 def cmd_array_method(self, args, kwargs): 1121 return self.compiler.exelist 1122 1123 def determine_args(self, kwargs, mode='link'): 1124 nobuiltins = kwargs.get('no_builtin_args', False) 1125 if not isinstance(nobuiltins, bool): 1126 raise InterpreterException('Type of no_builtin_args not a boolean.') 1127 args = [] 1128 incdirs = extract_as_list(kwargs, 'include_directories') 1129 for i in incdirs: 1130 if not isinstance(i, IncludeDirsHolder): 1131 raise InterpreterException('Include directories argument must be an include_directories object.') 1132 for idir in i.held_object.get_incdirs(): 1133 idir = os.path.join(self.environment.get_source_dir(), 1134 i.held_object.get_curdir(), idir) 1135 args += self.compiler.get_include_args(idir, False) 1136 if not nobuiltins: 1137 for_machine = Interpreter.machine_from_native_kwarg(kwargs) 1138 opts = self.environment.coredata.compiler_options[for_machine][self.compiler.language] 1139 args += self.compiler.get_option_compile_args(opts) 1140 if mode == 'link': 1141 args += self.compiler.get_option_link_args(opts) 1142 args += mesonlib.stringlistify(kwargs.get('args', [])) 1143 return args 1144 1145 def determine_dependencies(self, kwargs, endl=':'): 1146 deps = kwargs.get('dependencies', None) 1147 if deps is not None: 1148 deps = listify(deps) 1149 final_deps = [] 1150 for d in deps: 1151 try: 1152 d = d.held_object 1153 except Exception: 1154 pass 1155 if isinstance(d, InternalDependency) or not isinstance(d, Dependency): 1156 raise InterpreterException('Dependencies must be external dependencies') 1157 final_deps.append(d) 1158 deps = final_deps 1159 return deps, self._dep_msg(deps, endl) 1160 1161 @permittedKwargs({ 1162 'prefix', 1163 'args', 1164 'dependencies', 1165 }) 1166 def alignment_method(self, args, kwargs): 1167 if len(args) != 1: 1168 raise InterpreterException('Alignment method takes exactly one positional argument.') 1169 check_stringlist(args) 1170 typename = args[0] 1171 prefix = kwargs.get('prefix', '') 1172 if not isinstance(prefix, str): 1173 raise InterpreterException('Prefix argument of alignment must be a string.') 1174 extra_args = mesonlib.stringlistify(kwargs.get('args', [])) 1175 deps, msg = self.determine_dependencies(kwargs) 1176 result = self.compiler.alignment(typename, prefix, self.environment, 1177 extra_args=extra_args, 1178 dependencies=deps) 1179 mlog.log('Checking for alignment of', mlog.bold(typename, True), msg, result) 1180 return result 1181 1182 @permittedKwargs({ 1183 'name', 1184 'no_builtin_args', 1185 'include_directories', 1186 'args', 1187 'dependencies', 1188 }) 1189 def run_method(self, args, kwargs): 1190 if len(args) != 1: 1191 raise InterpreterException('Run method takes exactly one positional argument.') 1192 code = args[0] 1193 if isinstance(code, mesonlib.File): 1194 code = mesonlib.File.from_absolute_file( 1195 code.rel_to_builddir(self.environment.source_dir)) 1196 elif not isinstance(code, str): 1197 raise InvalidArguments('Argument must be string or file.') 1198 testname = kwargs.get('name', '') 1199 if not isinstance(testname, str): 1200 raise InterpreterException('Testname argument must be a string.') 1201 extra_args = functools.partial(self.determine_args, kwargs) 1202 deps, msg = self.determine_dependencies(kwargs, endl=None) 1203 result = self.compiler.run(code, self.environment, extra_args=extra_args, 1204 dependencies=deps) 1205 if len(testname) > 0: 1206 if not result.compiled: 1207 h = mlog.red('DID NOT COMPILE') 1208 elif result.returncode == 0: 1209 h = mlog.green('YES') 1210 else: 1211 h = mlog.red('NO (%d)' % result.returncode) 1212 mlog.log('Checking if', mlog.bold(testname, True), msg, 'runs:', h) 1213 return TryRunResultHolder(result) 1214 1215 @noPosargs 1216 @permittedKwargs({}) 1217 def get_id_method(self, args, kwargs): 1218 return self.compiler.get_id() 1219 1220 @noPosargs 1221 @permittedKwargs({}) 1222 @FeatureNew('compiler.get_linker_id', '0.53.0') 1223 def get_linker_id_method(self, args, kwargs): 1224 return self.compiler.get_linker_id() 1225 1226 @noPosargs 1227 @permittedKwargs({}) 1228 def symbols_have_underscore_prefix_method(self, args, kwargs): 1229 ''' 1230 Check if the compiler prefixes _ (underscore) to global C symbols 1231 See: https://en.wikipedia.org/wiki/Name_mangling#C 1232 ''' 1233 return self.compiler.symbols_have_underscore_prefix(self.environment) 1234 1235 @noPosargs 1236 @permittedKwargs({}) 1237 def unittest_args_method(self, args, kwargs): 1238 ''' 1239 This function is deprecated and should not be used. 1240 It can be removed in a future version of Meson. 1241 ''' 1242 if not hasattr(self.compiler, 'get_feature_args'): 1243 raise InterpreterException('This {} compiler has no feature arguments.'.format(self.compiler.get_display_language())) 1244 build_to_src = os.path.relpath(self.environment.get_source_dir(), self.environment.get_build_dir()) 1245 return self.compiler.get_feature_args({'unittest': 'true'}, build_to_src) 1246 1247 @permittedKwargs({ 1248 'prefix', 1249 'no_builtin_args', 1250 'include_directories', 1251 'args', 1252 'dependencies', 1253 }) 1254 def has_member_method(self, args, kwargs): 1255 if len(args) != 2: 1256 raise InterpreterException('Has_member takes exactly two arguments.') 1257 check_stringlist(args) 1258 typename, membername = args 1259 prefix = kwargs.get('prefix', '') 1260 if not isinstance(prefix, str): 1261 raise InterpreterException('Prefix argument of has_member must be a string.') 1262 extra_args = functools.partial(self.determine_args, kwargs) 1263 deps, msg = self.determine_dependencies(kwargs) 1264 had, cached = self.compiler.has_members(typename, [membername], prefix, 1265 self.environment, 1266 extra_args=extra_args, 1267 dependencies=deps) 1268 cached = mlog.blue('(cached)') if cached else '' 1269 if had: 1270 hadtxt = mlog.green('YES') 1271 else: 1272 hadtxt = mlog.red('NO') 1273 mlog.log('Checking whether type', mlog.bold(typename, True), 1274 'has member', mlog.bold(membername, True), msg, hadtxt, cached) 1275 return had 1276 1277 @permittedKwargs({ 1278 'prefix', 1279 'no_builtin_args', 1280 'include_directories', 1281 'args', 1282 'dependencies', 1283 }) 1284 def has_members_method(self, args, kwargs): 1285 if len(args) < 2: 1286 raise InterpreterException('Has_members needs at least two arguments.') 1287 check_stringlist(args) 1288 typename, *membernames = args 1289 prefix = kwargs.get('prefix', '') 1290 if not isinstance(prefix, str): 1291 raise InterpreterException('Prefix argument of has_members must be a string.') 1292 extra_args = functools.partial(self.determine_args, kwargs) 1293 deps, msg = self.determine_dependencies(kwargs) 1294 had, cached = self.compiler.has_members(typename, membernames, prefix, 1295 self.environment, 1296 extra_args=extra_args, 1297 dependencies=deps) 1298 cached = mlog.blue('(cached)') if cached else '' 1299 if had: 1300 hadtxt = mlog.green('YES') 1301 else: 1302 hadtxt = mlog.red('NO') 1303 members = mlog.bold(', '.join(['"{}"'.format(m) for m in membernames])) 1304 mlog.log('Checking whether type', mlog.bold(typename, True), 1305 'has members', members, msg, hadtxt, cached) 1306 return had 1307 1308 @permittedKwargs({ 1309 'prefix', 1310 'no_builtin_args', 1311 'include_directories', 1312 'args', 1313 'dependencies', 1314 }) 1315 def has_function_method(self, args, kwargs): 1316 if len(args) != 1: 1317 raise InterpreterException('Has_function takes exactly one argument.') 1318 check_stringlist(args) 1319 funcname = args[0] 1320 prefix = kwargs.get('prefix', '') 1321 if not isinstance(prefix, str): 1322 raise InterpreterException('Prefix argument of has_function must be a string.') 1323 extra_args = self.determine_args(kwargs) 1324 deps, msg = self.determine_dependencies(kwargs) 1325 had, cached = self.compiler.has_function(funcname, prefix, self.environment, 1326 extra_args=extra_args, 1327 dependencies=deps) 1328 cached = mlog.blue('(cached)') if cached else '' 1329 if had: 1330 hadtxt = mlog.green('YES') 1331 else: 1332 hadtxt = mlog.red('NO') 1333 mlog.log('Checking for function', mlog.bold(funcname, True), msg, hadtxt, cached) 1334 return had 1335 1336 @permittedKwargs({ 1337 'prefix', 1338 'no_builtin_args', 1339 'include_directories', 1340 'args', 1341 'dependencies', 1342 }) 1343 def has_type_method(self, args, kwargs): 1344 if len(args) != 1: 1345 raise InterpreterException('Has_type takes exactly one argument.') 1346 check_stringlist(args) 1347 typename = args[0] 1348 prefix = kwargs.get('prefix', '') 1349 if not isinstance(prefix, str): 1350 raise InterpreterException('Prefix argument of has_type must be a string.') 1351 extra_args = functools.partial(self.determine_args, kwargs) 1352 deps, msg = self.determine_dependencies(kwargs) 1353 had, cached = self.compiler.has_type(typename, prefix, self.environment, 1354 extra_args=extra_args, dependencies=deps) 1355 cached = mlog.blue('(cached)') if cached else '' 1356 if had: 1357 hadtxt = mlog.green('YES') 1358 else: 1359 hadtxt = mlog.red('NO') 1360 mlog.log('Checking for type', mlog.bold(typename, True), msg, hadtxt, cached) 1361 return had 1362 1363 @FeatureNew('compiler.compute_int', '0.40.0') 1364 @permittedKwargs({ 1365 'prefix', 1366 'low', 1367 'high', 1368 'guess', 1369 'no_builtin_args', 1370 'include_directories', 1371 'args', 1372 'dependencies', 1373 }) 1374 def compute_int_method(self, args, kwargs): 1375 if len(args) != 1: 1376 raise InterpreterException('Compute_int takes exactly one argument.') 1377 check_stringlist(args) 1378 expression = args[0] 1379 prefix = kwargs.get('prefix', '') 1380 low = kwargs.get('low', None) 1381 high = kwargs.get('high', None) 1382 guess = kwargs.get('guess', None) 1383 if not isinstance(prefix, str): 1384 raise InterpreterException('Prefix argument of compute_int must be a string.') 1385 if low is not None and not isinstance(low, int): 1386 raise InterpreterException('Low argument of compute_int must be an int.') 1387 if high is not None and not isinstance(high, int): 1388 raise InterpreterException('High argument of compute_int must be an int.') 1389 if guess is not None and not isinstance(guess, int): 1390 raise InterpreterException('Guess argument of compute_int must be an int.') 1391 extra_args = functools.partial(self.determine_args, kwargs) 1392 deps, msg = self.determine_dependencies(kwargs) 1393 res = self.compiler.compute_int(expression, low, high, guess, prefix, 1394 self.environment, extra_args=extra_args, 1395 dependencies=deps) 1396 mlog.log('Computing int of', mlog.bold(expression, True), msg, res) 1397 return res 1398 1399 @permittedKwargs({ 1400 'prefix', 1401 'no_builtin_args', 1402 'include_directories', 1403 'args', 1404 'dependencies', 1405 }) 1406 def sizeof_method(self, args, kwargs): 1407 if len(args) != 1: 1408 raise InterpreterException('Sizeof takes exactly one argument.') 1409 check_stringlist(args) 1410 element = args[0] 1411 prefix = kwargs.get('prefix', '') 1412 if not isinstance(prefix, str): 1413 raise InterpreterException('Prefix argument of sizeof must be a string.') 1414 extra_args = functools.partial(self.determine_args, kwargs) 1415 deps, msg = self.determine_dependencies(kwargs) 1416 esize = self.compiler.sizeof(element, prefix, self.environment, 1417 extra_args=extra_args, dependencies=deps) 1418 mlog.log('Checking for size of', mlog.bold(element, True), msg, esize) 1419 return esize 1420 1421 @FeatureNew('compiler.get_define', '0.40.0') 1422 @permittedKwargs({ 1423 'prefix', 1424 'no_builtin_args', 1425 'include_directories', 1426 'args', 1427 'dependencies', 1428 }) 1429 def get_define_method(self, args, kwargs): 1430 if len(args) != 1: 1431 raise InterpreterException('get_define() takes exactly one argument.') 1432 check_stringlist(args) 1433 element = args[0] 1434 prefix = kwargs.get('prefix', '') 1435 if not isinstance(prefix, str): 1436 raise InterpreterException('Prefix argument of get_define() must be a string.') 1437 extra_args = functools.partial(self.determine_args, kwargs) 1438 deps, msg = self.determine_dependencies(kwargs) 1439 value, cached = self.compiler.get_define(element, prefix, self.environment, 1440 extra_args=extra_args, 1441 dependencies=deps) 1442 cached = mlog.blue('(cached)') if cached else '' 1443 mlog.log('Fetching value of define', mlog.bold(element, True), msg, value, cached) 1444 return value 1445 1446 @permittedKwargs({ 1447 'name', 1448 'no_builtin_args', 1449 'include_directories', 1450 'args', 1451 'dependencies', 1452 }) 1453 def compiles_method(self, args, kwargs): 1454 if len(args) != 1: 1455 raise InterpreterException('compiles method takes exactly one argument.') 1456 code = args[0] 1457 if isinstance(code, mesonlib.File): 1458 code = mesonlib.File.from_absolute_file( 1459 code.rel_to_builddir(self.environment.source_dir)) 1460 elif not isinstance(code, str): 1461 raise InvalidArguments('Argument must be string or file.') 1462 testname = kwargs.get('name', '') 1463 if not isinstance(testname, str): 1464 raise InterpreterException('Testname argument must be a string.') 1465 extra_args = functools.partial(self.determine_args, kwargs) 1466 deps, msg = self.determine_dependencies(kwargs, endl=None) 1467 result, cached = self.compiler.compiles(code, self.environment, 1468 extra_args=extra_args, 1469 dependencies=deps) 1470 if len(testname) > 0: 1471 if result: 1472 h = mlog.green('YES') 1473 else: 1474 h = mlog.red('NO') 1475 cached = mlog.blue('(cached)') if cached else '' 1476 mlog.log('Checking if', mlog.bold(testname, True), msg, 'compiles:', h, cached) 1477 return result 1478 1479 @permittedKwargs({ 1480 'name', 1481 'no_builtin_args', 1482 'include_directories', 1483 'args', 1484 'dependencies', 1485 }) 1486 def links_method(self, args, kwargs): 1487 if len(args) != 1: 1488 raise InterpreterException('links method takes exactly one argument.') 1489 code = args[0] 1490 if isinstance(code, mesonlib.File): 1491 code = mesonlib.File.from_absolute_file( 1492 code.rel_to_builddir(self.environment.source_dir)) 1493 elif not isinstance(code, str): 1494 raise InvalidArguments('Argument must be string or file.') 1495 testname = kwargs.get('name', '') 1496 if not isinstance(testname, str): 1497 raise InterpreterException('Testname argument must be a string.') 1498 extra_args = functools.partial(self.determine_args, kwargs) 1499 deps, msg = self.determine_dependencies(kwargs, endl=None) 1500 result, cached = self.compiler.links(code, self.environment, 1501 extra_args=extra_args, 1502 dependencies=deps) 1503 cached = mlog.blue('(cached)') if cached else '' 1504 if len(testname) > 0: 1505 if result: 1506 h = mlog.green('YES') 1507 else: 1508 h = mlog.red('NO') 1509 mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached) 1510 return result 1511 1512 @FeatureNew('compiler.check_header', '0.47.0') 1513 @FeatureNewKwargs('compiler.check_header', '0.50.0', ['required']) 1514 @permittedKwargs(header_permitted_kwargs) 1515 def check_header_method(self, args, kwargs): 1516 if len(args) != 1: 1517 raise InterpreterException('check_header method takes exactly one argument.') 1518 check_stringlist(args) 1519 hname = args[0] 1520 prefix = kwargs.get('prefix', '') 1521 if not isinstance(prefix, str): 1522 raise InterpreterException('Prefix argument of has_header must be a string.') 1523 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) 1524 if disabled: 1525 mlog.log('Check usable header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled') 1526 return False 1527 extra_args = functools.partial(self.determine_args, kwargs) 1528 deps, msg = self.determine_dependencies(kwargs) 1529 haz, cached = self.compiler.check_header(hname, prefix, self.environment, 1530 extra_args=extra_args, 1531 dependencies=deps) 1532 cached = mlog.blue('(cached)') if cached else '' 1533 if required and not haz: 1534 raise InterpreterException('{} header {!r} not usable'.format(self.compiler.get_display_language(), hname)) 1535 elif haz: 1536 h = mlog.green('YES') 1537 else: 1538 h = mlog.red('NO') 1539 mlog.log('Check usable header', mlog.bold(hname, True), msg, h, cached) 1540 return haz 1541 1542 @FeatureNewKwargs('compiler.has_header', '0.50.0', ['required']) 1543 @permittedKwargs(header_permitted_kwargs) 1544 def has_header_method(self, args, kwargs): 1545 if len(args) != 1: 1546 raise InterpreterException('has_header method takes exactly one argument.') 1547 check_stringlist(args) 1548 hname = args[0] 1549 prefix = kwargs.get('prefix', '') 1550 if not isinstance(prefix, str): 1551 raise InterpreterException('Prefix argument of has_header must be a string.') 1552 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) 1553 if disabled: 1554 mlog.log('Has header', mlog.bold(hname, True), 'skipped: feature', mlog.bold(feature), 'disabled') 1555 return False 1556 extra_args = functools.partial(self.determine_args, kwargs) 1557 deps, msg = self.determine_dependencies(kwargs) 1558 haz, cached = self.compiler.has_header(hname, prefix, self.environment, 1559 extra_args=extra_args, dependencies=deps) 1560 cached = mlog.blue('(cached)') if cached else '' 1561 if required and not haz: 1562 raise InterpreterException('{} header {!r} not found'.format(self.compiler.get_display_language(), hname)) 1563 elif haz: 1564 h = mlog.green('YES') 1565 else: 1566 h = mlog.red('NO') 1567 mlog.log('Has header', mlog.bold(hname, True), msg, h, cached) 1568 return haz 1569 1570 @FeatureNewKwargs('compiler.has_header_symbol', '0.50.0', ['required']) 1571 @permittedKwargs(header_permitted_kwargs) 1572 def has_header_symbol_method(self, args, kwargs): 1573 if len(args) != 2: 1574 raise InterpreterException('has_header_symbol method takes exactly two arguments.') 1575 check_stringlist(args) 1576 hname, symbol = args 1577 prefix = kwargs.get('prefix', '') 1578 if not isinstance(prefix, str): 1579 raise InterpreterException('Prefix argument of has_header_symbol must be a string.') 1580 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) 1581 if disabled: 1582 mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), 'skipped: feature', mlog.bold(feature), 'disabled') 1583 return False 1584 extra_args = functools.partial(self.determine_args, kwargs) 1585 deps, msg = self.determine_dependencies(kwargs) 1586 haz, cached = self.compiler.has_header_symbol(hname, symbol, prefix, self.environment, 1587 extra_args=extra_args, 1588 dependencies=deps) 1589 if required and not haz: 1590 raise InterpreterException('{} symbol {} not found in header {}'.format(self.compiler.get_display_language(), symbol, hname)) 1591 elif haz: 1592 h = mlog.green('YES') 1593 else: 1594 h = mlog.red('NO') 1595 cached = mlog.blue('(cached)') if cached else '' 1596 mlog.log('Header <{0}> has symbol'.format(hname), mlog.bold(symbol, True), msg, h, cached) 1597 return haz 1598 1599 def notfound_library(self, libname): 1600 lib = dependencies.ExternalLibrary(libname, None, 1601 self.environment, 1602 self.compiler.language, 1603 silent=True) 1604 return ExternalLibraryHolder(lib, self.subproject) 1605 1606 @FeatureNewKwargs('compiler.find_library', '0.51.0', ['static']) 1607 @FeatureNewKwargs('compiler.find_library', '0.50.0', ['has_headers']) 1608 @FeatureNewKwargs('compiler.find_library', '0.49.0', ['disabler']) 1609 @disablerIfNotFound 1610 @permittedKwargs(find_library_permitted_kwargs) 1611 def find_library_method(self, args, kwargs): 1612 # TODO add dependencies support? 1613 if len(args) != 1: 1614 raise InterpreterException('find_library method takes one argument.') 1615 libname = args[0] 1616 if not isinstance(libname, str): 1617 raise InterpreterException('Library name not a string.') 1618 1619 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) 1620 if disabled: 1621 mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled') 1622 return self.notfound_library(libname) 1623 1624 has_header_kwargs = {k[7:]: v for k, v in kwargs.items() if k.startswith('header_')} 1625 has_header_kwargs['required'] = required 1626 headers = mesonlib.stringlistify(kwargs.get('has_headers', [])) 1627 for h in headers: 1628 if not self.has_header_method([h], has_header_kwargs): 1629 return self.notfound_library(libname) 1630 1631 search_dirs = extract_search_dirs(kwargs) 1632 1633 libtype = mesonlib.LibType.PREFER_SHARED 1634 if 'static' in kwargs: 1635 if not isinstance(kwargs['static'], bool): 1636 raise InterpreterException('static must be a boolean') 1637 libtype = mesonlib.LibType.STATIC if kwargs['static'] else mesonlib.LibType.SHARED 1638 linkargs = self.compiler.find_library(libname, self.environment, search_dirs, libtype) 1639 if required and not linkargs: 1640 if libtype == mesonlib.LibType.PREFER_SHARED: 1641 libtype = 'shared or static' 1642 else: 1643 libtype = libtype.name.lower() 1644 raise InterpreterException('{} {} library {!r} not found' 1645 .format(self.compiler.get_display_language(), 1646 libtype, libname)) 1647 lib = dependencies.ExternalLibrary(libname, linkargs, self.environment, 1648 self.compiler.language) 1649 return ExternalLibraryHolder(lib, self.subproject) 1650 1651 @permittedKwargs({}) 1652 def has_argument_method(self, args: T.Sequence[str], kwargs) -> bool: 1653 args = mesonlib.stringlistify(args) 1654 if len(args) != 1: 1655 raise InterpreterException('has_argument takes exactly one argument.') 1656 return self.has_multi_arguments_method(args, kwargs) 1657 1658 @permittedKwargs({}) 1659 def has_multi_arguments_method(self, args: T.Sequence[str], kwargs: dict): 1660 args = mesonlib.stringlistify(args) 1661 result, cached = self.compiler.has_multi_arguments(args, self.environment) 1662 if result: 1663 h = mlog.green('YES') 1664 else: 1665 h = mlog.red('NO') 1666 cached = mlog.blue('(cached)') if cached else '' 1667 mlog.log( 1668 'Compiler for {} supports arguments {}:'.format( 1669 self.compiler.get_display_language(), ' '.join(args)), 1670 h, cached) 1671 return result 1672 1673 @FeatureNew('compiler.get_supported_arguments', '0.43.0') 1674 @permittedKwargs({}) 1675 def get_supported_arguments_method(self, args, kwargs): 1676 args = mesonlib.stringlistify(args) 1677 supported_args = [] 1678 for arg in args: 1679 if self.has_argument_method(arg, kwargs): 1680 supported_args.append(arg) 1681 return supported_args 1682 1683 @permittedKwargs({}) 1684 def first_supported_argument_method(self, args: T.Sequence[str], kwargs: dict) -> T.List[str]: 1685 for arg in mesonlib.stringlistify(args): 1686 if self.has_argument_method(arg, kwargs): 1687 mlog.log('First supported argument:', mlog.bold(arg)) 1688 return [arg] 1689 mlog.log('First supported argument:', mlog.red('None')) 1690 return [] 1691 1692 @FeatureNew('compiler.has_link_argument', '0.46.0') 1693 @permittedKwargs({}) 1694 def has_link_argument_method(self, args, kwargs): 1695 args = mesonlib.stringlistify(args) 1696 if len(args) != 1: 1697 raise InterpreterException('has_link_argument takes exactly one argument.') 1698 return self.has_multi_link_arguments_method(args, kwargs) 1699 1700 @FeatureNew('compiler.has_multi_link_argument', '0.46.0') 1701 @permittedKwargs({}) 1702 def has_multi_link_arguments_method(self, args, kwargs): 1703 args = mesonlib.stringlistify(args) 1704 result, cached = self.compiler.has_multi_link_arguments(args, self.environment) 1705 cached = mlog.blue('(cached)') if cached else '' 1706 if result: 1707 h = mlog.green('YES') 1708 else: 1709 h = mlog.red('NO') 1710 mlog.log( 1711 'Compiler for {} supports link arguments {}:'.format( 1712 self.compiler.get_display_language(), ' '.join(args)), 1713 h, cached) 1714 return result 1715 1716 @FeatureNew('compiler.get_supported_link_arguments_method', '0.46.0') 1717 @permittedKwargs({}) 1718 def get_supported_link_arguments_method(self, args, kwargs): 1719 args = mesonlib.stringlistify(args) 1720 supported_args = [] 1721 for arg in args: 1722 if self.has_link_argument_method(arg, kwargs): 1723 supported_args.append(arg) 1724 return supported_args 1725 1726 @FeatureNew('compiler.first_supported_link_argument_method', '0.46.0') 1727 @permittedKwargs({}) 1728 def first_supported_link_argument_method(self, args, kwargs): 1729 for i in mesonlib.stringlistify(args): 1730 if self.has_link_argument_method(i, kwargs): 1731 mlog.log('First supported link argument:', mlog.bold(i)) 1732 return [i] 1733 mlog.log('First supported link argument:', mlog.red('None')) 1734 return [] 1735 1736 @FeatureNew('compiler.has_function_attribute', '0.48.0') 1737 @permittedKwargs({}) 1738 def has_func_attribute_method(self, args, kwargs): 1739 args = mesonlib.stringlistify(args) 1740 if len(args) != 1: 1741 raise InterpreterException('has_func_attribute takes exactly one argument.') 1742 result, cached = self.compiler.has_func_attribute(args[0], self.environment) 1743 cached = mlog.blue('(cached)') if cached else '' 1744 h = mlog.green('YES') if result else mlog.red('NO') 1745 mlog.log('Compiler for {} supports function attribute {}:'.format(self.compiler.get_display_language(), args[0]), h, cached) 1746 return result 1747 1748 @FeatureNew('compiler.get_supported_function_attributes', '0.48.0') 1749 @permittedKwargs({}) 1750 def get_supported_function_attributes_method(self, args, kwargs): 1751 args = mesonlib.stringlistify(args) 1752 return [a for a in args if self.has_func_attribute_method(a, kwargs)] 1753 1754 @FeatureNew('compiler.get_argument_syntax_method', '0.49.0') 1755 @noPosargs 1756 @noKwargs 1757 def get_argument_syntax_method(self, args, kwargs): 1758 return self.compiler.get_argument_syntax() 1759 1760 1761ModuleState = collections.namedtuple('ModuleState', [ 1762 'source_root', 'build_to_src', 'subproject', 'subdir', 'current_lineno', 'environment', 1763 'project_name', 'project_version', 'backend', 'targets', 1764 'data', 'headers', 'man', 'global_args', 'project_args', 'build_machine', 1765 'host_machine', 'target_machine', 'current_node']) 1766 1767class ModuleHolder(InterpreterObject, ObjectHolder): 1768 def __init__(self, modname, module, interpreter): 1769 InterpreterObject.__init__(self) 1770 ObjectHolder.__init__(self, module) 1771 self.modname = modname 1772 self.interpreter = interpreter 1773 1774 def method_call(self, method_name, args, kwargs): 1775 try: 1776 fn = getattr(self.held_object, method_name) 1777 except AttributeError: 1778 raise InvalidArguments('Module %s does not have method %s.' % (self.modname, method_name)) 1779 if method_name.startswith('_'): 1780 raise InvalidArguments('Function {!r} in module {!r} is private.'.format(method_name, self.modname)) 1781 if not getattr(fn, 'no-args-flattening', False): 1782 args = flatten(args) 1783 # This is not 100% reliable but we can't use hash() 1784 # because the Build object contains dicts and lists. 1785 num_targets = len(self.interpreter.build.targets) 1786 state = ModuleState( 1787 source_root = self.interpreter.environment.get_source_dir(), 1788 build_to_src=mesonlib.relpath(self.interpreter.environment.get_source_dir(), 1789 self.interpreter.environment.get_build_dir()), 1790 subproject=self.interpreter.subproject, 1791 subdir=self.interpreter.subdir, 1792 current_lineno=self.interpreter.current_lineno, 1793 environment=self.interpreter.environment, 1794 project_name=self.interpreter.build.project_name, 1795 project_version=self.interpreter.build.dep_manifest[self.interpreter.active_projectname], 1796 # The backend object is under-used right now, but we will need it: 1797 # https://github.com/mesonbuild/meson/issues/1419 1798 backend=self.interpreter.backend, 1799 targets=self.interpreter.build.targets, 1800 data=self.interpreter.build.data, 1801 headers=self.interpreter.build.get_headers(), 1802 man=self.interpreter.build.get_man(), 1803 #global_args_for_build = self.interpreter.build.global_args.build, 1804 global_args = self.interpreter.build.global_args.host, 1805 #project_args_for_build = self.interpreter.build.projects_args.build.get(self.interpreter.subproject, {}), 1806 project_args = self.interpreter.build.projects_args.host.get(self.interpreter.subproject, {}), 1807 build_machine=self.interpreter.builtin['build_machine'].held_object, 1808 host_machine=self.interpreter.builtin['host_machine'].held_object, 1809 target_machine=self.interpreter.builtin['target_machine'].held_object, 1810 current_node=self.current_node 1811 ) 1812 # Many modules do for example self.interpreter.find_program_impl(), 1813 # so we have to ensure they use the current interpreter and not the one 1814 # that first imported that module, otherwise it will use outdated 1815 # overrides. 1816 self.held_object.interpreter = self.interpreter 1817 if self.held_object.is_snippet(method_name): 1818 value = fn(self.interpreter, state, args, kwargs) 1819 return self.interpreter.holderify(value) 1820 else: 1821 value = fn(state, args, kwargs) 1822 if num_targets != len(self.interpreter.build.targets): 1823 raise InterpreterException('Extension module altered internal state illegally.') 1824 return self.interpreter.module_method_callback(value) 1825 1826 1827class Summary: 1828 def __init__(self, project_name, project_version): 1829 self.project_name = project_name 1830 self.project_version = project_version 1831 self.sections = collections.defaultdict(dict) 1832 self.max_key_len = 0 1833 1834 def add_section(self, section, values, kwargs): 1835 bool_yn = kwargs.get('bool_yn', False) 1836 if not isinstance(bool_yn, bool): 1837 raise InterpreterException('bool_yn keyword argument must be boolean') 1838 list_sep = kwargs.get('list_sep') 1839 if list_sep is not None and not isinstance(list_sep, str): 1840 raise InterpreterException('list_sep keyword argument must be string') 1841 for k, v in values.items(): 1842 if k in self.sections[section]: 1843 raise InterpreterException('Summary section {!r} already have key {!r}'.format(section, k)) 1844 formatted_values = [] 1845 for i in listify(v): 1846 if not isinstance(i, (str, int)): 1847 m = 'Summary value in section {!r}, key {!r}, must be string, integer or boolean' 1848 raise InterpreterException(m.format(section, k)) 1849 if bool_yn and isinstance(i, bool): 1850 formatted_values.append(mlog.green('YES') if i else mlog.red('NO')) 1851 else: 1852 formatted_values.append(i) 1853 self.sections[section][k] = (formatted_values, list_sep) 1854 self.max_key_len = max(self.max_key_len, len(k)) 1855 1856 def dump(self): 1857 mlog.log(self.project_name, mlog.normal_cyan(self.project_version)) 1858 for section, values in self.sections.items(): 1859 mlog.log('') # newline 1860 if section: 1861 mlog.log(' ', mlog.bold(section)) 1862 for k, v in values.items(): 1863 v, list_sep = v 1864 indent = self.max_key_len - len(k) + 3 1865 end = ' ' if v else '' 1866 mlog.log(' ' * indent, k + ':', end=end) 1867 if list_sep is None: 1868 indent = self.max_key_len + 6 1869 list_sep = '\n' + ' ' * indent 1870 mlog.log(*v, sep=list_sep) 1871 mlog.log('') # newline 1872 1873 1874class MesonMain(InterpreterObject): 1875 def __init__(self, build, interpreter): 1876 InterpreterObject.__init__(self) 1877 self.build = build 1878 self.interpreter = interpreter 1879 self._found_source_scripts = {} 1880 self.methods.update({'get_compiler': self.get_compiler_method, 1881 'is_cross_build': self.is_cross_build_method, 1882 'has_exe_wrapper': self.has_exe_wrapper_method, 1883 'can_run_host_binaries': self.can_run_host_binaries_method, 1884 'is_unity': self.is_unity_method, 1885 'is_subproject': self.is_subproject_method, 1886 'current_source_dir': self.current_source_dir_method, 1887 'current_build_dir': self.current_build_dir_method, 1888 'source_root': self.source_root_method, 1889 'build_root': self.build_root_method, 1890 'add_install_script': self.add_install_script_method, 1891 'add_postconf_script': self.add_postconf_script_method, 1892 'add_dist_script': self.add_dist_script_method, 1893 'install_dependency_manifest': self.install_dependency_manifest_method, 1894 'override_dependency': self.override_dependency_method, 1895 'override_find_program': self.override_find_program_method, 1896 'project_version': self.project_version_method, 1897 'project_license': self.project_license_method, 1898 'version': self.version_method, 1899 'project_name': self.project_name_method, 1900 'get_cross_property': self.get_cross_property_method, 1901 'get_external_property': self.get_external_property_method, 1902 'backend': self.backend_method, 1903 }) 1904 1905 def _find_source_script(self, prog: T.Union[str, ExecutableHolder], args): 1906 if isinstance(prog, ExecutableHolder): 1907 prog_path = self.interpreter.backend.get_target_filename(prog.held_object) 1908 return build.RunScript([prog_path], args) 1909 elif isinstance(prog, ExternalProgramHolder): 1910 return build.RunScript(prog.get_command(), args) 1911 1912 # Prefer scripts in the current source directory 1913 search_dir = os.path.join(self.interpreter.environment.source_dir, 1914 self.interpreter.subdir) 1915 key = (prog, search_dir) 1916 if key in self._found_source_scripts: 1917 found = self._found_source_scripts[key] 1918 else: 1919 found = dependencies.ExternalProgram(prog, search_dir=search_dir) 1920 if found.found(): 1921 self._found_source_scripts[key] = found 1922 else: 1923 m = 'Script or command {!r} not found or not executable' 1924 raise InterpreterException(m.format(prog)) 1925 return build.RunScript(found.get_command(), args) 1926 1927 def _process_script_args( 1928 self, name: str, args: T.List[T.Union[ 1929 str, mesonlib.File, CustomTargetHolder, 1930 CustomTargetIndexHolder, ConfigureFileHolder, 1931 ExternalProgramHolder, ExecutableHolder, 1932 ]], allow_built: bool = False) -> T.List[str]: 1933 script_args = [] # T.List[str] 1934 new = False 1935 for a in args: 1936 a = unholder(a) 1937 if isinstance(a, str): 1938 script_args.append(a) 1939 elif isinstance(a, mesonlib.File): 1940 new = True 1941 script_args.append(a.rel_to_builddir(self.interpreter.environment.source_dir)) 1942 elif isinstance(a, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): 1943 if not allow_built: 1944 raise InterpreterException('Arguments to {} cannot be built'.format(name)) 1945 new = True 1946 script_args.extend([os.path.join(a.get_subdir(), o) for o in a.get_outputs()]) 1947 1948 # This feels really hacky, but I'm not sure how else to fix 1949 # this without completely rewriting install script handling. 1950 # This is complicated by the fact that the install target 1951 # depends on all. 1952 if isinstance(a, build.CustomTargetIndex): 1953 a.target.build_by_default = True 1954 else: 1955 a.build_by_default = True 1956 elif isinstance(a, build.ConfigureFile): 1957 new = True 1958 script_args.append(os.path.join(a.subdir, a.targetname)) 1959 elif isinstance(a, dependencies.ExternalProgram): 1960 script_args.extend(a.command) 1961 new = True 1962 else: 1963 raise InterpreterException( 1964 'Arguments to {} must be strings, Files, CustomTargets, ' 1965 'Indexes of CustomTargets, or ConfigureFiles'.format(name)) 1966 if new: 1967 FeatureNew.single_use( 1968 'Calling "{}" with File, CustomTaget, Index of CustomTarget, ' 1969 'ConfigureFile, Executable, or ExternalProgram'.format(name), 1970 '0.55.0', self.interpreter.subproject) 1971 return script_args 1972 1973 @permittedKwargs(set()) 1974 def add_install_script_method(self, args: 'T.Tuple[T.Union[str, ExecutableHolder], T.Union[str, mesonlib.File, CustomTargetHolder, CustomTargetIndexHolder, ConfigureFileHolder], ...]', kwargs): 1975 if len(args) < 1: 1976 raise InterpreterException('add_install_script takes one or more arguments') 1977 script_args = self._process_script_args('add_install_script', args[1:], allow_built=True) 1978 script = self._find_source_script(args[0], script_args) 1979 self.build.install_scripts.append(script) 1980 1981 @permittedKwargs(set()) 1982 def add_postconf_script_method(self, args, kwargs): 1983 if len(args) < 1: 1984 raise InterpreterException('add_postconf_script takes one or more arguments') 1985 script_args = self._process_script_args('add_postconf_script', args[1:], allow_built=True) 1986 script = self._find_source_script(args[0], script_args) 1987 self.build.postconf_scripts.append(script) 1988 1989 @permittedKwargs(set()) 1990 def add_dist_script_method(self, args, kwargs): 1991 if len(args) < 1: 1992 raise InterpreterException('add_dist_script takes one or more arguments') 1993 if len(args) > 1: 1994 FeatureNew.single_use('Calling "add_dist_script" with multiple arguments', 1995 '0.49.0', self.interpreter.subproject) 1996 if self.interpreter.subproject != '': 1997 raise InterpreterException('add_dist_script may not be used in a subproject.') 1998 script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True) 1999 script = self._find_source_script(args[0], script_args) 2000 self.build.dist_scripts.append(script) 2001 2002 @noPosargs 2003 @permittedKwargs({}) 2004 def current_source_dir_method(self, args, kwargs): 2005 src = self.interpreter.environment.source_dir 2006 sub = self.interpreter.subdir 2007 if sub == '': 2008 return src 2009 return os.path.join(src, sub) 2010 2011 @noPosargs 2012 @permittedKwargs({}) 2013 def current_build_dir_method(self, args, kwargs): 2014 src = self.interpreter.environment.build_dir 2015 sub = self.interpreter.subdir 2016 if sub == '': 2017 return src 2018 return os.path.join(src, sub) 2019 2020 @noPosargs 2021 @permittedKwargs({}) 2022 def backend_method(self, args, kwargs): 2023 return self.interpreter.backend.name 2024 2025 @noPosargs 2026 @permittedKwargs({}) 2027 def source_root_method(self, args, kwargs): 2028 return self.interpreter.environment.source_dir 2029 2030 @noPosargs 2031 @permittedKwargs({}) 2032 def build_root_method(self, args, kwargs): 2033 return self.interpreter.environment.build_dir 2034 2035 @noPosargs 2036 @permittedKwargs({}) 2037 @FeatureDeprecated('meson.has_exe_wrapper', '0.55.0', 'use meson.can_run_host_binaries instead.') 2038 def has_exe_wrapper_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: 2039 return self.can_run_host_binaries_impl(args, kwargs) 2040 2041 @noPosargs 2042 @permittedKwargs({}) 2043 @FeatureNew('meson.can_run_host_binaries', '0.55.0') 2044 def can_run_host_binaries_method(self, args: T.Tuple[object, ...], kwargs: T.Dict[str, object]) -> bool: 2045 return self.can_run_host_binaries_impl(args, kwargs) 2046 2047 def can_run_host_binaries_impl(self, args, kwargs): 2048 if (self.is_cross_build_method(None, None) and 2049 self.build.environment.need_exe_wrapper()): 2050 if self.build.environment.exe_wrapper is None: 2051 return False 2052 # We return True when exe_wrap is defined, when it's not needed, and 2053 # when we're compiling natively. The last two are semantically confusing. 2054 # Need to revisit this. 2055 return True 2056 2057 @noPosargs 2058 @permittedKwargs({}) 2059 def is_cross_build_method(self, args, kwargs): 2060 return self.build.environment.is_cross_build() 2061 2062 @permittedKwargs({'native'}) 2063 def get_compiler_method(self, args, kwargs): 2064 if len(args) != 1: 2065 raise InterpreterException('get_compiler_method must have one and only one argument.') 2066 cname = args[0] 2067 for_machine = Interpreter.machine_from_native_kwarg(kwargs) 2068 clist = self.interpreter.coredata.compilers[for_machine] 2069 if cname in clist: 2070 return CompilerHolder(clist[cname], self.build.environment, self.interpreter.subproject) 2071 raise InterpreterException('Tried to access compiler for unspecified language "%s".' % cname) 2072 2073 @noPosargs 2074 @permittedKwargs({}) 2075 def is_unity_method(self, args, kwargs): 2076 optval = self.interpreter.environment.coredata.get_builtin_option('unity') 2077 if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()): 2078 return True 2079 return False 2080 2081 @noPosargs 2082 @permittedKwargs({}) 2083 def is_subproject_method(self, args, kwargs): 2084 return self.interpreter.is_subproject() 2085 2086 @permittedKwargs({}) 2087 def install_dependency_manifest_method(self, args, kwargs): 2088 if len(args) != 1: 2089 raise InterpreterException('Must specify manifest install file name') 2090 if not isinstance(args[0], str): 2091 raise InterpreterException('Argument must be a string.') 2092 self.build.dep_manifest_name = args[0] 2093 2094 @FeatureNew('meson.override_find_program', '0.46.0') 2095 @permittedKwargs({}) 2096 def override_find_program_method(self, args, kwargs): 2097 if len(args) != 2: 2098 raise InterpreterException('Override needs two arguments') 2099 name, exe = args 2100 if not isinstance(name, str): 2101 raise InterpreterException('First argument must be a string') 2102 if hasattr(exe, 'held_object'): 2103 exe = exe.held_object 2104 if isinstance(exe, mesonlib.File): 2105 abspath = exe.absolute_path(self.interpreter.environment.source_dir, 2106 self.interpreter.environment.build_dir) 2107 if not os.path.exists(abspath): 2108 raise InterpreterException('Tried to override %s with a file that does not exist.' % name) 2109 exe = OverrideProgram(abspath) 2110 if not isinstance(exe, (dependencies.ExternalProgram, build.Executable)): 2111 raise InterpreterException('Second argument must be an external program or executable.') 2112 self.interpreter.add_find_program_override(name, exe) 2113 2114 @FeatureNew('meson.override_dependency', '0.54.0') 2115 @permittedKwargs({'native'}) 2116 def override_dependency_method(self, args, kwargs): 2117 if len(args) != 2: 2118 raise InterpreterException('Override needs two arguments') 2119 name = args[0] 2120 dep = args[1] 2121 if not isinstance(name, str) or not name: 2122 raise InterpreterException('First argument must be a string and cannot be empty') 2123 if hasattr(dep, 'held_object'): 2124 dep = dep.held_object 2125 if not isinstance(dep, dependencies.Dependency): 2126 raise InterpreterException('Second argument must be a dependency object') 2127 identifier = dependencies.get_dep_identifier(name, kwargs) 2128 for_machine = self.interpreter.machine_from_native_kwarg(kwargs) 2129 override = self.build.dependency_overrides[for_machine].get(identifier) 2130 if override: 2131 m = 'Tried to override dependency {!r} which has already been resolved or overridden at {}' 2132 location = mlog.get_error_location_string(override.node.filename, override.node.lineno) 2133 raise InterpreterException(m.format(name, location)) 2134 self.build.dependency_overrides[for_machine][identifier] = \ 2135 build.DependencyOverride(dep, self.interpreter.current_node) 2136 2137 @noPosargs 2138 @permittedKwargs({}) 2139 def project_version_method(self, args, kwargs): 2140 return self.build.dep_manifest[self.interpreter.active_projectname]['version'] 2141 2142 @FeatureNew('meson.project_license()', '0.45.0') 2143 @noPosargs 2144 @permittedKwargs({}) 2145 def project_license_method(self, args, kwargs): 2146 return self.build.dep_manifest[self.interpreter.active_projectname]['license'] 2147 2148 @noPosargs 2149 @permittedKwargs({}) 2150 def version_method(self, args, kwargs): 2151 return coredata.version 2152 2153 @noPosargs 2154 @permittedKwargs({}) 2155 def project_name_method(self, args, kwargs): 2156 return self.interpreter.active_projectname 2157 2158 @noArgsFlattening 2159 @permittedKwargs({}) 2160 def get_cross_property_method(self, args, kwargs) -> str: 2161 if len(args) < 1 or len(args) > 2: 2162 raise InterpreterException('Must have one or two arguments.') 2163 propname = args[0] 2164 if not isinstance(propname, str): 2165 raise InterpreterException('Property name must be string.') 2166 try: 2167 props = self.interpreter.environment.properties.host 2168 return props[propname] 2169 except Exception: 2170 if len(args) == 2: 2171 return args[1] 2172 raise InterpreterException('Unknown cross property: %s.' % propname) 2173 2174 @noArgsFlattening 2175 @permittedKwargs({'native'}) 2176 @FeatureNew('meson.get_external_property', '0.54.0') 2177 def get_external_property_method(self, args: T.Sequence[str], kwargs: dict) -> str: 2178 if len(args) < 1 or len(args) > 2: 2179 raise InterpreterException('Must have one or two positional arguments.') 2180 propname = args[0] 2181 if not isinstance(propname, str): 2182 raise InterpreterException('Property name must be string.') 2183 2184 def _get_native() -> str: 2185 try: 2186 props = self.interpreter.environment.properties.build 2187 return props[propname] 2188 except Exception: 2189 if len(args) == 2: 2190 return args[1] 2191 raise InterpreterException('Unknown native property: %s.' % propname) 2192 if 'native' in kwargs: 2193 if kwargs['native']: 2194 return _get_native() 2195 else: 2196 return self.get_cross_property_method(args, {}) 2197 else: # native: not specified 2198 if self.build.environment.is_cross_build(): 2199 return self.get_cross_property_method(args, kwargs) 2200 else: 2201 return _get_native() 2202 2203known_library_kwargs = ( 2204 build.known_shlib_kwargs | 2205 build.known_stlib_kwargs 2206) 2207 2208known_build_target_kwargs = ( 2209 known_library_kwargs | 2210 build.known_exe_kwargs | 2211 build.known_jar_kwargs | 2212 {'target_type'} 2213) 2214 2215_base_test_args = {'args', 'depends', 'env', 'should_fail', 'timeout', 'workdir', 'suite', 'priority', 'protocol'} 2216 2217permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 2218 'add_global_link_arguments': {'language', 'native'}, 2219 'add_languages': {'required', 'native'}, 2220 'add_project_link_arguments': {'language', 'native'}, 2221 'add_project_arguments': {'language', 'native'}, 2222 'add_test_setup': {'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default'}, 2223 'benchmark': _base_test_args, 2224 'build_target': known_build_target_kwargs, 2225 'configure_file': {'input', 2226 'output', 2227 'configuration', 2228 'command', 2229 'copy', 2230 'depfile', 2231 'install_dir', 2232 'install_mode', 2233 'capture', 2234 'install', 2235 'format', 2236 'output_format', 2237 'encoding'}, 2238 'custom_target': {'input', 2239 'output', 2240 'command', 2241 'install', 2242 'install_dir', 2243 'install_mode', 2244 'build_always', 2245 'capture', 2246 'depends', 2247 'depend_files', 2248 'depfile', 2249 'build_by_default', 2250 'build_always_stale', 2251 'console'}, 2252 'dependency': {'default_options', 2253 'embed', 2254 'fallback', 2255 'language', 2256 'main', 2257 'method', 2258 'modules', 2259 'components', 2260 'cmake_module_path', 2261 'optional_modules', 2262 'native', 2263 'not_found_message', 2264 'required', 2265 'static', 2266 'version', 2267 'private_headers', 2268 'cmake_args', 2269 'include_type', 2270 }, 2271 'declare_dependency': {'include_directories', 2272 'link_with', 2273 'sources', 2274 'dependencies', 2275 'compile_args', 2276 'link_args', 2277 'link_whole', 2278 'version', 2279 'variables', 2280 }, 2281 'executable': build.known_exe_kwargs, 2282 'find_program': {'required', 'native', 'version', 'dirs'}, 2283 'generator': {'arguments', 2284 'output', 2285 'depends', 2286 'depfile', 2287 'capture', 2288 'preserve_path_from'}, 2289 'include_directories': {'is_system'}, 2290 'install_data': {'install_dir', 'install_mode', 'rename', 'sources'}, 2291 'install_headers': {'install_dir', 'install_mode', 'subdir'}, 2292 'install_man': {'install_dir', 'install_mode'}, 2293 'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'}, 2294 'jar': build.known_jar_kwargs, 2295 'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}, 2296 'run_command': {'check', 'capture', 'env'}, 2297 'run_target': {'command', 'depends'}, 2298 'shared_library': build.known_shlib_kwargs, 2299 'shared_module': build.known_shmod_kwargs, 2300 'static_library': build.known_stlib_kwargs, 2301 'both_libraries': known_library_kwargs, 2302 'library': known_library_kwargs, 2303 'subdir': {'if_found'}, 2304 'subproject': {'version', 'default_options', 'required'}, 2305 'test': set.union(_base_test_args, {'is_parallel'}), 2306 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, 2307 } 2308 2309 2310class Interpreter(InterpreterBase): 2311 2312 def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects', 2313 modules = None, default_project_options=None, mock=False, ast=None): 2314 super().__init__(build.environment.get_source_dir(), subdir, subproject) 2315 self.an_unpicklable_object = mesonlib.an_unpicklable_object 2316 self.build = build 2317 self.environment = build.environment 2318 self.coredata = self.environment.get_coredata() 2319 self.backend = backend 2320 self.summary = {} 2321 if modules is None: 2322 self.modules = {} 2323 else: 2324 self.modules = modules 2325 # Subproject directory is usually the name of the subproject, but can 2326 # be different for dependencies provided by wrap files. 2327 self.subproject_directory_name = subdir.split(os.path.sep)[-1] 2328 self.subproject_dir = subproject_dir 2329 self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') 2330 if not mock and ast is None: 2331 self.load_root_meson_file() 2332 self.sanity_check_ast() 2333 elif ast is not None: 2334 self.ast = ast 2335 self.sanity_check_ast() 2336 self.builtin.update({'meson': MesonMain(build, self)}) 2337 self.generators = [] 2338 self.visited_subdirs = {} 2339 self.project_args_frozen = False 2340 self.global_args_frozen = False # implies self.project_args_frozen 2341 self.subprojects = {} 2342 self.subproject_stack = [] 2343 self.configure_file_outputs = {} 2344 # Passed from the outside, only used in subprojects. 2345 if default_project_options: 2346 self.default_project_options = default_project_options.copy() 2347 else: 2348 self.default_project_options = {} 2349 self.project_default_options = {} 2350 self.build_func_dict() 2351 # build_def_files needs to be defined before parse_project is called 2352 self.build_def_files = [os.path.join(self.subdir, environment.build_filename)] 2353 if not mock: 2354 self.parse_project() 2355 self._redetect_machines() 2356 2357 def _redetect_machines(self): 2358 # Re-initialize machine descriptions. We can do a better job now because we 2359 # have the compilers needed to gain more knowledge, so wipe out old 2360 # inference and start over. 2361 machines = self.build.environment.machines.miss_defaulting() 2362 machines.build = environment.detect_machine_info(self.coredata.compilers.build) 2363 self.build.environment.machines = machines.default_missing() 2364 assert self.build.environment.machines.build.cpu is not None 2365 assert self.build.environment.machines.host.cpu is not None 2366 assert self.build.environment.machines.target.cpu is not None 2367 2368 self.builtin['build_machine'] = \ 2369 MachineHolder(self.build.environment.machines.build) 2370 self.builtin['host_machine'] = \ 2371 MachineHolder(self.build.environment.machines.host) 2372 self.builtin['target_machine'] = \ 2373 MachineHolder(self.build.environment.machines.target) 2374 2375 def get_non_matching_default_options(self): 2376 env = self.environment 2377 for def_opt_name, def_opt_value in self.project_default_options.items(): 2378 for opts in env.coredata.get_all_options(): 2379 cur_opt_value = opts.get(def_opt_name) 2380 if cur_opt_value is not None: 2381 def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value) 2382 if def_opt_value != cur_opt_value.value: 2383 yield (def_opt_name, def_opt_value, cur_opt_value) 2384 2385 def build_func_dict(self): 2386 self.funcs.update({'add_global_arguments': self.func_add_global_arguments, 2387 'add_project_arguments': self.func_add_project_arguments, 2388 'add_global_link_arguments': self.func_add_global_link_arguments, 2389 'add_project_link_arguments': self.func_add_project_link_arguments, 2390 'add_test_setup': self.func_add_test_setup, 2391 'add_languages': self.func_add_languages, 2392 'alias_target': self.func_alias_target, 2393 'assert': self.func_assert, 2394 'benchmark': self.func_benchmark, 2395 'build_target': self.func_build_target, 2396 'configuration_data': self.func_configuration_data, 2397 'configure_file': self.func_configure_file, 2398 'custom_target': self.func_custom_target, 2399 'declare_dependency': self.func_declare_dependency, 2400 'dependency': self.func_dependency, 2401 'disabler': self.func_disabler, 2402 'environment': self.func_environment, 2403 'error': self.func_error, 2404 'executable': self.func_executable, 2405 'generator': self.func_generator, 2406 'gettext': self.func_gettext, 2407 'get_option': self.func_get_option, 2408 'get_variable': self.func_get_variable, 2409 'files': self.func_files, 2410 'find_library': self.func_find_library, 2411 'find_program': self.func_find_program, 2412 'include_directories': self.func_include_directories, 2413 'import': self.func_import, 2414 'install_data': self.func_install_data, 2415 'install_headers': self.func_install_headers, 2416 'install_man': self.func_install_man, 2417 'install_subdir': self.func_install_subdir, 2418 'is_disabler': self.func_is_disabler, 2419 'is_variable': self.func_is_variable, 2420 'jar': self.func_jar, 2421 'join_paths': self.func_join_paths, 2422 'library': self.func_library, 2423 'message': self.func_message, 2424 'warning': self.func_warning, 2425 'option': self.func_option, 2426 'project': self.func_project, 2427 'run_target': self.func_run_target, 2428 'run_command': self.func_run_command, 2429 'set_variable': self.func_set_variable, 2430 'subdir': self.func_subdir, 2431 'subdir_done': self.func_subdir_done, 2432 'subproject': self.func_subproject, 2433 'summary': self.func_summary, 2434 'shared_library': self.func_shared_lib, 2435 'shared_module': self.func_shared_module, 2436 'static_library': self.func_static_lib, 2437 'both_libraries': self.func_both_lib, 2438 'test': self.func_test, 2439 'vcs_tag': self.func_vcs_tag 2440 }) 2441 if 'MESON_UNIT_TEST' in os.environ: 2442 self.funcs.update({'exception': self.func_exception}) 2443 2444 def holderify(self, item): 2445 if isinstance(item, list): 2446 return [self.holderify(x) for x in item] 2447 if isinstance(item, dict): 2448 return {k: self.holderify(v) for k, v in item.items()} 2449 2450 if isinstance(item, build.CustomTarget): 2451 return CustomTargetHolder(item, self) 2452 elif isinstance(item, (int, str, bool, Disabler, InterpreterObject)) or item is None: 2453 return item 2454 elif isinstance(item, build.Executable): 2455 return ExecutableHolder(item, self) 2456 elif isinstance(item, build.GeneratedList): 2457 return GeneratedListHolder(item) 2458 elif isinstance(item, build.RunTarget): 2459 raise RuntimeError('This is not a pipe.') 2460 elif isinstance(item, build.RunScript): 2461 raise RuntimeError('Do not do this.') 2462 elif isinstance(item, build.Data): 2463 return DataHolder(item) 2464 elif isinstance(item, dependencies.Dependency): 2465 return DependencyHolder(item, self.subproject) 2466 elif isinstance(item, dependencies.ExternalProgram): 2467 return ExternalProgramHolder(item, self.subproject) 2468 elif hasattr(item, 'held_object'): 2469 return item 2470 else: 2471 raise InterpreterException('Module returned a value of unknown type.') 2472 2473 def process_new_values(self, invalues): 2474 invalues = listify(invalues) 2475 for v in invalues: 2476 if isinstance(v, (RunTargetHolder, CustomTargetHolder, BuildTargetHolder)): 2477 v = v.held_object 2478 2479 if isinstance(v, (build.BuildTarget, build.CustomTarget, build.RunTarget)): 2480 self.add_target(v.name, v) 2481 elif isinstance(v, list): 2482 self.module_method_callback(v) 2483 elif isinstance(v, build.GeneratedList): 2484 pass 2485 elif isinstance(v, build.RunScript): 2486 self.build.install_scripts.append(v) 2487 elif isinstance(v, build.Data): 2488 self.build.data.append(v) 2489 elif isinstance(v, dependencies.ExternalProgram): 2490 return ExternalProgramHolder(v, self.subproject) 2491 elif isinstance(v, dependencies.InternalDependency): 2492 # FIXME: This is special cased and not ideal: 2493 # The first source is our new VapiTarget, the rest are deps 2494 self.process_new_values(v.sources[0]) 2495 elif hasattr(v, 'held_object'): 2496 pass 2497 elif isinstance(v, (int, str, bool, Disabler)): 2498 pass 2499 else: 2500 raise InterpreterException('Module returned a value of unknown type.') 2501 2502 def module_method_callback(self, return_object): 2503 if not isinstance(return_object, ModuleReturnValue): 2504 raise InterpreterException('Bug in module, it returned an invalid object') 2505 invalues = return_object.new_objects 2506 self.process_new_values(invalues) 2507 return self.holderify(return_object.return_value) 2508 2509 def get_build_def_files(self): 2510 return self.build_def_files 2511 2512 def add_build_def_file(self, f): 2513 # Use relative path for files within source directory, and absolute path 2514 # for system files. Skip files within build directory. Also skip not regular 2515 # files (e.g. /dev/stdout) Normalize the path to avoid duplicates, this 2516 # is especially important to convert '/' to '\' on Windows. 2517 if isinstance(f, mesonlib.File): 2518 if f.is_built: 2519 return 2520 f = os.path.normpath(f.relative_name()) 2521 elif os.path.isfile(f) and not f.startswith('/dev'): 2522 srcdir = Path(self.environment.get_source_dir()) 2523 builddir = Path(self.environment.get_build_dir()) 2524 try: 2525 f = Path(f).resolve() 2526 except OSError: 2527 f = Path(f) 2528 s = f.stat() 2529 if (hasattr(s, 'st_file_attributes') and 2530 s.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT != 0 and 2531 s.st_reparse_tag == stat.IO_REPARSE_TAG_APPEXECLINK): 2532 # This is a Windows Store link which we can't 2533 # resolve, so just do our best otherwise. 2534 f = f.parent.resolve() / f.name 2535 else: 2536 raise 2537 if builddir in f.parents: 2538 return 2539 if srcdir in f.parents: 2540 f = f.relative_to(srcdir) 2541 f = str(f) 2542 else: 2543 return 2544 if f not in self.build_def_files: 2545 self.build_def_files.append(f) 2546 2547 def get_variables(self): 2548 return self.variables 2549 2550 def check_stdlibs(self): 2551 for for_machine in MachineChoice: 2552 props = self.build.environment.properties[for_machine] 2553 for l in self.coredata.compilers[for_machine].keys(): 2554 try: 2555 di = mesonlib.stringlistify(props.get_stdlib(l)) 2556 if len(di) != 2: 2557 raise InterpreterException('Stdlib definition for %s should have exactly two elements.' 2558 % l) 2559 projname, depname = di 2560 subproj = self.do_subproject(projname, 'meson', {}) 2561 self.build.stdlibs.host[l] = subproj.get_variable_method([depname], {}) 2562 except KeyError: 2563 pass 2564 except InvalidArguments: 2565 pass 2566 2567 @stringArgs 2568 @noKwargs 2569 def func_import(self, node, args, kwargs): 2570 if len(args) != 1: 2571 raise InvalidCode('Import takes one argument.') 2572 modname = args[0] 2573 if modname.startswith('unstable-'): 2574 plainname = modname.split('-', 1)[1] 2575 mlog.warning('Module %s has no backwards or forwards compatibility and might not exist in future releases.' % modname, location=node) 2576 modname = 'unstable_' + plainname 2577 if modname not in self.modules: 2578 try: 2579 module = importlib.import_module('mesonbuild.modules.' + modname) 2580 except ImportError: 2581 raise InvalidArguments('Module "%s" does not exist' % (modname, )) 2582 self.modules[modname] = module.initialize(self) 2583 return ModuleHolder(modname, self.modules[modname], self) 2584 2585 @stringArgs 2586 @noKwargs 2587 def func_files(self, node, args, kwargs): 2588 return [mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, fname) for fname in args] 2589 2590 @FeatureNewKwargs('declare_dependency', '0.46.0', ['link_whole']) 2591 @FeatureNewKwargs('declare_dependency', '0.54.0', ['variables']) 2592 @permittedKwargs(permitted_kwargs['declare_dependency']) 2593 @noPosargs 2594 def func_declare_dependency(self, node, args, kwargs): 2595 version = kwargs.get('version', self.project_version) 2596 if not isinstance(version, str): 2597 raise InterpreterException('Version must be a string.') 2598 incs = self.extract_incdirs(kwargs) 2599 libs = unholder(extract_as_list(kwargs, 'link_with')) 2600 libs_whole = unholder(extract_as_list(kwargs, 'link_whole')) 2601 sources = extract_as_list(kwargs, 'sources') 2602 sources = unholder(listify(self.source_strings_to_files(sources))) 2603 deps = unholder(extract_as_list(kwargs, 'dependencies')) 2604 compile_args = mesonlib.stringlistify(kwargs.get('compile_args', [])) 2605 link_args = mesonlib.stringlistify(kwargs.get('link_args', [])) 2606 variables = kwargs.get('variables', {}) 2607 if not isinstance(variables, dict): 2608 raise InterpreterException('variables must be a dict.') 2609 if not all(isinstance(v, str) for v in variables.values()): 2610 # Because that is how they will come from pkg-config and cmake 2611 raise InterpreterException('variables values be strings.') 2612 final_deps = [] 2613 for d in deps: 2614 try: 2615 d = d.held_object 2616 except Exception: 2617 pass 2618 if not isinstance(d, (dependencies.Dependency, dependencies.ExternalLibrary, dependencies.InternalDependency)): 2619 raise InterpreterException('Dependencies must be external deps') 2620 final_deps.append(d) 2621 for l in libs: 2622 if isinstance(l, dependencies.Dependency): 2623 raise InterpreterException('''Entries in "link_with" may only be self-built targets, 2624external dependencies (including libraries) must go to "dependencies".''') 2625 dep = dependencies.InternalDependency(version, incs, compile_args, 2626 link_args, libs, libs_whole, sources, final_deps, 2627 variables) 2628 return DependencyHolder(dep, self.subproject) 2629 2630 @noKwargs 2631 def func_assert(self, node, args, kwargs): 2632 if len(args) == 1: 2633 FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject) 2634 value = args[0] 2635 message = None 2636 elif len(args) == 2: 2637 value, message = args 2638 if not isinstance(message, str): 2639 raise InterpreterException('Assert message not a string.') 2640 else: 2641 raise InterpreterException('Assert takes between one and two arguments') 2642 if not isinstance(value, bool): 2643 raise InterpreterException('Assert value not bool.') 2644 if not value: 2645 if message is None: 2646 from .ast import AstPrinter 2647 printer = AstPrinter() 2648 node.args.arguments[0].accept(printer) 2649 message = printer.result 2650 raise InterpreterException('Assert failed: ' + message) 2651 2652 def validate_arguments(self, args, argcount, arg_types): 2653 if argcount is not None: 2654 if argcount != len(args): 2655 raise InvalidArguments('Expected %d arguments, got %d.' % 2656 (argcount, len(args))) 2657 for actual, wanted in zip(args, arg_types): 2658 if wanted is not None: 2659 if not isinstance(actual, wanted): 2660 raise InvalidArguments('Incorrect argument type.') 2661 2662 @FeatureNewKwargs('run_command', '0.50.0', ['env']) 2663 @FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture']) 2664 @permittedKwargs(permitted_kwargs['run_command']) 2665 def func_run_command(self, node, args, kwargs): 2666 return self.run_command_impl(node, args, kwargs) 2667 2668 def run_command_impl(self, node, args, kwargs, in_builddir=False): 2669 if len(args) < 1: 2670 raise InterpreterException('Not enough arguments') 2671 cmd, *cargs = args 2672 capture = kwargs.get('capture', True) 2673 srcdir = self.environment.get_source_dir() 2674 builddir = self.environment.get_build_dir() 2675 2676 check = kwargs.get('check', False) 2677 if not isinstance(check, bool): 2678 raise InterpreterException('Check must be boolean.') 2679 2680 env = self.unpack_env_kwarg(kwargs) 2681 2682 m = 'must be a string, or the output of find_program(), files() '\ 2683 'or configure_file(), or a compiler object; not {!r}' 2684 expanded_args = [] 2685 if isinstance(cmd, ExternalProgramHolder): 2686 cmd = cmd.held_object 2687 if isinstance(cmd, build.Executable): 2688 progname = node.args.arguments[0].value 2689 msg = 'Program {!r} was overridden with the compiled executable {!r}'\ 2690 ' and therefore cannot be used during configuration' 2691 raise InterpreterException(msg.format(progname, cmd.description())) 2692 if not cmd.found(): 2693 raise InterpreterException('command {!r} not found or not executable'.format(cmd.get_name())) 2694 elif isinstance(cmd, CompilerHolder): 2695 exelist = cmd.compiler.get_exelist() 2696 cmd = exelist[0] 2697 prog = ExternalProgram(cmd, silent=True) 2698 if not prog.found(): 2699 raise InterpreterException('Program {!r} not found ' 2700 'or not executable'.format(cmd)) 2701 cmd = prog 2702 expanded_args = exelist[1:] 2703 else: 2704 if isinstance(cmd, mesonlib.File): 2705 cmd = cmd.absolute_path(srcdir, builddir) 2706 elif not isinstance(cmd, str): 2707 raise InterpreterException('First argument ' + m.format(cmd)) 2708 # Prefer scripts in the current source directory 2709 search_dir = os.path.join(srcdir, self.subdir) 2710 prog = ExternalProgram(cmd, silent=True, search_dir=search_dir) 2711 if not prog.found(): 2712 raise InterpreterException('Program or command {!r} not found ' 2713 'or not executable'.format(cmd)) 2714 cmd = prog 2715 for a in listify(cargs): 2716 if isinstance(a, str): 2717 expanded_args.append(a) 2718 elif isinstance(a, mesonlib.File): 2719 expanded_args.append(a.absolute_path(srcdir, builddir)) 2720 elif isinstance(a, ExternalProgramHolder): 2721 expanded_args.append(a.held_object.get_path()) 2722 else: 2723 raise InterpreterException('Arguments ' + m.format(a)) 2724 # If any file that was used as an argument to the command 2725 # changes, we must re-run the configuration step. 2726 self.add_build_def_file(cmd.get_path()) 2727 for a in expanded_args: 2728 if not os.path.isabs(a): 2729 a = os.path.join(builddir if in_builddir else srcdir, self.subdir, a) 2730 self.add_build_def_file(a) 2731 return RunProcess(cmd, expanded_args, env, srcdir, builddir, self.subdir, 2732 self.environment.get_build_command() + ['introspect'], 2733 in_builddir=in_builddir, check=check, capture=capture) 2734 2735 @stringArgs 2736 def func_gettext(self, nodes, args, kwargs): 2737 raise InterpreterException('Gettext() function has been moved to module i18n. Import it and use i18n.gettext() instead') 2738 2739 def func_option(self, nodes, args, kwargs): 2740 raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') 2741 2742 @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) 2743 @permittedKwargs(permitted_kwargs['subproject']) 2744 @stringArgs 2745 def func_subproject(self, nodes, args, kwargs): 2746 if len(args) != 1: 2747 raise InterpreterException('Subproject takes exactly one argument') 2748 dirname = args[0] 2749 return self.do_subproject(dirname, 'meson', kwargs) 2750 2751 def disabled_subproject(self, dirname, disabled_feature=None, exception=None): 2752 sub = SubprojectHolder(None, self.subproject_dir, dirname, 2753 disabled_feature=disabled_feature, exception=exception) 2754 self.subprojects[dirname] = sub 2755 return sub 2756 2757 def get_subproject(self, dirname): 2758 sub = self.subprojects.get(dirname) 2759 if sub and sub.found(): 2760 return sub 2761 return None 2762 2763 def do_subproject(self, dirname: str, method: str, kwargs): 2764 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) 2765 if disabled: 2766 mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled') 2767 return self.disabled_subproject(dirname, disabled_feature=feature) 2768 2769 default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) 2770 default_options = coredata.create_options_dict(default_options) 2771 if dirname == '': 2772 raise InterpreterException('Subproject dir name must not be empty.') 2773 if dirname[0] == '.': 2774 raise InterpreterException('Subproject dir name must not start with a period.') 2775 if '..' in dirname: 2776 raise InterpreterException('Subproject name must not contain a ".." path segment.') 2777 if os.path.isabs(dirname): 2778 raise InterpreterException('Subproject name must not be an absolute path.') 2779 if has_path_sep(dirname): 2780 mlog.warning('Subproject name has a path separator. This may cause unexpected behaviour.', 2781 location=self.current_node) 2782 if dirname in self.subproject_stack: 2783 fullstack = self.subproject_stack + [dirname] 2784 incpath = ' => '.join(fullstack) 2785 raise InvalidCode('Recursive include of subprojects: %s.' % incpath) 2786 if dirname in self.subprojects: 2787 subproject = self.subprojects[dirname] 2788 if required and not subproject.found(): 2789 raise InterpreterException('Subproject "%s/%s" required but not found.' % ( 2790 self.subproject_dir, dirname)) 2791 return subproject 2792 2793 r = self.environment.wrap_resolver 2794 try: 2795 resolved = r.resolve(dirname, method, self.subproject) 2796 except wrap.WrapException as e: 2797 subprojdir = os.path.join(self.subproject_dir, r.directory) 2798 if isinstance(e, wrap.WrapNotFoundException): 2799 # if the reason subproject execution failed was because 2800 # the directory doesn't exist, try to give some helpful 2801 # advice if it's a nested subproject that needs 2802 # promotion... 2803 self.print_nested_info(dirname) 2804 if not required: 2805 mlog.log(e) 2806 mlog.log('Subproject ', mlog.bold(subprojdir), 'is buildable:', mlog.red('NO'), '(disabling)') 2807 return self.disabled_subproject(dirname, exception=e) 2808 raise e 2809 2810 subdir = os.path.join(self.subproject_dir, resolved) 2811 subdir_abs = os.path.join(self.environment.get_source_dir(), subdir) 2812 os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) 2813 self.global_args_frozen = True 2814 2815 mlog.log() 2816 with mlog.nested(): 2817 mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n') 2818 try: 2819 if method == 'meson': 2820 return self._do_subproject_meson(dirname, subdir, default_options, kwargs) 2821 elif method == 'cmake': 2822 return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, kwargs) 2823 else: 2824 raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) 2825 # Invalid code is always an error 2826 except InvalidCode: 2827 raise 2828 except Exception as e: 2829 if not required: 2830 with mlog.nested(): 2831 # Suppress the 'ERROR:' prefix because this exception is not 2832 # fatal and VS CI treat any logs with "ERROR:" as fatal. 2833 mlog.exception(e, prefix=mlog.yellow('Exception:')) 2834 mlog.log('\nSubproject', mlog.bold(dirname), 'is buildable:', mlog.red('NO'), '(disabling)') 2835 return self.disabled_subproject(dirname, exception=e) 2836 raise e 2837 2838 def _do_subproject_meson(self, dirname, subdir, default_options, kwargs, ast=None, build_def_files=None): 2839 with mlog.nested(): 2840 new_build = self.build.copy() 2841 subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, 2842 self.modules, default_options, ast=ast) 2843 subi.subprojects = self.subprojects 2844 2845 subi.subproject_stack = self.subproject_stack + [dirname] 2846 current_active = self.active_projectname 2847 current_warnings_counter = mlog.log_warnings_counter 2848 mlog.log_warnings_counter = 0 2849 subi.run() 2850 subi_warnings = mlog.log_warnings_counter 2851 mlog.log_warnings_counter = current_warnings_counter 2852 2853 mlog.log('Subproject', mlog.bold(dirname), 'finished.') 2854 2855 mlog.log() 2856 2857 if 'version' in kwargs: 2858 pv = subi.project_version 2859 wanted = kwargs['version'] 2860 if pv == 'undefined' or not mesonlib.version_compare_many(pv, wanted)[0]: 2861 raise InterpreterException('Subproject %s version is %s but %s required.' % (dirname, pv, wanted)) 2862 self.active_projectname = current_active 2863 self.subprojects.update(subi.subprojects) 2864 self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname, 2865 warnings=subi_warnings) 2866 # Duplicates are possible when subproject uses files from project root 2867 if build_def_files: 2868 self.build_def_files = list(set(self.build_def_files + build_def_files)) 2869 else: 2870 self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) 2871 self.build.merge(subi.build) 2872 self.build.subprojects[dirname] = subi.project_version 2873 self.summary.update(subi.summary) 2874 return self.subprojects[dirname] 2875 2876 def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwargs): 2877 with mlog.nested(): 2878 new_build = self.build.copy() 2879 prefix = self.coredata.builtins['prefix'].value 2880 2881 from .modules.cmake import CMakeSubprojectOptions 2882 options = kwargs.get('options', CMakeSubprojectOptions()) 2883 if not isinstance(options, CMakeSubprojectOptions): 2884 raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions' 2885 ' object (created by cmake.subproject_options())') 2886 2887 cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', [])) 2888 cmake_options += options.cmake_options 2889 cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend) 2890 cm_int.initialise(cmake_options) 2891 cm_int.analyse() 2892 2893 # Generate a meson ast and execute it with the normal do_subproject_meson 2894 ast = cm_int.pretend_to_be_meson(options.target_options) 2895 2896 mlog.log() 2897 with mlog.nested(): 2898 mlog.log('Processing generated meson AST') 2899 2900 # Debug print the generated meson file 2901 from .ast import AstIndentationGenerator, AstPrinter 2902 printer = AstPrinter() 2903 ast.accept(AstIndentationGenerator()) 2904 ast.accept(printer) 2905 printer.post_process() 2906 meson_filename = os.path.join(self.build.environment.get_build_dir(), subdir, 'meson.build') 2907 with open(meson_filename, "w") as f: 2908 f.write(printer.result) 2909 2910 mlog.log('Build file:', meson_filename) 2911 mlog.cmd_ci_include(meson_filename) 2912 mlog.log() 2913 2914 result = self._do_subproject_meson(dirname, subdir, default_options, kwargs, ast, cm_int.bs_files) 2915 result.cm_interpreter = cm_int 2916 2917 mlog.log() 2918 return result 2919 2920 def get_option_internal(self, optname): 2921 raw_optname = optname 2922 if self.is_subproject(): 2923 optname = self.subproject + ':' + optname 2924 2925 for opts in [ 2926 self.coredata.base_options, compilers.base_options, self.coredata.builtins, 2927 dict(self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine)), 2928 dict(self.coredata.flatten_lang_iterator( 2929 self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options))), 2930 ]: 2931 v = opts.get(optname) 2932 if v is None or v.yielding: 2933 v = opts.get(raw_optname) 2934 if v is not None: 2935 return v 2936 2937 try: 2938 opt = self.coredata.user_options[optname] 2939 if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options: 2940 popt = self.coredata.user_options[raw_optname] 2941 if type(opt) is type(popt): 2942 opt = popt 2943 else: 2944 # Get class name, then option type as a string 2945 opt_type = opt.__class__.__name__[4:][:-6].lower() 2946 popt_type = popt.__class__.__name__[4:][:-6].lower() 2947 # This is not a hard error to avoid dependency hell, the workaround 2948 # when this happens is to simply set the subproject's option directly. 2949 mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield ' 2950 'to parent option of type {3!r}, ignoring parent value. ' 2951 'Use -D{2}:{0}=value to set the value for this option manually' 2952 '.'.format(raw_optname, opt_type, self.subproject, popt_type), 2953 location=self.current_node) 2954 return opt 2955 except KeyError: 2956 pass 2957 2958 raise InterpreterException('Tried to access unknown option "%s".' % optname) 2959 2960 @stringArgs 2961 @noKwargs 2962 def func_get_option(self, nodes, args, kwargs): 2963 if len(args) != 1: 2964 raise InterpreterException('Argument required for get_option.') 2965 optname = args[0] 2966 if ':' in optname: 2967 raise InterpreterException('Having a colon in option name is forbidden, ' 2968 'projects are not allowed to directly access ' 2969 'options of other subprojects.') 2970 opt = self.get_option_internal(optname) 2971 if isinstance(opt, coredata.UserFeatureOption): 2972 return FeatureOptionHolder(self.environment, optname, opt) 2973 elif isinstance(opt, coredata.UserOption): 2974 return opt.value 2975 return opt 2976 2977 @noKwargs 2978 def func_configuration_data(self, node, args, kwargs): 2979 if len(args) > 1: 2980 raise InterpreterException('configuration_data takes only one optional positional arguments') 2981 elif len(args) == 1: 2982 FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject) 2983 initial_values = args[0] 2984 if not isinstance(initial_values, dict): 2985 raise InterpreterException('configuration_data first argument must be a dictionary') 2986 else: 2987 initial_values = {} 2988 return ConfigurationDataHolder(self.subproject, initial_values) 2989 2990 def set_backend(self): 2991 # The backend is already set when parsing subprojects 2992 if self.backend is not None: 2993 return 2994 backend = self.coredata.get_builtin_option('backend') 2995 from .backend import backends 2996 self.backend = backends.get_backend_from_name(backend, self.build, self) 2997 2998 if self.backend is None: 2999 raise InterpreterException('Unknown backend "%s".' % backend) 3000 if backend != self.backend.name: 3001 if self.backend.name.startswith('vs'): 3002 mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name)) 3003 self.coredata.set_builtin_option('backend', self.backend.name) 3004 3005 # Only init backend options on first invocation otherwise it would 3006 # override values previously set from command line. 3007 if self.environment.first_invocation: 3008 self.coredata.init_backend_options(backend) 3009 3010 options = {k: v for k, v in self.environment.cmd_line_options.items() if k.startswith('backend_')} 3011 self.coredata.set_options(options) 3012 3013 @stringArgs 3014 @permittedKwargs(permitted_kwargs['project']) 3015 def func_project(self, node, args, kwargs): 3016 if len(args) < 1: 3017 raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.') 3018 proj_name, *proj_langs = args 3019 if ':' in proj_name: 3020 raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name)) 3021 3022 # This needs to be evaluated as early as possible, as meson uses this 3023 # for things like deprecation testing. 3024 if 'meson_version' in kwargs: 3025 cv = coredata.version 3026 pv = kwargs['meson_version'] 3027 if not mesonlib.version_compare(cv, pv): 3028 raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv)) 3029 mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version'] 3030 3031 if os.path.exists(self.option_file): 3032 oi = optinterpreter.OptionInterpreter(self.subproject) 3033 oi.process(self.option_file) 3034 self.coredata.merge_user_options(oi.options) 3035 self.add_build_def_file(self.option_file) 3036 3037 # Do not set default_options on reconfigure otherwise it would override 3038 # values previously set from command line. That means that changing 3039 # default_options in a project will trigger a reconfigure but won't 3040 # have any effect. 3041 self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) 3042 self.project_default_options = coredata.create_options_dict(self.project_default_options) 3043 if self.environment.first_invocation: 3044 default_options = self.project_default_options 3045 default_options.update(self.default_project_options) 3046 self.coredata.init_builtins(self.subproject) 3047 else: 3048 default_options = {} 3049 self.coredata.set_default_options(default_options, self.subproject, self.environment) 3050 3051 if not self.is_subproject(): 3052 self.build.project_name = proj_name 3053 self.active_projectname = proj_name 3054 self.project_version = kwargs.get('version', 'undefined') 3055 if self.build.project_version is None: 3056 self.build.project_version = self.project_version 3057 proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) 3058 self.build.dep_manifest[proj_name] = {'version': self.project_version, 3059 'license': proj_license} 3060 if self.subproject in self.build.projects: 3061 raise InvalidCode('Second call to project().') 3062 if not self.is_subproject() and 'subproject_dir' in kwargs: 3063 spdirname = kwargs['subproject_dir'] 3064 if not isinstance(spdirname, str): 3065 raise InterpreterException('Subproject_dir must be a string') 3066 if os.path.isabs(spdirname): 3067 raise InterpreterException('Subproject_dir must not be an absolute path.') 3068 if spdirname.startswith('.'): 3069 raise InterpreterException('Subproject_dir must not begin with a period.') 3070 if '..' in spdirname: 3071 raise InterpreterException('Subproject_dir must not contain a ".." segment.') 3072 self.subproject_dir = spdirname 3073 3074 self.build.subproject_dir = self.subproject_dir 3075 if not self.is_subproject(): 3076 wrap_mode = self.coredata.get_builtin_option('wrap_mode') 3077 subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) 3078 self.environment.wrap_resolver = wrap.Resolver(subproject_dir_abs, wrap_mode) 3079 3080 self.build.projects[self.subproject] = proj_name 3081 mlog.log('Project name:', mlog.bold(proj_name)) 3082 mlog.log('Project version:', mlog.bold(self.project_version)) 3083 self.add_languages(proj_langs, True, MachineChoice.BUILD) 3084 self.add_languages(proj_langs, True, MachineChoice.HOST) 3085 self.set_backend() 3086 if not self.is_subproject(): 3087 self.check_stdlibs() 3088 3089 @FeatureNewKwargs('add_languages', '0.54.0', ['native']) 3090 @permittedKwargs(permitted_kwargs['add_languages']) 3091 @stringArgs 3092 def func_add_languages(self, node, args, kwargs): 3093 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) 3094 if disabled: 3095 for lang in sorted(args, key=compilers.sort_clink): 3096 mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled') 3097 return False 3098 if 'native' in kwargs: 3099 return self.add_languages(args, required, self.machine_from_native_kwarg(kwargs)) 3100 else: 3101 # absent 'native' means 'both' for backwards compatibility 3102 tv = FeatureNew.get_target_version(self.subproject) 3103 if FeatureNew.check_version(tv, '0.54.0'): 3104 mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.', 3105 location=self.current_node) 3106 3107 success = self.add_languages(args, False, MachineChoice.BUILD) 3108 success &= self.add_languages(args, required, MachineChoice.HOST) 3109 return success 3110 3111 def get_message_string_arg(self, arg): 3112 if isinstance(arg, list): 3113 argstr = stringifyUserArguments(arg) 3114 elif isinstance(arg, dict): 3115 argstr = stringifyUserArguments(arg) 3116 elif isinstance(arg, str): 3117 argstr = arg 3118 elif isinstance(arg, int): 3119 argstr = str(arg) 3120 else: 3121 raise InvalidArguments('Function accepts only strings, integers, lists and lists thereof.') 3122 3123 return argstr 3124 3125 @noArgsFlattening 3126 @noKwargs 3127 def func_message(self, node, args, kwargs): 3128 if len(args) > 1: 3129 FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject) 3130 args_str = [self.get_message_string_arg(i) for i in args] 3131 self.message_impl(args_str) 3132 3133 def message_impl(self, args): 3134 mlog.log(mlog.bold('Message:'), *args) 3135 3136 @noArgsFlattening 3137 @FeatureNewKwargs('summary', '0.54.0', ['list_sep']) 3138 @permittedKwargs({'section', 'bool_yn', 'list_sep'}) 3139 @FeatureNew('summary', '0.53.0') 3140 def func_summary(self, node, args, kwargs): 3141 if len(args) == 1: 3142 if not isinstance(args[0], dict): 3143 raise InterpreterException('Summary first argument must be dictionary.') 3144 values = args[0] 3145 elif len(args) == 2: 3146 if not isinstance(args[0], str): 3147 raise InterpreterException('Summary first argument must be string.') 3148 values = {args[0]: args[1]} 3149 else: 3150 raise InterpreterException('Summary accepts at most 2 arguments.') 3151 section = kwargs.get('section', '') 3152 if not isinstance(section, str): 3153 raise InterpreterException('Summary\'s section keyword argument must be string.') 3154 self.summary_impl(section, values, kwargs) 3155 3156 def summary_impl(self, section, values, kwargs): 3157 if self.subproject not in self.summary: 3158 self.summary[self.subproject] = Summary(self.active_projectname, self.project_version) 3159 self.summary[self.subproject].add_section(section, values, kwargs) 3160 3161 def _print_summary(self): 3162 # Add automatic 'Supbrojects' section in main project. 3163 all_subprojects = collections.OrderedDict() 3164 for name, subp in sorted(self.subprojects.items()): 3165 value = subp.found() 3166 if subp.disabled_feature: 3167 value = [value, 'Feature {!r} disabled'.format(subp.disabled_feature)] 3168 elif subp.exception: 3169 value = [value, str(subp.exception)] 3170 elif subp.warnings > 0: 3171 value = [value, '{} warnings'.format(subp.warnings)] 3172 all_subprojects[name] = value 3173 if all_subprojects: 3174 self.summary_impl('Subprojects', all_subprojects, 3175 {'bool_yn': True, 3176 'list_sep': ' ', 3177 }) 3178 # Print all summaries, main project last. 3179 mlog.log('') # newline 3180 main_summary = self.summary.pop('', None) 3181 for _, summary in sorted(self.summary.items()): 3182 summary.dump() 3183 if main_summary: 3184 main_summary.dump() 3185 3186 @noArgsFlattening 3187 @FeatureNew('warning', '0.44.0') 3188 @noKwargs 3189 def func_warning(self, node, args, kwargs): 3190 if len(args) > 1: 3191 FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject) 3192 args_str = [self.get_message_string_arg(i) for i in args] 3193 mlog.warning(*args_str, location=node) 3194 3195 @noKwargs 3196 def func_error(self, node, args, kwargs): 3197 self.validate_arguments(args, 1, [str]) 3198 raise InterpreterException('Problem encountered: ' + args[0]) 3199 3200 @noKwargs 3201 def func_exception(self, node, args, kwargs): 3202 self.validate_arguments(args, 0, []) 3203 raise Exception() 3204 3205 def add_languages(self, args: T.Sequence[str], required: bool, for_machine: MachineChoice) -> bool: 3206 success = self.add_languages_for(args, required, for_machine) 3207 if not self.coredata.is_cross_build(): 3208 self.coredata.copy_build_options_from_regular_ones() 3209 self._redetect_machines() 3210 return success 3211 3212 def should_skip_sanity_check(self, for_machine: MachineChoice) -> bool: 3213 should = self.environment.properties.host.get('skip_sanity_check', False) 3214 if not isinstance(should, bool): 3215 raise InterpreterException('Option skip_sanity_check must be a boolean.') 3216 if for_machine != MachineChoice.HOST and not should: 3217 return False 3218 if not self.environment.is_cross_build() and not should: 3219 return False 3220 return should 3221 3222 def add_languages_for(self, args, required, for_machine: MachineChoice): 3223 args = [a.lower() for a in args] 3224 langs = set(self.coredata.compilers[for_machine].keys()) 3225 langs.update(args) 3226 if 'vala' in langs: 3227 if 'c' not in langs: 3228 raise InterpreterException('Compiling Vala requires C. Add C to your project languages and rerun Meson.') 3229 3230 success = True 3231 for lang in sorted(args, key=compilers.sort_clink): 3232 clist = self.coredata.compilers[for_machine] 3233 machine_name = for_machine.get_lower_case_name() 3234 if lang in clist: 3235 comp = clist[lang] 3236 else: 3237 try: 3238 comp = self.environment.detect_compiler_for(lang, for_machine) 3239 if comp is None: 3240 raise InvalidArguments('Tried to use unknown language "%s".' % lang) 3241 if self.should_skip_sanity_check(for_machine): 3242 mlog.log_once('Cross compiler sanity tests disabled via the cross file.') 3243 else: 3244 comp.sanity_check(self.environment.get_scratch_dir(), self.environment) 3245 except Exception: 3246 if not required: 3247 mlog.log('Compiler for language', 3248 mlog.bold(lang), 'for the', machine_name, 3249 'machine not found.') 3250 success = False 3251 continue 3252 else: 3253 raise 3254 3255 if for_machine == MachineChoice.HOST or self.environment.is_cross_build(): 3256 logger_fun = mlog.log 3257 else: 3258 logger_fun = mlog.debug 3259 logger_fun(comp.get_display_language(), 'compiler for the', machine_name, 'machine:', 3260 mlog.bold(' '.join(comp.get_exelist())), comp.get_version_string()) 3261 if comp.linker is not None: 3262 logger_fun(comp.get_display_language(), 'linker for the', machine_name, 'machine:', 3263 mlog.bold(' '.join(comp.linker.get_exelist())), comp.linker.id, comp.linker.version) 3264 self.build.ensure_static_linker(comp) 3265 3266 return success 3267 3268 def program_from_file_for(self, for_machine, prognames): 3269 for p in unholder(prognames): 3270 if isinstance(p, mesonlib.File): 3271 continue # Always points to a local (i.e. self generated) file. 3272 if not isinstance(p, str): 3273 raise InterpreterException('Executable name must be a string') 3274 prog = ExternalProgram.from_bin_list(self.environment, for_machine, p) 3275 if prog.found(): 3276 return ExternalProgramHolder(prog, self.subproject) 3277 return None 3278 3279 def program_from_system(self, args, search_dirs, silent=False): 3280 # Search for scripts relative to current subdir. 3281 # Do not cache found programs because find_program('foobar') 3282 # might give different results when run from different source dirs. 3283 source_dir = os.path.join(self.environment.get_source_dir(), self.subdir) 3284 for exename in args: 3285 if isinstance(exename, mesonlib.File): 3286 if exename.is_built: 3287 search_dir = os.path.join(self.environment.get_build_dir(), 3288 exename.subdir) 3289 else: 3290 search_dir = os.path.join(self.environment.get_source_dir(), 3291 exename.subdir) 3292 exename = exename.fname 3293 extra_search_dirs = [] 3294 elif isinstance(exename, str): 3295 search_dir = source_dir 3296 extra_search_dirs = search_dirs 3297 else: 3298 raise InvalidArguments('find_program only accepts strings and ' 3299 'files, not {!r}'.format(exename)) 3300 extprog = dependencies.ExternalProgram(exename, search_dir=search_dir, 3301 extra_search_dirs=extra_search_dirs, 3302 silent=silent) 3303 progobj = ExternalProgramHolder(extprog, self.subproject) 3304 if progobj.found(): 3305 return progobj 3306 3307 def program_from_overrides(self, command_names, extra_info): 3308 for name in command_names: 3309 if not isinstance(name, str): 3310 continue 3311 if name in self.build.find_overrides: 3312 exe = self.build.find_overrides[name] 3313 extra_info.append(mlog.blue('(overriden)')) 3314 return ExternalProgramHolder(exe, self.subproject, self.backend) 3315 return None 3316 3317 def store_name_lookups(self, command_names): 3318 for name in command_names: 3319 if isinstance(name, str): 3320 self.build.searched_programs.add(name) 3321 3322 def add_find_program_override(self, name, exe): 3323 if name in self.build.searched_programs: 3324 raise InterpreterException('Tried to override finding of executable "%s" which has already been found.' 3325 % name) 3326 if name in self.build.find_overrides: 3327 raise InterpreterException('Tried to override executable "%s" which has already been overridden.' 3328 % name) 3329 self.build.find_overrides[name] = exe 3330 3331 def notfound_program(self, args): 3332 return ExternalProgramHolder(dependencies.NonExistingExternalProgram(' '.join(args)), self.subproject) 3333 3334 # TODO update modules to always pass `for_machine`. It is bad-form to assume 3335 # the host machine. 3336 def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, 3337 required=True, silent=True, wanted='', search_dirs=None, 3338 version_func=None): 3339 args = mesonlib.listify(args) 3340 3341 extra_info = [] 3342 progobj = self.program_lookup(args, for_machine, required, search_dirs, extra_info) 3343 if progobj is None: 3344 progobj = self.notfound_program(args) 3345 3346 if not progobj.found(): 3347 mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO')) 3348 if required: 3349 m = 'Program {!r} not found' 3350 raise InterpreterException(m.format(progobj.get_name())) 3351 return progobj 3352 3353 if wanted: 3354 if version_func: 3355 version = version_func(progobj) 3356 else: 3357 version = progobj.get_version(self) 3358 is_found, not_found, found = mesonlib.version_compare_many(version, wanted) 3359 if not is_found: 3360 mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), 3361 'found', mlog.normal_cyan(version), 'but need:', 3362 mlog.bold(', '.join(["'{}'".format(e) for e in not_found]))) 3363 if required: 3364 m = 'Invalid version of program, need {!r} {!r} found {!r}.' 3365 raise InterpreterException(m.format(progobj.get_name(), not_found, version)) 3366 return self.notfound_program(args) 3367 extra_info.insert(0, mlog.normal_cyan(version)) 3368 3369 # Only store successful lookups 3370 self.store_name_lookups(args) 3371 mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.green('YES'), *extra_info) 3372 return progobj 3373 3374 def program_lookup(self, args, for_machine, required, search_dirs, extra_info): 3375 progobj = self.program_from_overrides(args, extra_info) 3376 if progobj: 3377 return progobj 3378 3379 fallback = None 3380 wrap_mode = self.coredata.get_builtin_option('wrap_mode') 3381 if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver: 3382 fallback = self.environment.wrap_resolver.find_program_provider(args) 3383 if fallback and wrap_mode == WrapMode.forcefallback: 3384 return self.find_program_fallback(fallback, args, required, extra_info) 3385 3386 progobj = self.program_from_file_for(for_machine, args) 3387 if progobj is None: 3388 progobj = self.program_from_system(args, search_dirs, silent=True) 3389 if progobj is None and args[0].endswith('python3'): 3390 prog = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) 3391 progobj = ExternalProgramHolder(prog, self.subproject) if prog.found() else None 3392 if progobj is None and fallback and required: 3393 progobj = self.find_program_fallback(fallback, args, required, extra_info) 3394 3395 return progobj 3396 3397 def find_program_fallback(self, fallback, args, required, extra_info): 3398 mlog.log('Fallback to subproject', mlog.bold(fallback), 'which provides program', 3399 mlog.bold(' '.join(args))) 3400 sp_kwargs = { 'required': required } 3401 self.do_subproject(fallback, 'meson', sp_kwargs) 3402 return self.program_from_overrides(args, extra_info) 3403 3404 @FeatureNewKwargs('find_program', '0.53.0', ['dirs']) 3405 @FeatureNewKwargs('find_program', '0.52.0', ['version']) 3406 @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) 3407 @disablerIfNotFound 3408 @permittedKwargs(permitted_kwargs['find_program']) 3409 def func_find_program(self, node, args, kwargs): 3410 if not args: 3411 raise InterpreterException('No program name specified.') 3412 3413 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) 3414 if disabled: 3415 mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled') 3416 return self.notfound_program(args) 3417 3418 search_dirs = extract_search_dirs(kwargs) 3419 wanted = mesonlib.stringlistify(kwargs.get('version', [])) 3420 for_machine = self.machine_from_native_kwarg(kwargs) 3421 return self.find_program_impl(args, for_machine, required=required, 3422 silent=False, wanted=wanted, 3423 search_dirs=search_dirs) 3424 3425 def func_find_library(self, node, args, kwargs): 3426 raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n' 3427 'Look here for documentation: http://mesonbuild.com/Reference-manual.html#compiler-object\n' 3428 'Look here for example: http://mesonbuild.com/howtox.html#add-math-library-lm-portably\n' 3429 ) 3430 3431 def _find_cached_dep(self, name, display_name, kwargs): 3432 # Check if we want this as a build-time / build machine or runt-time / 3433 # host machine dep. 3434 for_machine = self.machine_from_native_kwarg(kwargs) 3435 identifier = dependencies.get_dep_identifier(name, kwargs) 3436 wanted_vers = mesonlib.stringlistify(kwargs.get('version', [])) 3437 3438 override = self.build.dependency_overrides[for_machine].get(identifier) 3439 if override: 3440 info = [mlog.blue('(overridden)' if override.explicit else '(cached)')] 3441 cached_dep = override.dep 3442 # We don't implicitly override not-found dependencies, but user could 3443 # have explicitly called meson.override_dependency() with a not-found 3444 # dep. 3445 if not cached_dep.found(): 3446 mlog.log('Dependency', mlog.bold(display_name), 3447 'found:', mlog.red('NO'), *info) 3448 return identifier, cached_dep 3449 found_vers = cached_dep.get_version() 3450 if not self.check_version(wanted_vers, found_vers): 3451 mlog.log('Dependency', mlog.bold(name), 3452 'found:', mlog.red('NO'), 3453 'found', mlog.normal_cyan(found_vers), 'but need:', 3454 mlog.bold(', '.join(["'{}'".format(e) for e in wanted_vers])), 3455 *info) 3456 return identifier, NotFoundDependency(self.environment) 3457 else: 3458 info = [mlog.blue('(cached)')] 3459 cached_dep = self.coredata.deps[for_machine].get(identifier) 3460 if cached_dep: 3461 found_vers = cached_dep.get_version() 3462 if not self.check_version(wanted_vers, found_vers): 3463 return identifier, None 3464 3465 if cached_dep: 3466 if found_vers: 3467 info = [mlog.normal_cyan(found_vers), *info] 3468 mlog.log('Dependency', mlog.bold(display_name), 3469 'found:', mlog.green('YES'), *info) 3470 return identifier, cached_dep 3471 3472 return identifier, None 3473 3474 @staticmethod 3475 def check_version(wanted, found): 3476 if not wanted: 3477 return True 3478 if found == 'undefined' or not mesonlib.version_compare_many(found, wanted)[0]: 3479 return False 3480 return True 3481 3482 def notfound_dependency(self): 3483 return DependencyHolder(NotFoundDependency(self.environment), self.subproject) 3484 3485 def verify_fallback_consistency(self, dirname, varname, cached_dep): 3486 subi = self.get_subproject(dirname) 3487 if not cached_dep or not varname or not subi or not cached_dep.found(): 3488 return 3489 dep = subi.get_variable_method([varname], {}) 3490 if dep.held_object != cached_dep: 3491 m = 'Inconsistency: Subproject has overridden the dependency with another variable than {!r}' 3492 raise DependencyException(m.format(varname)) 3493 3494 def get_subproject_dep(self, name, display_name, dirname, varname, kwargs): 3495 required = kwargs.get('required', True) 3496 wanted = mesonlib.stringlistify(kwargs.get('version', [])) 3497 subproj_path = os.path.join(self.subproject_dir, dirname) 3498 dep = self.notfound_dependency() 3499 try: 3500 subproject = self.subprojects[dirname] 3501 _, cached_dep = self._find_cached_dep(name, display_name, kwargs) 3502 if varname is None: 3503 # Assuming the subproject overridden the dependency we want 3504 if cached_dep: 3505 if required and not cached_dep.found(): 3506 m = 'Dependency {!r} is not satisfied' 3507 raise DependencyException(m.format(display_name)) 3508 return DependencyHolder(cached_dep, self.subproject) 3509 else: 3510 if required: 3511 m = 'Subproject {} did not override dependency {}' 3512 raise DependencyException(m.format(subproj_path, display_name)) 3513 mlog.log('Dependency', mlog.bold(display_name), 'from subproject', 3514 mlog.bold(subproj_path), 'found:', mlog.red('NO')) 3515 return self.notfound_dependency() 3516 if subproject.found(): 3517 self.verify_fallback_consistency(dirname, varname, cached_dep) 3518 dep = self.subprojects[dirname].get_variable_method([varname], {}) 3519 except InvalidArguments: 3520 pass 3521 3522 if not isinstance(dep, DependencyHolder): 3523 raise InvalidCode('Fetched variable {!r} in the subproject {!r} is ' 3524 'not a dependency object.'.format(varname, dirname)) 3525 3526 if not dep.found(): 3527 if required: 3528 raise DependencyException('Could not find dependency {} in subproject {}' 3529 ''.format(varname, dirname)) 3530 # If the dependency is not required, don't raise an exception 3531 mlog.log('Dependency', mlog.bold(display_name), 'from subproject', 3532 mlog.bold(subproj_path), 'found:', mlog.red('NO')) 3533 return dep 3534 3535 found = dep.held_object.get_version() 3536 if not self.check_version(wanted, found): 3537 if required: 3538 raise DependencyException('Version {} of subproject dependency {} already ' 3539 'cached, requested incompatible version {} for ' 3540 'dep {}'.format(found, dirname, wanted, display_name)) 3541 3542 mlog.log('Dependency', mlog.bold(display_name), 'from subproject', 3543 mlog.bold(subproj_path), 'found:', mlog.red('NO'), 3544 'found', mlog.normal_cyan(found), 'but need:', 3545 mlog.bold(', '.join(["'{}'".format(e) for e in wanted]))) 3546 return self.notfound_dependency() 3547 3548 found = mlog.normal_cyan(found) if found else None 3549 mlog.log('Dependency', mlog.bold(display_name), 'from subproject', 3550 mlog.bold(subproj_path), 'found:', mlog.green('YES'), found) 3551 return dep 3552 3553 def _handle_featurenew_dependencies(self, name): 3554 'Do a feature check on dependencies used by this subproject' 3555 if name == 'mpi': 3556 FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject) 3557 elif name == 'pcap': 3558 FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject) 3559 elif name == 'vulkan': 3560 FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject) 3561 elif name == 'libwmf': 3562 FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject) 3563 elif name == 'openmp': 3564 FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject) 3565 3566 @FeatureNewKwargs('dependency', '0.54.0', ['components']) 3567 @FeatureNewKwargs('dependency', '0.52.0', ['include_type']) 3568 @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args']) 3569 @FeatureNewKwargs('dependency', '0.49.0', ['disabler']) 3570 @FeatureNewKwargs('dependency', '0.40.0', ['method']) 3571 @FeatureNewKwargs('dependency', '0.38.0', ['default_options']) 3572 @disablerIfNotFound 3573 @permittedKwargs(permitted_kwargs['dependency']) 3574 def func_dependency(self, node, args, kwargs): 3575 self.validate_arguments(args, 1, [str]) 3576 name = args[0] 3577 display_name = name if name else '(anonymous)' 3578 mods = extract_as_list(kwargs, 'modules') 3579 if mods: 3580 display_name += ' (modules: {})'.format(', '.join(str(i) for i in mods)) 3581 not_found_message = kwargs.get('not_found_message', '') 3582 if not isinstance(not_found_message, str): 3583 raise InvalidArguments('The not_found_message must be a string.') 3584 try: 3585 d = self.dependency_impl(name, display_name, kwargs) 3586 except Exception: 3587 if not_found_message: 3588 self.message_impl([not_found_message]) 3589 raise 3590 if not d.found() and not_found_message: 3591 self.message_impl([not_found_message]) 3592 self.message_impl([not_found_message]) 3593 # Override this dependency to have consistent results in subsequent 3594 # dependency lookups. 3595 if name and d.found(): 3596 for_machine = self.machine_from_native_kwarg(kwargs) 3597 identifier = dependencies.get_dep_identifier(name, kwargs) 3598 if identifier not in self.build.dependency_overrides[for_machine]: 3599 self.build.dependency_overrides[for_machine][identifier] = \ 3600 build.DependencyOverride(d.held_object, node, explicit=False) 3601 return d 3602 3603 def dependency_impl(self, name, display_name, kwargs): 3604 disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) 3605 if disabled: 3606 mlog.log('Dependency', mlog.bold(display_name), 'skipped: feature', mlog.bold(feature), 'disabled') 3607 return self.notfound_dependency() 3608 3609 has_fallback = 'fallback' in kwargs 3610 if not has_fallback and name: 3611 # Add an implicit fallback if we have a wrap file or a directory with the same name, 3612 # but only if this dependency is required. It is common to first check for a pkg-config, 3613 # then fallback to use find_library() and only afterward check again the dependency 3614 # with a fallback. If the fallback has already been configured then we have to use it 3615 # even if the dependency is not required. 3616 provider = self.environment.wrap_resolver.find_dep_provider(name) 3617 dirname = mesonlib.listify(provider)[0] 3618 if provider and (required or self.get_subproject(dirname)): 3619 kwargs['fallback'] = provider 3620 has_fallback = True 3621 3622 if 'default_options' in kwargs and not has_fallback: 3623 mlog.warning('The "default_options" keyworg argument does nothing without a "fallback" keyword argument.', 3624 location=self.current_node) 3625 3626 # writing just "dependency('')" is an error, because it can only fail 3627 if name == '' and required and not has_fallback: 3628 raise InvalidArguments('Dependency is both required and not-found') 3629 3630 if '<' in name or '>' in name or '=' in name: 3631 raise InvalidArguments('Characters <, > and = are forbidden in dependency names. To specify' 3632 'version\n requirements use the \'version\' keyword argument instead.') 3633 3634 identifier, cached_dep = self._find_cached_dep(name, display_name, kwargs) 3635 if cached_dep: 3636 if has_fallback: 3637 dirname, varname = self.get_subproject_infos(kwargs) 3638 self.verify_fallback_consistency(dirname, varname, cached_dep) 3639 if required and not cached_dep.found(): 3640 m = 'Dependency {!r} was already checked and was not found' 3641 raise DependencyException(m.format(display_name)) 3642 return DependencyHolder(cached_dep, self.subproject) 3643 3644 # If the dependency has already been configured, possibly by 3645 # a higher level project, try to use it first. 3646 if has_fallback: 3647 dirname, varname = self.get_subproject_infos(kwargs) 3648 if self.get_subproject(dirname): 3649 return self.get_subproject_dep(name, display_name, dirname, varname, kwargs) 3650 3651 wrap_mode = self.coredata.get_builtin_option('wrap_mode') 3652 force_fallback_for = self.coredata.get_builtin_option('force_fallback_for') 3653 forcefallback = has_fallback and (wrap_mode == WrapMode.forcefallback or \ 3654 name in force_fallback_for or \ 3655 dirname in force_fallback_for) 3656 if name != '' and not forcefallback: 3657 self._handle_featurenew_dependencies(name) 3658 kwargs['required'] = required and not has_fallback 3659 dep = dependencies.find_external_dependency(name, self.environment, kwargs) 3660 kwargs['required'] = required 3661 # Only store found-deps in the cache 3662 # Never add fallback deps to self.coredata.deps since we 3663 # cannot cache them. They must always be evaluated else 3664 # we won't actually read all the build files. 3665 if dep.found(): 3666 for_machine = self.machine_from_native_kwarg(kwargs) 3667 self.coredata.deps[for_machine].put(identifier, dep) 3668 return DependencyHolder(dep, self.subproject) 3669 3670 if has_fallback: 3671 return self.dependency_fallback(name, display_name, kwargs) 3672 3673 return self.notfound_dependency() 3674 3675 @FeatureNew('disabler', '0.44.0') 3676 @noKwargs 3677 @noPosargs 3678 def func_disabler(self, node, args, kwargs): 3679 return Disabler() 3680 3681 def print_nested_info(self, dependency_name): 3682 message = ['Dependency', mlog.bold(dependency_name), 'not found but it is available in a sub-subproject.\n' + 3683 'To use it in the current project, promote it by going in the project source\n' 3684 'root and issuing'] 3685 sprojs = mesonlib.detect_subprojects('subprojects', self.source_root) 3686 if dependency_name not in sprojs: 3687 return 3688 found = sprojs[dependency_name] 3689 if len(found) > 1: 3690 message.append('one of the following commands:') 3691 else: 3692 message.append('the following command:') 3693 command_templ = '\nmeson wrap promote {}' 3694 for l in found: 3695 message.append(mlog.bold(command_templ.format(l[len(self.source_root) + 1:]))) 3696 mlog.warning(*message, location=self.current_node) 3697 3698 def get_subproject_infos(self, kwargs): 3699 fbinfo = mesonlib.stringlistify(kwargs['fallback']) 3700 if len(fbinfo) == 1: 3701 FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject) 3702 return fbinfo[0], None 3703 elif len(fbinfo) != 2: 3704 raise InterpreterException('Fallback info must have one or two items.') 3705 return fbinfo 3706 3707 def dependency_fallback(self, name, display_name, kwargs): 3708 dirname, varname = self.get_subproject_infos(kwargs) 3709 required = kwargs.get('required', True) 3710 3711 # Explicitly listed fallback preferences for specific subprojects 3712 # take precedence over wrap-mode 3713 force_fallback_for = self.coredata.get_builtin_option('force_fallback_for') 3714 if name in force_fallback_for or dirname in force_fallback_for: 3715 mlog.log('Looking for a fallback subproject for the dependency', 3716 mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject') 3717 elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback: 3718 mlog.log('Not looking for a fallback subproject for the dependency', 3719 mlog.bold(display_name), 'because:\nUse of fallback ' 3720 'dependencies is disabled.') 3721 if required: 3722 m = 'Dependency {!r} not found and fallback is disabled' 3723 raise DependencyException(m.format(display_name)) 3724 return self.notfound_dependency() 3725 elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.forcefallback: 3726 mlog.log('Looking for a fallback subproject for the dependency', 3727 mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.') 3728 else: 3729 mlog.log('Looking for a fallback subproject for the dependency', 3730 mlog.bold(display_name)) 3731 sp_kwargs = { 3732 'default_options': kwargs.get('default_options', []), 3733 'required': required, 3734 } 3735 self.do_subproject(dirname, 'meson', sp_kwargs) 3736 return self.get_subproject_dep(name, display_name, dirname, varname, kwargs) 3737 3738 @FeatureNewKwargs('executable', '0.42.0', ['implib']) 3739 @permittedKwargs(permitted_kwargs['executable']) 3740 def func_executable(self, node, args, kwargs): 3741 return self.build_target(node, args, kwargs, ExecutableHolder) 3742 3743 @permittedKwargs(permitted_kwargs['static_library']) 3744 def func_static_lib(self, node, args, kwargs): 3745 return self.build_target(node, args, kwargs, StaticLibraryHolder) 3746 3747 @permittedKwargs(permitted_kwargs['shared_library']) 3748 def func_shared_lib(self, node, args, kwargs): 3749 holder = self.build_target(node, args, kwargs, SharedLibraryHolder) 3750 holder.held_object.shared_library_only = True 3751 return holder 3752 3753 @permittedKwargs(permitted_kwargs['both_libraries']) 3754 def func_both_lib(self, node, args, kwargs): 3755 return self.build_both_libraries(node, args, kwargs) 3756 3757 @FeatureNew('shared_module', '0.37.0') 3758 @permittedKwargs(permitted_kwargs['shared_module']) 3759 def func_shared_module(self, node, args, kwargs): 3760 return self.build_target(node, args, kwargs, SharedModuleHolder) 3761 3762 @permittedKwargs(permitted_kwargs['library']) 3763 def func_library(self, node, args, kwargs): 3764 return self.build_library(node, args, kwargs) 3765 3766 @permittedKwargs(permitted_kwargs['jar']) 3767 def func_jar(self, node, args, kwargs): 3768 return self.build_target(node, args, kwargs, JarHolder) 3769 3770 @FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options']) 3771 @permittedKwargs(permitted_kwargs['build_target']) 3772 def func_build_target(self, node, args, kwargs): 3773 if 'target_type' not in kwargs: 3774 raise InterpreterException('Missing target_type keyword argument') 3775 target_type = kwargs.pop('target_type') 3776 if target_type == 'executable': 3777 return self.build_target(node, args, kwargs, ExecutableHolder) 3778 elif target_type == 'shared_library': 3779 return self.build_target(node, args, kwargs, SharedLibraryHolder) 3780 elif target_type == 'shared_module': 3781 FeatureNew('build_target(target_type: \'shared_module\')', 3782 '0.51.0').use(self.subproject) 3783 return self.build_target(node, args, kwargs, SharedModuleHolder) 3784 elif target_type == 'static_library': 3785 return self.build_target(node, args, kwargs, StaticLibraryHolder) 3786 elif target_type == 'both_libraries': 3787 return self.build_both_libraries(node, args, kwargs) 3788 elif target_type == 'library': 3789 return self.build_library(node, args, kwargs) 3790 elif target_type == 'jar': 3791 return self.build_target(node, args, kwargs, JarHolder) 3792 else: 3793 raise InterpreterException('Unknown target_type.') 3794 3795 @permittedKwargs(permitted_kwargs['vcs_tag']) 3796 @FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'], 3797 'combine build_by_default and build_always_stale instead.') 3798 def func_vcs_tag(self, node, args, kwargs): 3799 if 'input' not in kwargs or 'output' not in kwargs: 3800 raise InterpreterException('Keyword arguments input and output must exist') 3801 if 'fallback' not in kwargs: 3802 FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject) 3803 fallback = kwargs.pop('fallback', self.project_version) 3804 if not isinstance(fallback, str): 3805 raise InterpreterException('Keyword argument fallback must be a string.') 3806 replace_string = kwargs.pop('replace_string', '@VCS_TAG@') 3807 regex_selector = '(.*)' # default regex selector for custom command: use complete output 3808 vcs_cmd = kwargs.get('command', None) 3809 if vcs_cmd and not isinstance(vcs_cmd, list): 3810 vcs_cmd = [vcs_cmd] 3811 source_dir = os.path.normpath(os.path.join(self.environment.get_source_dir(), self.subdir)) 3812 if vcs_cmd: 3813 # Is the command an executable in path or maybe a script in the source tree? 3814 vcs_cmd[0] = shutil.which(vcs_cmd[0]) or os.path.join(source_dir, vcs_cmd[0]) 3815 else: 3816 vcs = mesonlib.detect_vcs(source_dir) 3817 if vcs: 3818 mlog.log('Found %s repository at %s' % (vcs['name'], vcs['wc_dir'])) 3819 vcs_cmd = vcs['get_rev'].split() 3820 regex_selector = vcs['rev_regex'] 3821 else: 3822 vcs_cmd = [' '] # executing this cmd will fail in vcstagger.py and force to use the fallback string 3823 # vcstagger.py parameters: infile, outfile, fallback, source_dir, replace_string, regex_selector, command... 3824 kwargs['command'] = self.environment.get_build_command() + \ 3825 ['--internal', 3826 'vcstagger', 3827 '@INPUT0@', 3828 '@OUTPUT0@', 3829 fallback, 3830 source_dir, 3831 replace_string, 3832 regex_selector] + vcs_cmd 3833 kwargs.setdefault('build_by_default', True) 3834 kwargs.setdefault('build_always_stale', True) 3835 return self._func_custom_target_impl(node, [kwargs['output']], kwargs) 3836 3837 @FeatureNew('subdir_done', '0.46.0') 3838 @stringArgs 3839 def func_subdir_done(self, node, args, kwargs): 3840 if len(kwargs) > 0: 3841 raise InterpreterException('exit does not take named arguments') 3842 if len(args) > 0: 3843 raise InterpreterException('exit does not take any arguments') 3844 raise SubdirDoneRequest() 3845 3846 @stringArgs 3847 @FeatureNewKwargs('custom_target', '0.48.0', ['console']) 3848 @FeatureNewKwargs('custom_target', '0.47.0', ['install_mode', 'build_always_stale']) 3849 @FeatureNewKwargs('custom_target', '0.40.0', ['build_by_default']) 3850 @permittedKwargs(permitted_kwargs['custom_target']) 3851 def func_custom_target(self, node, args, kwargs): 3852 if len(args) != 1: 3853 raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name') 3854 if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']): 3855 FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject) 3856 return self._func_custom_target_impl(node, args, kwargs) 3857 3858 def _func_custom_target_impl(self, node, args, kwargs): 3859 'Implementation-only, without FeatureNew checks, for internal use' 3860 name = args[0] 3861 kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) 3862 if 'input' in kwargs: 3863 try: 3864 kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input')) 3865 except mesonlib.MesonException: 3866 mlog.warning('''Custom target input \'%s\' can\'t be converted to File object(s). 3867This will become a hard error in the future.''' % kwargs['input'], location=self.current_node) 3868 tg = CustomTargetHolder(build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend), self) 3869 self.add_target(name, tg.held_object) 3870 return tg 3871 3872 @permittedKwargs(permitted_kwargs['run_target']) 3873 def func_run_target(self, node, args, kwargs): 3874 if len(args) > 1: 3875 raise InvalidCode('Run_target takes only one positional argument: the target name.') 3876 elif len(args) == 1: 3877 if 'command' not in kwargs: 3878 raise InterpreterException('Missing "command" keyword argument') 3879 all_args = extract_as_list(kwargs, 'command') 3880 deps = unholder(extract_as_list(kwargs, 'depends')) 3881 else: 3882 raise InterpreterException('Run_target needs at least one positional argument.') 3883 3884 cleaned_args = [] 3885 for i in unholder(listify(all_args)): 3886 if not isinstance(i, (str, build.BuildTarget, build.CustomTarget, dependencies.ExternalProgram, mesonlib.File)): 3887 mlog.debug('Wrong type:', str(i)) 3888 raise InterpreterException('Invalid argument to run_target.') 3889 if isinstance(i, dependencies.ExternalProgram) and not i.found(): 3890 raise InterpreterException('Tried to use non-existing executable {!r}'.format(i.name)) 3891 cleaned_args.append(i) 3892 name = args[0] 3893 if not isinstance(name, str): 3894 raise InterpreterException('First argument must be a string.') 3895 cleaned_deps = [] 3896 for d in deps: 3897 if not isinstance(d, (build.BuildTarget, build.CustomTarget)): 3898 raise InterpreterException('Depends items must be build targets.') 3899 cleaned_deps.append(d) 3900 command, *cmd_args = cleaned_args 3901 tg = RunTargetHolder(build.RunTarget(name, command, cmd_args, cleaned_deps, self.subdir, self.subproject), self) 3902 self.add_target(name, tg.held_object) 3903 full_name = (self.subproject, name) 3904 assert(full_name not in self.build.run_target_names) 3905 self.build.run_target_names.add(full_name) 3906 return tg 3907 3908 @FeatureNew('alias_target', '0.52.0') 3909 @noKwargs 3910 def func_alias_target(self, node, args, kwargs): 3911 if len(args) < 2: 3912 raise InvalidCode('alias_target takes at least 2 arguments.') 3913 name = args[0] 3914 if not isinstance(name, str): 3915 raise InterpreterException('First argument must be a string.') 3916 deps = unholder(listify(args[1:])) 3917 for d in deps: 3918 if not isinstance(d, (build.BuildTarget, build.CustomTarget)): 3919 raise InterpreterException('Depends items must be build targets.') 3920 tg = RunTargetHolder(build.AliasTarget(name, deps, self.subdir, self.subproject), self) 3921 self.add_target(name, tg.held_object) 3922 return tg 3923 3924 @permittedKwargs(permitted_kwargs['generator']) 3925 def func_generator(self, node, args, kwargs): 3926 gen = GeneratorHolder(self, args, kwargs) 3927 self.generators.append(gen) 3928 return gen 3929 3930 @FeatureNewKwargs('benchmark', '0.46.0', ['depends']) 3931 @FeatureNewKwargs('benchmark', '0.52.0', ['priority']) 3932 @permittedKwargs(permitted_kwargs['benchmark']) 3933 def func_benchmark(self, node, args, kwargs): 3934 # is_parallel isn't valid here, so make sure it isn't passed 3935 if 'is_parallel' in kwargs: 3936 del kwargs['is_parallel'] 3937 self.add_test(node, args, kwargs, False) 3938 3939 @FeatureNewKwargs('test', '0.46.0', ['depends']) 3940 @FeatureNewKwargs('test', '0.52.0', ['priority']) 3941 @permittedKwargs(permitted_kwargs['test']) 3942 def func_test(self, node, args, kwargs): 3943 if kwargs.get('protocol') == 'gtest': 3944 FeatureNew.single_use('"gtest" protocol for tests', '0.55.0', self.subproject) 3945 self.add_test(node, args, kwargs, True) 3946 3947 def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables: 3948 envlist = kwargs.get('env', EnvironmentVariablesHolder()) 3949 if isinstance(envlist, EnvironmentVariablesHolder): 3950 env = envlist.held_object 3951 elif isinstance(envlist, dict): 3952 FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject) 3953 env = EnvironmentVariablesHolder(envlist) 3954 env = env.held_object 3955 else: 3956 envlist = listify(envlist) 3957 # Convert from array to environment object 3958 env = EnvironmentVariablesHolder(envlist) 3959 env = env.held_object 3960 return env 3961 3962 def add_test(self, node, args, kwargs, is_base_test): 3963 if len(args) != 2: 3964 raise InterpreterException('test expects 2 arguments, {} given'.format(len(args))) 3965 if not isinstance(args[0], str): 3966 raise InterpreterException('First argument of test must be a string.') 3967 exe = args[1] 3968 if not isinstance(exe, (ExecutableHolder, JarHolder, ExternalProgramHolder)): 3969 if isinstance(exe, mesonlib.File): 3970 exe = self.func_find_program(node, args[1], {}) 3971 else: 3972 raise InterpreterException('Second argument must be executable.') 3973 par = kwargs.get('is_parallel', True) 3974 if not isinstance(par, bool): 3975 raise InterpreterException('Keyword argument is_parallel must be a boolean.') 3976 cmd_args = unholder(extract_as_list(kwargs, 'args')) 3977 for i in cmd_args: 3978 if not isinstance(i, (str, mesonlib.File, build.Target)): 3979 raise InterpreterException('Command line arguments must be strings, files or targets.') 3980 env = self.unpack_env_kwarg(kwargs) 3981 should_fail = kwargs.get('should_fail', False) 3982 if not isinstance(should_fail, bool): 3983 raise InterpreterException('Keyword argument should_fail must be a boolean.') 3984 timeout = kwargs.get('timeout', 30) 3985 if 'workdir' in kwargs: 3986 workdir = kwargs['workdir'] 3987 if not isinstance(workdir, str): 3988 raise InterpreterException('Workdir keyword argument must be a string.') 3989 if not os.path.isabs(workdir): 3990 raise InterpreterException('Workdir keyword argument must be an absolute path.') 3991 else: 3992 workdir = None 3993 if not isinstance(timeout, int): 3994 raise InterpreterException('Timeout must be an integer.') 3995 protocol = kwargs.get('protocol', 'exitcode') 3996 if protocol not in {'exitcode', 'tap', 'gtest'}: 3997 raise InterpreterException('Protocol must be "exitcode", "tap", or "gtest".') 3998 suite = [] 3999 prj = self.subproject if self.is_subproject() else self.build.project_name 4000 for s in mesonlib.stringlistify(kwargs.get('suite', '')): 4001 if len(s) > 0: 4002 s = ':' + s 4003 suite.append(prj.replace(' ', '_').replace(':', '_') + s) 4004 depends = unholder(extract_as_list(kwargs, 'depends')) 4005 for dep in depends: 4006 if not isinstance(dep, (build.CustomTarget, build.BuildTarget)): 4007 raise InterpreterException('Depends items must be build targets.') 4008 priority = kwargs.get('priority', 0) 4009 if not isinstance(priority, int): 4010 raise InterpreterException('Keyword argument priority must be an integer.') 4011 t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args, 4012 env, should_fail, timeout, workdir, protocol, priority) 4013 if is_base_test: 4014 self.build.tests.append(t) 4015 mlog.debug('Adding test', mlog.bold(args[0], True)) 4016 else: 4017 self.build.benchmarks.append(t) 4018 mlog.debug('Adding benchmark', mlog.bold(args[0], True)) 4019 4020 @FeatureNewKwargs('install_headers', '0.47.0', ['install_mode']) 4021 @permittedKwargs(permitted_kwargs['install_headers']) 4022 def func_install_headers(self, node, args, kwargs): 4023 source_files = self.source_strings_to_files(args) 4024 kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) 4025 h = Headers(source_files, kwargs) 4026 self.build.headers.append(h) 4027 return h 4028 4029 @FeatureNewKwargs('install_man', '0.47.0', ['install_mode']) 4030 @permittedKwargs(permitted_kwargs['install_man']) 4031 def func_install_man(self, node, args, kwargs): 4032 fargs = self.source_strings_to_files(args) 4033 kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) 4034 m = Man(fargs, kwargs) 4035 self.build.man.append(m) 4036 return m 4037 4038 @FeatureNewKwargs('subdir', '0.44.0', ['if_found']) 4039 @permittedKwargs(permitted_kwargs['subdir']) 4040 def func_subdir(self, node, args, kwargs): 4041 self.validate_arguments(args, 1, [str]) 4042 mesonlib.check_direntry_issues(args) 4043 if '..' in args[0]: 4044 raise InvalidArguments('Subdir contains ..') 4045 if self.subdir == '' and args[0] == self.subproject_dir: 4046 raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.') 4047 if self.subdir == '' and args[0].startswith('meson-'): 4048 raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().') 4049 for i in mesonlib.extract_as_list(kwargs, 'if_found'): 4050 if not hasattr(i, 'found_method'): 4051 raise InterpreterException('Object used in if_found does not have a found method.') 4052 if not i.found_method([], {}): 4053 return 4054 prev_subdir = self.subdir 4055 subdir = os.path.join(prev_subdir, args[0]) 4056 if os.path.isabs(subdir): 4057 raise InvalidArguments('Subdir argument must be a relative path.') 4058 absdir = os.path.join(self.environment.get_source_dir(), subdir) 4059 symlinkless_dir = os.path.realpath(absdir) 4060 if symlinkless_dir in self.visited_subdirs: 4061 raise InvalidArguments('Tried to enter directory "%s", which has already been visited.' 4062 % subdir) 4063 self.visited_subdirs[symlinkless_dir] = True 4064 self.subdir = subdir 4065 os.makedirs(os.path.join(self.environment.build_dir, subdir), exist_ok=True) 4066 buildfilename = os.path.join(self.subdir, environment.build_filename) 4067 self.build_def_files.append(buildfilename) 4068 absname = os.path.join(self.environment.get_source_dir(), buildfilename) 4069 if not os.path.isfile(absname): 4070 self.subdir = prev_subdir 4071 raise InterpreterException("Non-existent build file '{!s}'".format(buildfilename)) 4072 with open(absname, encoding='utf8') as f: 4073 code = f.read() 4074 assert(isinstance(code, str)) 4075 try: 4076 codeblock = mparser.Parser(code, absname).parse() 4077 except mesonlib.MesonException as me: 4078 me.file = absname 4079 raise me 4080 try: 4081 self.evaluate_codeblock(codeblock) 4082 except SubdirDoneRequest: 4083 pass 4084 self.subdir = prev_subdir 4085 4086 def _get_kwarg_install_mode(self, kwargs): 4087 if kwargs.get('install_mode', None) is None: 4088 return None 4089 install_mode = [] 4090 mode = mesonlib.typeslistify(kwargs.get('install_mode', []), (str, int)) 4091 for m in mode: 4092 # We skip any arguments that are set to `false` 4093 if m is False: 4094 m = None 4095 install_mode.append(m) 4096 if len(install_mode) > 3: 4097 raise InvalidArguments('Keyword argument install_mode takes at ' 4098 'most 3 arguments.') 4099 if len(install_mode) > 0 and install_mode[0] is not None and \ 4100 not isinstance(install_mode[0], str): 4101 raise InvalidArguments('Keyword argument install_mode requires the ' 4102 'permissions arg to be a string or false') 4103 return FileMode(*install_mode) 4104 4105 @FeatureNewKwargs('install_data', '0.46.0', ['rename']) 4106 @FeatureNewKwargs('install_data', '0.38.0', ['install_mode']) 4107 @permittedKwargs(permitted_kwargs['install_data']) 4108 def func_install_data(self, node, args, kwargs): 4109 kwsource = mesonlib.stringlistify(kwargs.get('sources', [])) 4110 raw_sources = args + kwsource 4111 sources = [] 4112 source_strings = [] 4113 for s in raw_sources: 4114 if isinstance(s, mesonlib.File): 4115 sources.append(s) 4116 elif isinstance(s, str): 4117 source_strings.append(s) 4118 else: 4119 raise InvalidArguments('Argument must be string or file.') 4120 sources += self.source_strings_to_files(source_strings) 4121 install_dir = kwargs.get('install_dir', None) 4122 if not isinstance(install_dir, (str, type(None))): 4123 raise InvalidArguments('Keyword argument install_dir not a string.') 4124 install_mode = self._get_kwarg_install_mode(kwargs) 4125 rename = kwargs.get('rename', None) 4126 data = DataHolder(build.Data(sources, install_dir, install_mode, rename)) 4127 self.build.data.append(data.held_object) 4128 return data 4129 4130 @FeatureNewKwargs('install_subdir', '0.42.0', ['exclude_files', 'exclude_directories']) 4131 @FeatureNewKwargs('install_subdir', '0.38.0', ['install_mode']) 4132 @permittedKwargs(permitted_kwargs['install_subdir']) 4133 @stringArgs 4134 def func_install_subdir(self, node, args, kwargs): 4135 if len(args) != 1: 4136 raise InvalidArguments('Install_subdir requires exactly one argument.') 4137 subdir = args[0] 4138 if 'install_dir' not in kwargs: 4139 raise InvalidArguments('Missing keyword argument install_dir') 4140 install_dir = kwargs['install_dir'] 4141 if not isinstance(install_dir, str): 4142 raise InvalidArguments('Keyword argument install_dir not a string.') 4143 if 'strip_directory' in kwargs: 4144 if not isinstance(kwargs['strip_directory'], bool): 4145 raise InterpreterException('"strip_directory" keyword must be a boolean.') 4146 strip_directory = kwargs['strip_directory'] 4147 else: 4148 strip_directory = False 4149 if 'exclude_files' in kwargs: 4150 exclude = extract_as_list(kwargs, 'exclude_files') 4151 for f in exclude: 4152 if not isinstance(f, str): 4153 raise InvalidArguments('Exclude argument not a string.') 4154 elif os.path.isabs(f): 4155 raise InvalidArguments('Exclude argument cannot be absolute.') 4156 exclude_files = set(exclude) 4157 else: 4158 exclude_files = set() 4159 if 'exclude_directories' in kwargs: 4160 exclude = extract_as_list(kwargs, 'exclude_directories') 4161 for d in exclude: 4162 if not isinstance(d, str): 4163 raise InvalidArguments('Exclude argument not a string.') 4164 elif os.path.isabs(d): 4165 raise InvalidArguments('Exclude argument cannot be absolute.') 4166 exclude_directories = set(exclude) 4167 else: 4168 exclude_directories = set() 4169 exclude = (exclude_files, exclude_directories) 4170 install_mode = self._get_kwarg_install_mode(kwargs) 4171 idir = InstallDir(self.subdir, subdir, install_dir, install_mode, exclude, strip_directory) 4172 self.build.install_dirs.append(idir) 4173 return idir 4174 4175 @FeatureNewKwargs('configure_file', '0.47.0', ['copy', 'output_format', 'install_mode', 'encoding']) 4176 @FeatureNewKwargs('configure_file', '0.46.0', ['format']) 4177 @FeatureNewKwargs('configure_file', '0.41.0', ['capture']) 4178 @FeatureNewKwargs('configure_file', '0.50.0', ['install']) 4179 @FeatureNewKwargs('configure_file', '0.52.0', ['depfile']) 4180 @permittedKwargs(permitted_kwargs['configure_file']) 4181 def func_configure_file(self, node, args, kwargs): 4182 if len(args) > 0: 4183 raise InterpreterException("configure_file takes only keyword arguments.") 4184 if 'output' not in kwargs: 4185 raise InterpreterException('Required keyword argument "output" not defined.') 4186 actions = set(['configuration', 'command', 'copy']).intersection(kwargs.keys()) 4187 if len(actions) == 0: 4188 raise InterpreterException('Must specify an action with one of these ' 4189 'keyword arguments: \'configuration\', ' 4190 '\'command\', or \'copy\'.') 4191 elif len(actions) == 2: 4192 raise InterpreterException('Must not specify both {!r} and {!r} ' 4193 'keyword arguments since they are ' 4194 'mutually exclusive.'.format(*actions)) 4195 elif len(actions) == 3: 4196 raise InterpreterException('Must specify one of {!r}, {!r}, and ' 4197 '{!r} keyword arguments since they are ' 4198 'mutually exclusive.'.format(*actions)) 4199 if 'capture' in kwargs: 4200 if not isinstance(kwargs['capture'], bool): 4201 raise InterpreterException('"capture" keyword must be a boolean.') 4202 if 'command' not in kwargs: 4203 raise InterpreterException('"capture" keyword requires "command" keyword.') 4204 4205 if 'format' in kwargs: 4206 fmt = kwargs['format'] 4207 if not isinstance(fmt, str): 4208 raise InterpreterException('"format" keyword must be a string.') 4209 else: 4210 fmt = 'meson' 4211 4212 if fmt not in ('meson', 'cmake', 'cmake@'): 4213 raise InterpreterException('"format" possible values are "meson", "cmake" or "cmake@".') 4214 4215 if 'output_format' in kwargs: 4216 output_format = kwargs['output_format'] 4217 if not isinstance(output_format, str): 4218 raise InterpreterException('"output_format" keyword must be a string.') 4219 else: 4220 output_format = 'c' 4221 4222 if output_format not in ('c', 'nasm'): 4223 raise InterpreterException('"format" possible values are "c" or "nasm".') 4224 4225 if 'depfile' in kwargs: 4226 depfile = kwargs['depfile'] 4227 if not isinstance(depfile, str): 4228 raise InterpreterException('depfile file name must be a string') 4229 else: 4230 depfile = None 4231 4232 # Validate input 4233 inputs = self.source_strings_to_files(extract_as_list(kwargs, 'input')) 4234 inputs_abs = [] 4235 for f in inputs: 4236 if isinstance(f, mesonlib.File): 4237 inputs_abs.append(f.absolute_path(self.environment.source_dir, 4238 self.environment.build_dir)) 4239 self.add_build_def_file(f) 4240 else: 4241 raise InterpreterException('Inputs can only be strings or file objects') 4242 # Validate output 4243 output = kwargs['output'] 4244 if not isinstance(output, str): 4245 raise InterpreterException('Output file name must be a string') 4246 if inputs_abs: 4247 values = mesonlib.get_filenames_templates_dict(inputs_abs, None) 4248 outputs = mesonlib.substitute_values([output], values) 4249 output = outputs[0] 4250 if depfile: 4251 depfile = mesonlib.substitute_values([depfile], values)[0] 4252 ofile_rpath = os.path.join(self.subdir, output) 4253 if ofile_rpath in self.configure_file_outputs: 4254 mesonbuildfile = os.path.join(self.subdir, 'meson.build') 4255 current_call = "{}:{}".format(mesonbuildfile, self.current_lineno) 4256 first_call = "{}:{}".format(mesonbuildfile, self.configure_file_outputs[ofile_rpath]) 4257 mlog.warning('Output file', mlog.bold(ofile_rpath, True), 'for configure_file() at', current_call, 'overwrites configure_file() output at', first_call) 4258 else: 4259 self.configure_file_outputs[ofile_rpath] = self.current_lineno 4260 if os.path.dirname(output) != '': 4261 raise InterpreterException('Output file name must not contain a subdirectory.') 4262 (ofile_path, ofile_fname) = os.path.split(os.path.join(self.subdir, output)) 4263 ofile_abs = os.path.join(self.environment.build_dir, ofile_path, ofile_fname) 4264 # Perform the appropriate action 4265 if 'configuration' in kwargs: 4266 conf = kwargs['configuration'] 4267 if isinstance(conf, dict): 4268 FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject) 4269 conf = ConfigurationDataHolder(self.subproject, conf) 4270 elif not isinstance(conf, ConfigurationDataHolder): 4271 raise InterpreterException('Argument "configuration" is not of type configuration_data') 4272 mlog.log('Configuring', mlog.bold(output), 'using configuration') 4273 if len(inputs) > 1: 4274 raise InterpreterException('At most one input file can given in configuration mode') 4275 if inputs: 4276 os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) 4277 file_encoding = kwargs.setdefault('encoding', 'utf-8') 4278 missing_variables, confdata_useless = \ 4279 mesonlib.do_conf_file(inputs_abs[0], ofile_abs, conf.held_object, 4280 fmt, file_encoding) 4281 if missing_variables: 4282 var_list = ", ".join(map(repr, sorted(missing_variables))) 4283 mlog.warning( 4284 "The variable(s) %s in the input file '%s' are not " 4285 "present in the given configuration data." % ( 4286 var_list, inputs[0]), location=node) 4287 if confdata_useless: 4288 ifbase = os.path.basename(inputs_abs[0]) 4289 mlog.warning('Got an empty configuration_data() object and found no ' 4290 'substitutions in the input file {!r}. If you want to ' 4291 'copy a file to the build dir, use the \'copy:\' keyword ' 4292 'argument added in 0.47.0'.format(ifbase), location=node) 4293 else: 4294 mesonlib.dump_conf_header(ofile_abs, conf.held_object, output_format) 4295 conf.mark_used() 4296 elif 'command' in kwargs: 4297 if len(inputs) > 1: 4298 FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject) 4299 # We use absolute paths for input and output here because the cwd 4300 # that the command is run from is 'unspecified', so it could change. 4301 # Currently it's builddir/subdir for in_builddir else srcdir/subdir. 4302 values = mesonlib.get_filenames_templates_dict(inputs_abs, [ofile_abs]) 4303 if depfile: 4304 depfile = os.path.join(self.environment.get_scratch_dir(), depfile) 4305 values['@DEPFILE@'] = depfile 4306 # Substitute @INPUT@, @OUTPUT@, etc here. 4307 cmd = mesonlib.substitute_values(kwargs['command'], values) 4308 mlog.log('Configuring', mlog.bold(output), 'with command') 4309 res = self.run_command_impl(node, cmd, {}, True) 4310 if res.returncode != 0: 4311 raise InterpreterException('Running configure command failed.\n%s\n%s' % 4312 (res.stdout, res.stderr)) 4313 if 'capture' in kwargs and kwargs['capture']: 4314 dst_tmp = ofile_abs + '~' 4315 file_encoding = kwargs.setdefault('encoding', 'utf-8') 4316 with open(dst_tmp, 'w', encoding=file_encoding) as f: 4317 f.writelines(res.stdout) 4318 if inputs_abs: 4319 shutil.copymode(inputs_abs[0], dst_tmp) 4320 mesonlib.replace_if_different(ofile_abs, dst_tmp) 4321 if depfile: 4322 mlog.log('Reading depfile:', mlog.bold(depfile)) 4323 with open(depfile, 'r') as f: 4324 df = DepFile(f.readlines()) 4325 deps = df.get_all_dependencies(ofile_fname) 4326 for dep in deps: 4327 self.add_build_def_file(dep) 4328 4329 elif 'copy' in kwargs: 4330 if len(inputs_abs) != 1: 4331 raise InterpreterException('Exactly one input file must be given in copy mode') 4332 os.makedirs(os.path.join(self.environment.build_dir, self.subdir), exist_ok=True) 4333 shutil.copyfile(inputs_abs[0], ofile_abs) 4334 shutil.copystat(inputs_abs[0], ofile_abs) 4335 else: 4336 # Not reachable 4337 raise AssertionError 4338 # Install file if requested, we check for the empty string 4339 # for backwards compatibility. That was the behaviour before 4340 # 0.45.0 so preserve it. 4341 idir = kwargs.get('install_dir', '') 4342 if idir is False: 4343 idir = '' 4344 mlog.deprecation('Please use the new `install:` kwarg instead of passing ' 4345 '`false` to `install_dir:`', location=node) 4346 if not isinstance(idir, str): 4347 if isinstance(idir, list) and len(idir) == 0: 4348 mlog.deprecation('install_dir: kwarg must be a string and not an empty array. ' 4349 'Please use the install: kwarg to enable or disable installation. ' 4350 'This will be a hard error in the next release.') 4351 else: 4352 raise InterpreterException('"install_dir" must be a string') 4353 install = kwargs.get('install', idir != '') 4354 if not isinstance(install, bool): 4355 raise InterpreterException('"install" must be a boolean') 4356 if install: 4357 if not idir: 4358 raise InterpreterException('"install_dir" must be specified ' 4359 'when "install" in a configure_file ' 4360 'is true') 4361 cfile = mesonlib.File.from_built_file(ofile_path, ofile_fname) 4362 install_mode = self._get_kwarg_install_mode(kwargs) 4363 self.build.data.append(build.Data([cfile], idir, install_mode)) 4364 return mesonlib.File.from_built_file(self.subdir, output) 4365 4366 def extract_incdirs(self, kwargs): 4367 prospectives = unholder(extract_as_list(kwargs, 'include_directories')) 4368 result = [] 4369 for p in prospectives: 4370 if isinstance(p, build.IncludeDirs): 4371 result.append(p) 4372 elif isinstance(p, str): 4373 result.append(self.build_incdir_object([p]).held_object) 4374 else: 4375 raise InterpreterException('Include directory objects can only be created from strings or include directories.') 4376 return result 4377 4378 @permittedKwargs(permitted_kwargs['include_directories']) 4379 @stringArgs 4380 def func_include_directories(self, node, args, kwargs): 4381 return self.build_incdir_object(args, kwargs.get('is_system', False)) 4382 4383 def build_incdir_object(self, incdir_strings, is_system=False): 4384 if not isinstance(is_system, bool): 4385 raise InvalidArguments('Is_system must be boolean.') 4386 src_root = self.environment.get_source_dir() 4387 build_root = self.environment.get_build_dir() 4388 absbase_src = os.path.join(src_root, self.subdir) 4389 absbase_build = os.path.join(build_root, self.subdir) 4390 4391 for a in incdir_strings: 4392 if a.startswith(src_root): 4393 raise InvalidArguments('Tried to form an absolute path to a source dir. ' 4394 'You should not do that but use relative paths instead.' 4395 ''' 4396 4397To get include path to any directory relative to the current dir do 4398 4399incdir = include_directories(dirname) 4400 4401After this incdir will contain both the current source dir as well as the 4402corresponding build dir. It can then be used in any subdirectory and 4403Meson will take care of all the busywork to make paths work. 4404 4405Dirname can even be '.' to mark the current directory. Though you should 4406remember that the current source and build directories are always 4407put in the include directories by default so you only need to do 4408include_directories('.') if you intend to use the result in a 4409different subdirectory. 4410''') 4411 absdir_src = os.path.join(absbase_src, a) 4412 absdir_build = os.path.join(absbase_build, a) 4413 if not os.path.isdir(absdir_src) and not os.path.isdir(absdir_build): 4414 raise InvalidArguments('Include dir %s does not exist.' % a) 4415 i = IncludeDirsHolder(build.IncludeDirs(self.subdir, incdir_strings, is_system)) 4416 return i 4417 4418 @permittedKwargs(permitted_kwargs['add_test_setup']) 4419 @stringArgs 4420 def func_add_test_setup(self, node, args, kwargs): 4421 if len(args) != 1: 4422 raise InterpreterException('Add_test_setup needs one argument for the setup name.') 4423 setup_name = args[0] 4424 if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None: 4425 raise InterpreterException('Setup name may only contain alphanumeric characters.') 4426 if ":" not in setup_name: 4427 setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name 4428 try: 4429 inp = unholder(extract_as_list(kwargs, 'exe_wrapper')) 4430 exe_wrapper = [] 4431 for i in inp: 4432 if isinstance(i, str): 4433 exe_wrapper.append(i) 4434 elif isinstance(i, dependencies.ExternalProgram): 4435 if not i.found(): 4436 raise InterpreterException('Tried to use non-found executable.') 4437 exe_wrapper += i.get_command() 4438 else: 4439 raise InterpreterException('Exe wrapper can only contain strings or external binaries.') 4440 except KeyError: 4441 exe_wrapper = None 4442 gdb = kwargs.get('gdb', False) 4443 if not isinstance(gdb, bool): 4444 raise InterpreterException('Gdb option must be a boolean') 4445 timeout_multiplier = kwargs.get('timeout_multiplier', 1) 4446 if not isinstance(timeout_multiplier, int): 4447 raise InterpreterException('Timeout multiplier must be a number.') 4448 is_default = kwargs.get('is_default', False) 4449 if not isinstance(is_default, bool): 4450 raise InterpreterException('is_default option must be a boolean') 4451 if is_default: 4452 if self.build.test_setup_default_name is not None: 4453 raise InterpreterException('\'%s\' is already set as default. ' 4454 'is_default can be set to true only once' % self.build.test_setup_default_name) 4455 self.build.test_setup_default_name = setup_name 4456 env = self.unpack_env_kwarg(kwargs) 4457 self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env) 4458 4459 @permittedKwargs(permitted_kwargs['add_global_arguments']) 4460 @stringArgs 4461 def func_add_global_arguments(self, node, args, kwargs): 4462 for_machine = self.machine_from_native_kwarg(kwargs) 4463 self.add_global_arguments(node, self.build.global_args[for_machine], args, kwargs) 4464 4465 @permittedKwargs(permitted_kwargs['add_global_link_arguments']) 4466 @stringArgs 4467 def func_add_global_link_arguments(self, node, args, kwargs): 4468 for_machine = self.machine_from_native_kwarg(kwargs) 4469 self.add_global_arguments(node, self.build.global_link_args[for_machine], args, kwargs) 4470 4471 @permittedKwargs(permitted_kwargs['add_project_arguments']) 4472 @stringArgs 4473 def func_add_project_arguments(self, node, args, kwargs): 4474 for_machine = self.machine_from_native_kwarg(kwargs) 4475 self.add_project_arguments(node, self.build.projects_args[for_machine], args, kwargs) 4476 4477 @permittedKwargs(permitted_kwargs['add_project_link_arguments']) 4478 @stringArgs 4479 def func_add_project_link_arguments(self, node, args, kwargs): 4480 for_machine = self.machine_from_native_kwarg(kwargs) 4481 self.add_project_arguments(node, self.build.projects_link_args[for_machine], args, kwargs) 4482 4483 def warn_about_builtin_args(self, args): 4484 warnargs = ('/W1', '/W2', '/W3', '/W4', '/Wall', '-Wall', '-Wextra', '-Wpedantic') 4485 optargs = ('-O0', '-O2', '-O3', '-Os', '/O1', '/O2', '/Os') 4486 for arg in args: 4487 if arg in warnargs: 4488 mlog.warning('Consider using the built-in warning_level option instead of using "{}".'.format(arg), 4489 location=self.current_node) 4490 elif arg in optargs: 4491 mlog.warning('Consider using the built-in optimization level instead of using "{}".'.format(arg), 4492 location=self.current_node) 4493 elif arg == '-g': 4494 mlog.warning('Consider using the built-in debug option instead of using "{}".'.format(arg), 4495 location=self.current_node) 4496 elif arg == '-pipe': 4497 mlog.warning("You don't need to add -pipe, Meson will use it automatically when it is available.", 4498 location=self.current_node) 4499 elif arg.startswith('-fsanitize'): 4500 mlog.warning('Consider using the built-in option for sanitizers instead of using "{}".'.format(arg), 4501 location=self.current_node) 4502 elif arg.startswith('-std=') or arg.startswith('/std:'): 4503 mlog.warning('Consider using the built-in option for language standard version instead of using "{}".'.format(arg), 4504 location=self.current_node) 4505 4506 def add_global_arguments(self, node, argsdict, args, kwargs): 4507 if self.is_subproject(): 4508 msg = 'Function \'{}\' cannot be used in subprojects because ' \ 4509 'there is no way to make that reliable.\nPlease only call ' \ 4510 'this if is_subproject() returns false. Alternatively, ' \ 4511 'define a variable that\ncontains your language-specific ' \ 4512 'arguments and add it to the appropriate *_args kwarg ' \ 4513 'in each target.'.format(node.func_name) 4514 raise InvalidCode(msg) 4515 frozen = self.project_args_frozen or self.global_args_frozen 4516 self.add_arguments(node, argsdict, frozen, args, kwargs) 4517 4518 def add_project_arguments(self, node, argsdict, args, kwargs): 4519 if self.subproject not in argsdict: 4520 argsdict[self.subproject] = {} 4521 self.add_arguments(node, argsdict[self.subproject], 4522 self.project_args_frozen, args, kwargs) 4523 4524 def add_arguments(self, node, argsdict, args_frozen, args, kwargs): 4525 if args_frozen: 4526 msg = 'Tried to use \'{}\' after a build target has been declared.\n' \ 4527 'This is not permitted. Please declare all ' \ 4528 'arguments before your targets.'.format(node.func_name) 4529 raise InvalidCode(msg) 4530 4531 if 'language' not in kwargs: 4532 raise InvalidCode('Missing language definition in {}'.format(node.func_name)) 4533 4534 self.warn_about_builtin_args(args) 4535 4536 for lang in mesonlib.stringlistify(kwargs['language']): 4537 lang = lang.lower() 4538 argsdict[lang] = argsdict.get(lang, []) + args 4539 4540 @noKwargs 4541 @noArgsFlattening 4542 def func_environment(self, node, args, kwargs): 4543 if len(args) > 1: 4544 raise InterpreterException('environment takes only one optional positional arguments') 4545 elif len(args) == 1: 4546 FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject) 4547 initial_values = args[0] 4548 if not isinstance(initial_values, dict) and not isinstance(initial_values, list): 4549 raise InterpreterException('environment first argument must be a dictionary or a list') 4550 else: 4551 initial_values = {} 4552 return EnvironmentVariablesHolder(initial_values) 4553 4554 @stringArgs 4555 @noKwargs 4556 def func_join_paths(self, node, args, kwargs): 4557 return self.join_path_strings(args) 4558 4559 def run(self): 4560 super().run() 4561 mlog.log('Build targets in project:', mlog.bold(str(len(self.build.targets)))) 4562 FeatureNew.report(self.subproject) 4563 FeatureDeprecated.report(self.subproject) 4564 if not self.is_subproject(): 4565 self.print_extra_warnings() 4566 if self.subproject == '': 4567 self._print_summary() 4568 4569 def print_extra_warnings(self): 4570 # TODO cross compilation 4571 for c in self.coredata.compilers.host.values(): 4572 if c.get_id() == 'clang': 4573 self.check_clang_asan_lundef() 4574 break 4575 4576 def check_clang_asan_lundef(self): 4577 if 'b_lundef' not in self.coredata.base_options: 4578 return 4579 if 'b_sanitize' not in self.coredata.base_options: 4580 return 4581 if (self.coredata.base_options['b_lundef'].value and 4582 self.coredata.base_options['b_sanitize'].value != 'none'): 4583 mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef. 4584This will probably not work. 4585Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value), 4586 location=self.current_node) 4587 4588 def evaluate_subproject_info(self, path_from_source_root, subproject_dirname): 4589 depth = 0 4590 subproj_name = '' 4591 segs = PurePath(path_from_source_root).parts 4592 segs_spd = PurePath(subproject_dirname).parts 4593 while segs and segs[0] == segs_spd[0]: 4594 if len(segs_spd) == 1: 4595 subproj_name = segs[1] 4596 segs = segs[2:] 4597 depth += 1 4598 else: 4599 segs_spd = segs_spd[1:] 4600 segs = segs[1:] 4601 return (depth, subproj_name) 4602 4603 # Check that the indicated file is within the same subproject 4604 # as we currently are. This is to stop people doing 4605 # nasty things like: 4606 # 4607 # f = files('../../master_src/file.c') 4608 # 4609 # Note that this is validated only when the file 4610 # object is generated. The result can be used in a different 4611 # subproject than it is defined in (due to e.g. a 4612 # declare_dependency). 4613 def validate_within_subproject(self, subdir, fname): 4614 norm = os.path.normpath(os.path.join(subdir, fname)) 4615 if os.path.isabs(norm): 4616 if not norm.startswith(self.environment.source_dir): 4617 # Grabbing files outside the source tree is ok. 4618 # This is for vendor stuff like: 4619 # 4620 # /opt/vendorsdk/src/file_with_license_restrictions.c 4621 return 4622 norm = os.path.relpath(norm, self.environment.source_dir) 4623 assert(not os.path.isabs(norm)) 4624 (num_sps, sproj_name) = self.evaluate_subproject_info(norm, self.subproject_dir) 4625 plain_filename = os.path.basename(norm) 4626 if num_sps == 0: 4627 if not self.is_subproject(): 4628 return 4629 raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename) 4630 if num_sps > 1: 4631 raise InterpreterException('Sandbox violation: Tried to grab file %s from a nested subproject.' % plain_filename) 4632 if sproj_name != self.subproject_directory_name: 4633 raise InterpreterException('Sandbox violation: Tried to grab file %s from a different subproject.' % plain_filename) 4634 4635 def source_strings_to_files(self, sources): 4636 results = [] 4637 mesonlib.check_direntry_issues(sources) 4638 if not isinstance(sources, list): 4639 sources = [sources] 4640 for s in sources: 4641 if isinstance(s, (mesonlib.File, GeneratedListHolder, 4642 TargetHolder, CustomTargetIndexHolder, 4643 GeneratedObjectsHolder)): 4644 pass 4645 elif isinstance(s, str): 4646 self.validate_within_subproject(self.subdir, s) 4647 s = mesonlib.File.from_source_file(self.environment.source_dir, self.subdir, s) 4648 else: 4649 raise InterpreterException('Source item is {!r} instead of ' 4650 'string or File-type object'.format(s)) 4651 results.append(s) 4652 return results 4653 4654 def add_target(self, name, tobj): 4655 if name == '': 4656 raise InterpreterException('Target name must not be empty.') 4657 if name.strip() == '': 4658 raise InterpreterException('Target name must not consist only of whitespace.') 4659 if name.startswith('meson-'): 4660 raise InvalidArguments("Target names starting with 'meson-' are reserved " 4661 "for Meson's internal use. Please rename.") 4662 if name in coredata.forbidden_target_names: 4663 raise InvalidArguments("Target name '%s' is reserved for Meson's " 4664 "internal use. Please rename." % name) 4665 # To permit an executable and a shared library to have the 4666 # same name, such as "foo.exe" and "libfoo.a". 4667 idname = tobj.get_id() 4668 if idname in self.build.targets: 4669 raise InvalidCode('Tried to create target "%s", but a target of that name already exists.' % name) 4670 self.build.targets[idname] = tobj 4671 if idname not in self.coredata.target_guids: 4672 self.coredata.target_guids[idname] = str(uuid.uuid4()).upper() 4673 4674 @FeatureNew('both_libraries', '0.46.0') 4675 def build_both_libraries(self, node, args, kwargs): 4676 shared_holder = self.build_target(node, args, kwargs, SharedLibraryHolder) 4677 4678 # Check if user forces non-PIC static library. 4679 pic = True 4680 if 'pic' in kwargs: 4681 pic = kwargs['pic'] 4682 elif 'b_staticpic' in self.environment.coredata.base_options: 4683 pic = self.environment.coredata.base_options['b_staticpic'].value 4684 4685 if pic: 4686 # Exclude sources from args and kwargs to avoid building them twice 4687 static_args = [args[0]] 4688 static_kwargs = kwargs.copy() 4689 static_kwargs['sources'] = [] 4690 static_kwargs['objects'] = shared_holder.held_object.extract_all_objects() 4691 else: 4692 static_args = args 4693 static_kwargs = kwargs 4694 4695 static_holder = self.build_target(node, static_args, static_kwargs, StaticLibraryHolder) 4696 4697 return BothLibrariesHolder(shared_holder, static_holder, self) 4698 4699 def build_library(self, node, args, kwargs): 4700 default_library = self.coredata.get_builtin_option('default_library', self.subproject) 4701 if default_library == 'shared': 4702 return self.build_target(node, args, kwargs, SharedLibraryHolder) 4703 elif default_library == 'static': 4704 return self.build_target(node, args, kwargs, StaticLibraryHolder) 4705 elif default_library == 'both': 4706 return self.build_both_libraries(node, args, kwargs) 4707 else: 4708 raise InterpreterException('Unknown default_library value: %s.', default_library) 4709 4710 def build_target(self, node, args, kwargs, targetholder): 4711 @FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories']) 4712 @FeatureNewKwargs('build target', '0.41.0', ['rust_args']) 4713 @FeatureNewKwargs('build target', '0.40.0', ['build_by_default']) 4714 @FeatureNewKwargs('build target', '0.48.0', ['gnu_symbol_visibility']) 4715 def build_target_decorator_caller(self, node, args, kwargs): 4716 return True 4717 4718 build_target_decorator_caller(self, node, args, kwargs) 4719 4720 if not args: 4721 raise InterpreterException('Target does not have a name.') 4722 name, *sources = args 4723 for_machine = self.machine_from_native_kwarg(kwargs) 4724 if 'sources' in kwargs: 4725 sources += listify(kwargs['sources']) 4726 sources = self.source_strings_to_files(sources) 4727 objs = extract_as_list(kwargs, 'objects') 4728 kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') 4729 kwargs['install_mode'] = self._get_kwarg_install_mode(kwargs) 4730 if 'extra_files' in kwargs: 4731 ef = extract_as_list(kwargs, 'extra_files') 4732 kwargs['extra_files'] = self.source_strings_to_files(ef) 4733 self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) 4734 if targetholder == ExecutableHolder: 4735 targetclass = build.Executable 4736 elif targetholder == SharedLibraryHolder: 4737 targetclass = build.SharedLibrary 4738 elif targetholder == SharedModuleHolder: 4739 targetclass = build.SharedModule 4740 elif targetholder == StaticLibraryHolder: 4741 targetclass = build.StaticLibrary 4742 elif targetholder == JarHolder: 4743 targetclass = build.Jar 4744 else: 4745 mlog.debug('Unknown target type:', str(targetholder)) 4746 raise RuntimeError('Unreachable code') 4747 self.kwarg_strings_to_includedirs(kwargs) 4748 4749 # Filter out kwargs from other target types. For example 'soversion' 4750 # passed to library() when default_library == 'static'. 4751 kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs} 4752 4753 kwargs['include_directories'] = self.extract_incdirs(kwargs) 4754 target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs) 4755 target.project_version = self.project_version 4756 4757 if not self.environment.machines.matches_build_machine(for_machine): 4758 self.add_cross_stdlib_info(target) 4759 l = targetholder(target, self) 4760 self.add_target(name, l.held_object) 4761 self.project_args_frozen = True 4762 return l 4763 4764 def kwarg_strings_to_includedirs(self, kwargs): 4765 if 'd_import_dirs' in kwargs: 4766 items = mesonlib.extract_as_list(kwargs, 'd_import_dirs') 4767 cleaned_items = [] 4768 for i in items: 4769 if isinstance(i, str): 4770 # BW compatibility. This was permitted so we must support it 4771 # for a few releases so people can transition to "correct" 4772 # path declarations. 4773 if os.path.normpath(i).startswith(self.environment.get_source_dir()): 4774 mlog.warning('''Building a path to the source dir is not supported. Use a relative path instead. 4775This will become a hard error in the future.''', location=self.current_node) 4776 i = os.path.relpath(i, os.path.join(self.environment.get_source_dir(), self.subdir)) 4777 i = self.build_incdir_object([i]) 4778 cleaned_items.append(i) 4779 kwargs['d_import_dirs'] = cleaned_items 4780 4781 def get_used_languages(self, target): 4782 result = {} 4783 for i in target.sources: 4784 # TODO other platforms 4785 for lang, c in self.coredata.compilers.host.items(): 4786 if c.can_compile(i): 4787 result[lang] = True 4788 break 4789 return result 4790 4791 def add_cross_stdlib_info(self, target): 4792 if target.for_machine != MachineChoice.HOST: 4793 return 4794 for l in self.get_used_languages(target): 4795 props = self.environment.properties.host 4796 if props.has_stdlib(l) \ 4797 and self.subproject != props.get_stdlib(l)[0]: 4798 target.add_deps(self.build.stdlibs.host[l]) 4799 4800 def check_sources_exist(self, subdir, sources): 4801 for s in sources: 4802 if not isinstance(s, str): 4803 continue # This means a generated source and they always exist. 4804 fname = os.path.join(subdir, s) 4805 if not os.path.isfile(fname): 4806 raise InterpreterException('Tried to add non-existing source file %s.' % s) 4807 4808 # Only permit object extraction from the same subproject 4809 def validate_extraction(self, buildtarget: InterpreterObject) -> None: 4810 if not self.subdir.startswith(self.subproject_dir): 4811 if buildtarget.subdir.startswith(self.subproject_dir): 4812 raise InterpreterException('Tried to extract objects from a subproject target.') 4813 else: 4814 if not buildtarget.subdir.startswith(self.subproject_dir): 4815 raise InterpreterException('Tried to extract objects from the main project from a subproject.') 4816 if self.subdir.split('/')[1] != buildtarget.subdir.split('/')[1]: 4817 raise InterpreterException('Tried to extract objects from a different subproject.') 4818 4819 def is_subproject(self): 4820 return self.subproject != '' 4821 4822 @noKwargs 4823 @noArgsFlattening 4824 def func_set_variable(self, node, args, kwargs): 4825 if len(args) != 2: 4826 raise InvalidCode('Set_variable takes two arguments.') 4827 varname, value = args 4828 self.set_variable(varname, value) 4829 4830 @noKwargs 4831 @noArgsFlattening 4832 def func_get_variable(self, node, args, kwargs): 4833 if len(args) < 1 or len(args) > 2: 4834 raise InvalidCode('Get_variable takes one or two arguments.') 4835 varname = args[0] 4836 if isinstance(varname, Disabler): 4837 return varname 4838 if not isinstance(varname, str): 4839 raise InterpreterException('First argument must be a string.') 4840 try: 4841 return self.variables[varname] 4842 except KeyError: 4843 pass 4844 if len(args) == 2: 4845 return args[1] 4846 raise InterpreterException('Tried to get unknown variable "%s".' % varname) 4847 4848 @stringArgs 4849 @noKwargs 4850 def func_is_variable(self, node, args, kwargs): 4851 if len(args) != 1: 4852 raise InvalidCode('Is_variable takes two arguments.') 4853 varname = args[0] 4854 return varname in self.variables 4855 4856 @staticmethod 4857 def machine_from_native_kwarg(kwargs: T.Dict[str, T.Any]) -> MachineChoice: 4858 native = kwargs.get('native', False) 4859 if not isinstance(native, bool): 4860 raise InvalidArguments('Argument to "native" must be a boolean.') 4861 return MachineChoice.BUILD if native else MachineChoice.HOST 4862 4863 @FeatureNew('is_disabler', '0.52.0') 4864 @noKwargs 4865 def func_is_disabler(self, node, args, kwargs): 4866 if len(args) != 1: 4867 raise InvalidCode('Is_disabler takes one argument.') 4868 varname = args[0] 4869 return isinstance(varname, Disabler) 4870