1# Copyright 2012-2017 The Meson development team 2 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6 7# http://www.apache.org/licenses/LICENSE-2.0 8 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15from collections import OrderedDict 16from functools import lru_cache 17import copy 18import hashlib 19import itertools, pathlib 20import os 21import pickle 22import re 23import textwrap 24import typing as T 25 26from . import environment 27from . import dependencies 28from . import mlog 29from . import programs 30from .mesonlib import ( 31 HoldableObject, SecondLevelHolder, 32 File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, 33 extract_as_list, typeslistify, stringlistify, classify_unity_sources, 34 get_filenames_templates_dict, substitute_values, has_path_sep, 35 OptionKey, PerMachineDefaultable, 36 MesonBugException, FileOrString, 37) 38from .compilers import ( 39 Compiler, is_object, clink_langs, sort_clink, lang_suffixes, 40 is_known_suffix, detect_static_linker 41) 42from .linkers import StaticLinker 43from .interpreterbase import FeatureNew 44 45if T.TYPE_CHECKING: 46 from ._typing import ImmutableListProtocol, ImmutableSetProtocol 47 from .interpreter.interpreter import Test, SourceOutputs, Interpreter 48 from .mesonlib import FileMode, FileOrString 49 from .modules import ModuleState 50 from .backend.backends import Backend 51 52pch_kwargs = {'c_pch', 'cpp_pch'} 53 54lang_arg_kwargs = { 55 'c_args', 56 'cpp_args', 57 'cuda_args', 58 'd_args', 59 'd_import_dirs', 60 'd_unittest', 61 'd_module_versions', 62 'd_debug', 63 'fortran_args', 64 'java_args', 65 'objc_args', 66 'objcpp_args', 67 'rust_args', 68 'vala_args', 69 'cs_args', 70 'cython_args', 71} 72 73vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} 74rust_kwargs = {'rust_crate_type'} 75cs_kwargs = {'resources', 'cs_args'} 76 77buildtarget_kwargs = { 78 'build_by_default', 79 'build_rpath', 80 'dependencies', 81 'extra_files', 82 'gui_app', 83 'link_with', 84 'link_whole', 85 'link_args', 86 'link_depends', 87 'implicit_include_directories', 88 'include_directories', 89 'install', 90 'install_rpath', 91 'install_dir', 92 'install_mode', 93 'name_prefix', 94 'name_suffix', 95 'native', 96 'objects', 97 'override_options', 98 'sources', 99 'gnu_symbol_visibility', 100 'link_language', 101 'win_subsystem', 102} 103 104known_build_target_kwargs = ( 105 buildtarget_kwargs | 106 lang_arg_kwargs | 107 pch_kwargs | 108 vala_kwargs | 109 rust_kwargs | 110 cs_kwargs) 111 112known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'} 113known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} 114known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'} 115known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink'} 116known_jar_kwargs = known_exe_kwargs | {'main_class'} 117 118@lru_cache(maxsize=None) 119def get_target_macos_dylib_install_name(ld) -> str: 120 name = ['@rpath/', ld.prefix, ld.name] 121 if ld.soversion is not None: 122 name.append('.' + ld.soversion) 123 name.append('.dylib') 124 return ''.join(name) 125 126class InvalidArguments(MesonException): 127 pass 128 129class DependencyOverride(HoldableObject): 130 def __init__(self, dep, node, explicit=True): 131 self.dep = dep 132 self.node = node 133 self.explicit = explicit 134 135class Headers(HoldableObject): 136 137 def __init__(self, sources: T.List[File], install_subdir: T.Optional[str], 138 install_dir: T.Optional[str], install_mode: 'FileMode', 139 subproject: str): 140 self.sources = sources 141 self.install_subdir = install_subdir 142 self.custom_install_dir = install_dir 143 self.custom_install_mode = install_mode 144 self.subproject = subproject 145 146 # TODO: we really don't need any of these methods, but they're preserved to 147 # keep APIs relying on them working. 148 149 def set_install_subdir(self, subdir: str) -> None: 150 self.install_subdir = subdir 151 152 def get_install_subdir(self) -> T.Optional[str]: 153 return self.install_subdir 154 155 def get_sources(self) -> T.List[File]: 156 return self.sources 157 158 def get_custom_install_dir(self) -> T.Optional[str]: 159 return self.custom_install_dir 160 161 def get_custom_install_mode(self) -> 'FileMode': 162 return self.custom_install_mode 163 164 165class Man(HoldableObject): 166 167 def __init__(self, sources: T.List[File], install_dir: T.Optional[str], 168 install_mode: 'FileMode', subproject: str, 169 locale: T.Optional[str]): 170 self.sources = sources 171 self.custom_install_dir = install_dir 172 self.custom_install_mode = install_mode 173 self.subproject = subproject 174 self.locale = locale 175 176 def get_custom_install_dir(self) -> T.Optional[str]: 177 return self.custom_install_dir 178 179 def get_custom_install_mode(self) -> 'FileMode': 180 return self.custom_install_mode 181 182 def get_sources(self) -> T.List['File']: 183 return self.sources 184 185 186class InstallDir(HoldableObject): 187 188 def __init__(self, src_subdir: str, inst_subdir: str, install_dir: str, 189 install_mode: 'FileMode', 190 exclude: T.Tuple[T.Set[str], T.Set[str]], 191 strip_directory: bool, subproject: str, 192 from_source_dir: bool = True): 193 self.source_subdir = src_subdir 194 self.installable_subdir = inst_subdir 195 self.install_dir = install_dir 196 self.install_mode = install_mode 197 self.exclude = exclude 198 self.strip_directory = strip_directory 199 self.from_source_dir = from_source_dir 200 self.subproject = subproject 201 202 203class Build: 204 """A class that holds the status of one build including 205 all dependencies and so on. 206 """ 207 208 def __init__(self, environment: environment.Environment): 209 self.project_name = 'name of master project' 210 self.project_version = None 211 self.environment = environment 212 self.projects = {} 213 self.targets: T.MutableMapping[str, 'Target'] = OrderedDict() 214 self.run_target_names: T.Set[T.Tuple[str, str]] = set() 215 self.global_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) 216 self.global_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) 217 self.projects_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {}) 218 self.projects_link_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {}) 219 self.tests: T.List['Test'] = [] 220 self.benchmarks: T.List['Test'] = [] 221 self.headers: T.List[Headers] = [] 222 self.man: T.List[Man] = [] 223 self.data: T.List[Data] = [] 224 self.static_linker: PerMachine[StaticLinker] = PerMachine(None, None) 225 self.subprojects = {} 226 self.subproject_dir = '' 227 self.install_scripts = [] 228 self.postconf_scripts = [] 229 self.dist_scripts = [] 230 self.install_dirs: T.List[InstallDir] = [] 231 self.dep_manifest_name = None 232 self.dep_manifest = {} 233 self.stdlibs = PerMachine({}, {}) 234 self.test_setups: T.Dict[str, TestSetup] = {} 235 self.test_setup_default_name = None 236 self.find_overrides = {} 237 self.searched_programs = set() # The list of all programs that have been searched for. 238 239 # If we are doing a cross build we need two caches, if we're doing a 240 # build == host compilation the both caches should point to the same place. 241 self.dependency_overrides: PerMachine[T.Dict[T.Tuple, DependencyOverride]] = PerMachineDefaultable.default( 242 environment.is_cross_build(), {}, {}) 243 self.devenv: T.List[EnvironmentVariables] = [] 244 245 def get_build_targets(self): 246 build_targets = OrderedDict() 247 for name, t in self.targets.items(): 248 if isinstance(t, BuildTarget): 249 build_targets[name] = t 250 return build_targets 251 252 def get_custom_targets(self): 253 custom_targets = OrderedDict() 254 for name, t in self.targets.items(): 255 if isinstance(t, CustomTarget): 256 custom_targets[name] = t 257 return custom_targets 258 259 def copy(self): 260 other = Build(self.environment) 261 for k, v in self.__dict__.items(): 262 if isinstance(v, (list, dict, set, OrderedDict)): 263 other.__dict__[k] = v.copy() 264 else: 265 other.__dict__[k] = v 266 return other 267 268 def merge(self, other): 269 for k, v in other.__dict__.items(): 270 self.__dict__[k] = v 271 272 def ensure_static_linker(self, compiler): 273 if self.static_linker[compiler.for_machine] is None and compiler.needs_static_linker(): 274 self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler) 275 276 def get_project(self): 277 return self.projects[''] 278 279 def get_subproject_dir(self): 280 return self.subproject_dir 281 282 def get_targets(self) -> T.Dict[str, 'Target']: 283 return self.targets 284 285 def get_tests(self) -> T.List['Test']: 286 return self.tests 287 288 def get_benchmarks(self) -> T.List['Test']: 289 return self.benchmarks 290 291 def get_headers(self): 292 return self.headers 293 294 def get_man(self): 295 return self.man 296 297 def get_data(self): 298 return self.data 299 300 def get_install_subdirs(self): 301 return self.install_dirs 302 303 def get_global_args(self, compiler: 'Compiler', for_machine: 'MachineChoice') -> T.List[str]: 304 d = self.global_args[for_machine] 305 return d.get(compiler.get_language(), []) 306 307 def get_project_args(self, compiler: 'Compiler', project: str, for_machine: 'MachineChoice') -> T.List[str]: 308 d = self.projects_args[for_machine] 309 args = d.get(project) 310 if not args: 311 return [] 312 return args.get(compiler.get_language(), []) 313 314 def get_global_link_args(self, compiler: 'Compiler', for_machine: 'MachineChoice') -> T.List[str]: 315 d = self.global_link_args[for_machine] 316 return d.get(compiler.get_language(), []) 317 318 def get_project_link_args(self, compiler: 'Compiler', project: str, for_machine: 'MachineChoice') -> T.List[str]: 319 d = self.projects_link_args[for_machine] 320 321 link_args = d.get(project) 322 if not link_args: 323 return [] 324 325 return link_args.get(compiler.get_language(), []) 326 327class IncludeDirs(HoldableObject): 328 329 """Internal representation of an include_directories call.""" 330 331 def __init__(self, curdir: str, dirs: T.List[str], is_system: bool, extra_build_dirs: T.Optional[T.List[str]] = None): 332 self.curdir = curdir 333 self.incdirs = dirs 334 self.is_system = is_system 335 336 # Interpreter has validated that all given directories 337 # actually exist. 338 self.extra_build_dirs: T.List[str] = extra_build_dirs or [] 339 340 def __repr__(self) -> str: 341 r = '<{} {}/{}>' 342 return r.format(self.__class__.__name__, self.curdir, self.incdirs) 343 344 def get_curdir(self) -> str: 345 return self.curdir 346 347 def get_incdirs(self) -> T.List[str]: 348 return self.incdirs 349 350 def get_extra_build_dirs(self) -> T.List[str]: 351 return self.extra_build_dirs 352 353 def to_string_list(self, sourcedir: str) -> T.List[str]: 354 """Convert IncludeDirs object to a list of strings.""" 355 strlist: T.List[str] = [] 356 for idir in self.incdirs: 357 strlist.append(os.path.join(sourcedir, self.curdir, idir)) 358 return strlist 359 360class ExtractedObjects(HoldableObject): 361 ''' 362 Holds a list of sources for which the objects must be extracted 363 ''' 364 def __init__(self, target, srclist=None, genlist=None, objlist=None, recursive=True): 365 self.target = target 366 self.recursive = recursive 367 self.srclist = srclist if srclist is not None else [] 368 self.genlist = genlist if genlist is not None else [] 369 self.objlist = objlist if objlist is not None else [] 370 if self.target.is_unity: 371 self.check_unity_compatible() 372 373 def __repr__(self): 374 r = '<{0} {1!r}: {2}>' 375 return r.format(self.__class__.__name__, self.target.name, self.srclist) 376 377 @staticmethod 378 def get_sources(sources, generated_sources): 379 # Merge sources and generated sources 380 sources = list(sources) 381 for gensrc in generated_sources: 382 for s in gensrc.get_outputs(): 383 # We cannot know the path where this source will be generated, 384 # but all we need here is the file extension to determine the 385 # compiler. 386 sources.append(s) 387 388 # Filter out headers and all non-source files 389 return [s for s in sources if environment.is_source(s) and not environment.is_header(s)] 390 391 def classify_all_sources(self, sources, generated_sources): 392 sources = self.get_sources(sources, generated_sources) 393 return classify_unity_sources(self.target.compilers.values(), sources) 394 395 def check_unity_compatible(self): 396 # Figure out if the extracted object list is compatible with a Unity 397 # build. When we're doing a Unified build, we go through the sources, 398 # and create a single source file from each subset of the sources that 399 # can be compiled with a specific compiler. Then we create one object 400 # from each unified source file. So for each compiler we can either 401 # extra all its sources or none. 402 cmpsrcs = self.classify_all_sources(self.target.sources, self.target.generated) 403 extracted_cmpsrcs = self.classify_all_sources(self.srclist, self.genlist) 404 405 for comp, srcs in extracted_cmpsrcs.items(): 406 if set(srcs) != set(cmpsrcs[comp]): 407 raise MesonException('Single object files can not be extracted ' 408 'in Unity builds. You can only extract all ' 409 'the object files for each compiler at once.') 410 411 def get_outputs(self, backend): 412 return [ 413 backend.object_filename_from_source(self.target, source) 414 for source in self.get_sources(self.srclist, self.genlist) 415 ] 416 417class EnvironmentVariables(HoldableObject): 418 def __init__(self) -> None: 419 self.envvars = [] 420 # The set of all env vars we have operations for. Only used for self.has_name() 421 self.varnames = set() 422 423 def __repr__(self): 424 repr_str = "<{0}: {1}>" 425 return repr_str.format(self.__class__.__name__, self.envvars) 426 427 def has_name(self, name: str) -> bool: 428 return name in self.varnames 429 430 def set(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: 431 self.varnames.add(name) 432 self.envvars.append((self._set, name, values, separator)) 433 434 def append(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: 435 self.varnames.add(name) 436 self.envvars.append((self._append, name, values, separator)) 437 438 def prepend(self, name: str, values: T.List[str], separator: str = os.pathsep) -> None: 439 self.varnames.add(name) 440 self.envvars.append((self._prepend, name, values, separator)) 441 442 def _set(self, env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: 443 return separator.join(values) 444 445 def _append(self, env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: 446 curr = env.get(name) 447 return separator.join(values if curr is None else [curr] + values) 448 449 def _prepend(self, env: T.Dict[str, str], name: str, values: T.List[str], separator: str) -> str: 450 curr = env.get(name) 451 return separator.join(values if curr is None else values + [curr]) 452 453 def get_env(self, full_env: T.Dict[str, str]) -> T.Dict[str, str]: 454 env = full_env.copy() 455 for method, name, values, separator in self.envvars: 456 env[name] = method(env, name, values, separator) 457 return env 458 459class Target(HoldableObject): 460 461 # TODO: should Target be an abc.ABCMeta? 462 463 def __init__(self, name: str, subdir: str, subproject: str, build_by_default: bool, for_machine: MachineChoice): 464 if has_path_sep(name): 465 # Fix failing test 53 when this becomes an error. 466 mlog.warning(textwrap.dedent(f'''\ 467 Target "{name}" has a path separator in its name. 468 This is not supported, it can cause unexpected failures and will become 469 a hard error in the future.\ 470 ''')) 471 self.name = name 472 self.subdir = subdir 473 self.subproject = subproject 474 self.build_by_default = build_by_default 475 self.for_machine = for_machine 476 self.install = False 477 self.build_always_stale = False 478 self.option_overrides_base: T.Dict[OptionKey, str] = {} 479 self.option_overrides_compiler: T.Dict[OptionKey, str] = {} 480 self.extra_files = [] # type: T.List[File] 481 if not hasattr(self, 'typename'): 482 raise RuntimeError(f'Target type is not set for target class "{type(self).__name__}". This is a bug') 483 484 def __lt__(self, other: object) -> bool: 485 if not hasattr(other, 'get_id') and not callable(other.get_id): 486 return NotImplemented 487 return self.get_id() < other.get_id() 488 489 def __le__(self, other: object) -> bool: 490 if not hasattr(other, 'get_id') and not callable(other.get_id): 491 return NotImplemented 492 return self.get_id() <= other.get_id() 493 494 def __gt__(self, other: object) -> bool: 495 if not hasattr(other, 'get_id') and not callable(other.get_id): 496 return NotImplemented 497 return self.get_id() > other.get_id() 498 499 def __ge__(self, other: object) -> bool: 500 if not hasattr(other, 'get_id') and not callable(other.get_id): 501 return NotImplemented 502 return self.get_id() >= other.get_id() 503 504 def get_default_install_dir(self, env: environment.Environment) -> str: 505 raise NotImplementedError 506 507 def get_install_dir(self, environment: environment.Environment) -> T.Tuple[T.Any, bool]: 508 # Find the installation directory. 509 default_install_dir = self.get_default_install_dir(environment) 510 outdirs = self.get_custom_install_dir() 511 if outdirs[0] is not None and outdirs[0] != default_install_dir and outdirs[0] is not True: 512 # Either the value is set to a non-default value, or is set to 513 # False (which means we want this specific output out of many 514 # outputs to not be installed). 515 custom_install_dir = True 516 else: 517 custom_install_dir = False 518 outdirs[0] = default_install_dir 519 return outdirs, custom_install_dir 520 521 def get_basename(self) -> str: 522 return self.name 523 524 def get_subdir(self) -> str: 525 return self.subdir 526 527 def get_typename(self) -> str: 528 return self.typename 529 530 @staticmethod 531 def _get_id_hash(target_id): 532 # We don't really need cryptographic security here. 533 # Small-digest hash function with unlikely collision is good enough. 534 h = hashlib.sha256() 535 h.update(target_id.encode(encoding='utf-8', errors='replace')) 536 # This ID should be case-insensitive and should work in Visual Studio, 537 # e.g. it should not start with leading '-'. 538 return h.hexdigest()[:7] 539 540 @staticmethod 541 def construct_id_from_path(subdir: str, name: str, type_suffix: str) -> str: 542 """Construct target ID from subdir, name and type suffix. 543 544 This helper function is made public mostly for tests.""" 545 # This ID must also be a valid file name on all OSs. 546 # It should also avoid shell metacharacters for obvious 547 # reasons. '@' is not used as often as '_' in source code names. 548 # In case of collisions consider using checksums. 549 # FIXME replace with assert when slash in names is prohibited 550 name_part = name.replace('/', '@').replace('\\', '@') 551 assert not has_path_sep(type_suffix) 552 my_id = name_part + type_suffix 553 if subdir: 554 subdir_part = Target._get_id_hash(subdir) 555 # preserve myid for better debuggability 556 return subdir_part + '@@' + my_id 557 return my_id 558 559 def get_id(self) -> str: 560 return self.construct_id_from_path( 561 self.subdir, self.name, self.type_suffix()) 562 563 def process_kwargs_base(self, kwargs: T.Dict[str, T.Any]) -> None: 564 if 'build_by_default' in kwargs: 565 self.build_by_default = kwargs['build_by_default'] 566 if not isinstance(self.build_by_default, bool): 567 raise InvalidArguments('build_by_default must be a boolean value.') 568 elif kwargs.get('install', False): 569 # For backward compatibility, if build_by_default is not explicitly 570 # set, use the value of 'install' if it's enabled. 571 self.build_by_default = True 572 573 option_overrides = self.parse_overrides(kwargs) 574 575 for k, v in option_overrides.items(): 576 if k.lang: 577 self.option_overrides_compiler[k.evolve(machine=self.for_machine)] = v 578 continue 579 self.option_overrides_base[k] = v 580 581 @staticmethod 582 def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[OptionKey, str]: 583 result: T.Dict[OptionKey, str] = {} 584 overrides = stringlistify(kwargs.get('override_options', [])) 585 for o in overrides: 586 if '=' not in o: 587 raise InvalidArguments('Overrides must be of form "key=value"') 588 k, v = o.split('=', 1) 589 key = OptionKey.from_string(k.strip()) 590 v = v.strip() 591 result[key] = v 592 return result 593 594 def is_linkable_target(self) -> bool: 595 return False 596 597 def get_outputs(self) -> T.List[str]: 598 return [] 599 600 def should_install(self) -> bool: 601 return False 602 603class BuildTarget(Target): 604 known_kwargs = known_build_target_kwargs 605 606 def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, 607 sources: T.List['SourceOutputs'], objects, environment: environment.Environment, kwargs): 608 super().__init__(name, subdir, subproject, True, for_machine) 609 unity_opt = environment.coredata.get_option(OptionKey('unity')) 610 self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '') 611 self.environment = environment 612 self.compilers = OrderedDict() # type: OrderedDict[str, Compiler] 613 self.objects = [] 614 self.external_deps = [] 615 self.include_dirs = [] 616 self.link_language = kwargs.get('link_language') 617 self.link_targets: T.List[BuildTarget] = [] 618 self.link_whole_targets = [] 619 self.link_depends = [] 620 self.added_deps = set() 621 self.name_prefix_set = False 622 self.name_suffix_set = False 623 self.filename = 'no_name' 624 # The list of all files outputted by this target. Useful in cases such 625 # as Vala which generates .vapi and .h besides the compiled output. 626 self.outputs = [self.filename] 627 self.need_install = False 628 self.pch = {} 629 self.extra_args: T.Dict[str, T.List['FileOrString']] = {} 630 self.sources: T.List[File] = [] 631 self.generated: T.List[T.Union[GeneratedList, CustomTarget, CustomTargetIndex]] = [] 632 self.d_features = {} 633 self.pic = False 634 self.pie = False 635 # Track build_rpath entries so we can remove them at install time 636 self.rpath_dirs_to_remove: T.Set[bytes] = set() 637 self.process_sourcelist(sources) 638 # Objects can be: 639 # 1. Pre-existing objects provided by the user with the `objects:` kwarg 640 # 2. Compiled objects created by and extracted from another target 641 self.process_objectlist(objects) 642 self.process_kwargs(kwargs, environment) 643 self.check_unknown_kwargs(kwargs) 644 self.process_compilers() 645 if not any([self.sources, self.generated, self.objects, self.link_whole]): 646 raise InvalidArguments(f'Build target {name} has no sources.') 647 self.process_compilers_late() 648 self.validate_sources() 649 self.validate_install(environment) 650 self.check_module_linking() 651 652 def __repr__(self): 653 repr_str = "<{0} {1}: {2}>" 654 return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) 655 656 def __str__(self): 657 return f"{self.name}" 658 659 def validate_install(self, environment): 660 if self.for_machine is MachineChoice.BUILD and self.need_install: 661 if environment.is_cross_build(): 662 raise InvalidArguments('Tried to install a target for the build machine in a cross build.') 663 else: 664 mlog.warning('Installing target build for the build machine. This will fail in a cross build.') 665 666 def check_unknown_kwargs(self, kwargs): 667 # Override this method in derived classes that have more 668 # keywords. 669 self.check_unknown_kwargs_int(kwargs, self.known_kwargs) 670 671 def check_unknown_kwargs_int(self, kwargs, known_kwargs): 672 unknowns = [] 673 for k in kwargs: 674 if k not in known_kwargs: 675 unknowns.append(k) 676 if len(unknowns) > 0: 677 mlog.warning('Unknown keyword argument(s) in target {}: {}.'.format(self.name, ', '.join(unknowns))) 678 679 def process_objectlist(self, objects): 680 assert(isinstance(objects, list)) 681 for s in objects: 682 if isinstance(s, (str, File, ExtractedObjects)): 683 self.objects.append(s) 684 elif isinstance(s, (GeneratedList, CustomTarget)): 685 msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \ 686 f'for target {self.name!r}.\nIt is meant only for ' + \ 687 'pre-built object files that are shipped with the\nsource ' + \ 688 'tree. Try adding it in the list of sources.' 689 raise InvalidArguments(msg) 690 else: 691 raise InvalidArguments(f'Bad object of type {type(s).__name__!r} in target {self.name!r}.') 692 693 def process_sourcelist(self, sources: T.List['SourceOutputs']) -> None: 694 """Split sources into generated and static sources. 695 696 Sources can be: 697 1. Pre-existing source files in the source tree (static) 698 2. Pre-existing sources generated by configure_file in the build tree. 699 (static as they are only regenerated if meson itself is regenerated) 700 3. Sources files generated by another target or a Generator (generated) 701 """ 702 added_sources: T.Set[File] = set() # If the same source is defined multiple times, use it only once. 703 for s in sources: 704 if isinstance(s, File): 705 if s not in added_sources: 706 self.sources.append(s) 707 added_sources.add(s) 708 elif isinstance(s, (CustomTarget, CustomTargetIndex, GeneratedList)): 709 self.generated.append(s) 710 711 @staticmethod 712 def can_compile_remove_sources(compiler: 'Compiler', sources: T.List['FileOrString']) -> bool: 713 removed = False 714 for s in sources[:]: 715 if compiler.can_compile(s): 716 sources.remove(s) 717 removed = True 718 return removed 719 720 def process_compilers_late(self): 721 """Processes additional compilers after kwargs have been evaluated. 722 723 This can add extra compilers that might be required by keyword 724 arguments, such as link_with or dependencies. It will also try to guess 725 which compiler to use if one hasn't been selected already. 726 """ 727 # Populate list of compilers 728 compilers = self.environment.coredata.compilers[self.for_machine] 729 730 # did user override clink_langs for this target? 731 link_langs = [self.link_language] if self.link_language else clink_langs 732 733 # If this library is linked against another library we need to consider 734 # the languages of those libraries as well. 735 if self.link_targets or self.link_whole_targets: 736 extra = set() 737 for t in itertools.chain(self.link_targets, self.link_whole_targets): 738 if isinstance(t, CustomTarget) or isinstance(t, CustomTargetIndex): 739 continue # We can't know anything about these. 740 for name, compiler in t.compilers.items(): 741 if name in link_langs: 742 extra.add((name, compiler)) 743 for name, compiler in sorted(extra, key=lambda p: sort_clink(p[0])): 744 self.compilers[name] = compiler 745 746 if not self.compilers: 747 # No source files or parent targets, target consists of only object 748 # files of unknown origin. Just add the first clink compiler 749 # that we have and hope that it can link these objects 750 for lang in link_langs: 751 if lang in compilers: 752 self.compilers[lang] = compilers[lang] 753 break 754 755 def process_compilers(self): 756 ''' 757 Populate self.compilers, which is the list of compilers that this 758 target will use for compiling all its sources. 759 We also add compilers that were used by extracted objects to simplify 760 dynamic linker determination. 761 ''' 762 if not self.sources and not self.generated and not self.objects: 763 return 764 # Populate list of compilers 765 compilers = self.environment.coredata.compilers[self.for_machine] 766 # Pre-existing sources 767 sources = list(self.sources) 768 # All generated sources 769 for gensrc in self.generated: 770 for s in gensrc.get_outputs(): 771 # Generated objects can't be compiled, so don't use them for 772 # compiler detection. If our target only has generated objects, 773 # we will fall back to using the first c-like compiler we find, 774 # which is what we need. 775 if not is_object(s): 776 sources.append(s) 777 for d in self.external_deps: 778 for s in d.sources: 779 if isinstance(s, (str, File)): 780 sources.append(s) 781 782 # Sources that were used to create our extracted objects 783 for o in self.objects: 784 if not isinstance(o, ExtractedObjects): 785 continue 786 for s in o.srclist: 787 # Don't add Vala sources since that will pull in the Vala 788 # compiler even though we will never use it since we are 789 # dealing with compiled C code. 790 if not s.endswith(lang_suffixes['vala']): 791 sources.append(s) 792 if sources: 793 # For each source, try to add one compiler that can compile it. 794 # 795 # If it has a suffix that belongs to a known language, we must have 796 # a compiler for that language. 797 # 798 # Otherwise, it's ok if no compilers can compile it, because users 799 # are expected to be able to add arbitrary non-source files to the 800 # sources list 801 for s in sources: 802 for lang, compiler in compilers.items(): 803 if compiler.can_compile(s): 804 if lang not in self.compilers: 805 self.compilers[lang] = compiler 806 break 807 else: 808 if is_known_suffix(s): 809 raise MesonException('No {} machine compiler for "{}"'. 810 format(self.for_machine.get_lower_case_name(), s)) 811 812 # Re-sort according to clink_langs 813 self.compilers = OrderedDict(sorted(self.compilers.items(), 814 key=lambda t: sort_clink(t[0]))) 815 816 # If all our sources are Vala, our target also needs the C compiler but 817 # it won't get added above. 818 if ('vala' in self.compilers or 'cython' in self.compilers) and 'c' not in self.compilers: 819 self.compilers['c'] = compilers['c'] 820 821 def validate_sources(self): 822 if not self.sources: 823 return 824 for lang in ('cs', 'java'): 825 if lang in self.compilers: 826 check_sources = list(self.sources) 827 compiler = self.compilers[lang] 828 if not self.can_compile_remove_sources(compiler, check_sources): 829 raise InvalidArguments(f'No {lang} sources found in target {self.name!r}') 830 if check_sources: 831 m = '{0} targets can only contain {0} files:\n'.format(lang.capitalize()) 832 m += '\n'.join([repr(c) for c in check_sources]) 833 raise InvalidArguments(m) 834 # CSharp and Java targets can't contain any other file types 835 assert(len(self.compilers) == 1) 836 return 837 838 def process_link_depends(self, sources, environment): 839 """Process the link_depends keyword argument. 840 841 This is designed to handle strings, Files, and the output of Custom 842 Targets. Notably it doesn't handle generator() returned objects, since 843 adding them as a link depends would inherently cause them to be 844 generated twice, since the output needs to be passed to the ld_args and 845 link_depends. 846 """ 847 sources = listify(sources) 848 for s in sources: 849 if isinstance(s, File): 850 self.link_depends.append(s) 851 elif isinstance(s, str): 852 self.link_depends.append( 853 File.from_source_file(environment.source_dir, self.subdir, s)) 854 elif hasattr(s, 'get_outputs'): 855 self.link_depends.extend( 856 [File.from_built_file(s.get_subdir(), p) for p in s.get_outputs()]) 857 else: 858 raise InvalidArguments( 859 'Link_depends arguments must be strings, Files, ' 860 'or a Custom Target, or lists thereof.') 861 862 def get_original_kwargs(self): 863 return self.kwargs 864 865 def copy_kwargs(self, kwargs): 866 self.kwargs = copy.copy(kwargs) 867 for k, v in self.kwargs.items(): 868 if isinstance(v, list): 869 self.kwargs[k] = listify(v, flatten=True) 870 for t in ['dependencies', 'link_with', 'include_directories', 'sources']: 871 if t in self.kwargs: 872 self.kwargs[t] = listify(self.kwargs[t], flatten=True) 873 874 def extract_objects(self, srclist: T.List[FileOrString]) -> ExtractedObjects: 875 obj_src = [] 876 sources_set = set(self.sources) 877 for src in srclist: 878 if isinstance(src, str): 879 src = File(False, self.subdir, src) 880 elif isinstance(src, File): 881 FeatureNew.single_use('File argument for extract_objects', '0.50.0', self.subproject) 882 else: 883 raise MesonException(f'Object extraction arguments must be strings or Files (got {type(src).__name__}).') 884 # FIXME: It could be a generated source 885 if src not in sources_set: 886 raise MesonException(f'Tried to extract unknown source {src}.') 887 obj_src.append(src) 888 return ExtractedObjects(self, obj_src) 889 890 def extract_all_objects(self, recursive: bool = True) -> ExtractedObjects: 891 return ExtractedObjects(self, self.sources, self.generated, self.objects, 892 recursive) 893 894 def get_all_link_deps(self): 895 return self.get_transitive_link_deps() 896 897 @lru_cache(maxsize=None) 898 def get_transitive_link_deps(self) -> 'ImmutableListProtocol[Target]': 899 result: T.List[Target] = [] 900 for i in self.link_targets: 901 result += i.get_all_link_deps() 902 return result 903 904 def get_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 905 return self.get_transitive_link_deps_mapping(prefix, environment) 906 907 @lru_cache(maxsize=None) 908 def get_transitive_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 909 result: T.Dict[str, str] = {} 910 for i in self.link_targets: 911 mapping = i.get_link_deps_mapping(prefix, environment) 912 #we are merging two dictionaries, while keeping the earlier one dominant 913 result_tmp = mapping.copy() 914 result_tmp.update(result) 915 result = result_tmp 916 return result 917 918 @lru_cache(maxsize=None) 919 def get_link_dep_subdirs(self) -> 'ImmutableSetProtocol[str]': 920 result: OrderedSet[str] = OrderedSet() 921 for i in self.link_targets: 922 if not isinstance(i, StaticLibrary): 923 result.add(i.get_subdir()) 924 result.update(i.get_link_dep_subdirs()) 925 return result 926 927 def get_default_install_dir(self, environment: environment.Environment) -> str: 928 return environment.get_libdir() 929 930 def get_custom_install_dir(self): 931 return self.install_dir 932 933 def get_custom_install_mode(self): 934 return self.install_mode 935 936 def process_kwargs(self, kwargs, environment): 937 self.process_kwargs_base(kwargs) 938 self.copy_kwargs(kwargs) 939 kwargs.get('modules', []) 940 self.need_install = kwargs.get('install', self.need_install) 941 llist = extract_as_list(kwargs, 'link_with') 942 for linktarget in llist: 943 if isinstance(linktarget, dependencies.ExternalLibrary): 944 raise MesonException(textwrap.dedent('''\ 945 An external library was used in link_with keyword argument, which 946 is reserved for libraries built as part of this project. External 947 libraries must be passed using the dependencies keyword argument 948 instead, because they are conceptually "external dependencies", 949 just like those detected with the dependency() function.\ 950 ''')) 951 self.link(linktarget) 952 lwhole = extract_as_list(kwargs, 'link_whole') 953 for linktarget in lwhole: 954 self.link_whole(linktarget) 955 956 c_pchlist, cpp_pchlist, clist, cpplist, cudalist, cslist, valalist, objclist, objcpplist, fortranlist, rustlist \ 957 = [extract_as_list(kwargs, c) for c in ['c_pch', 'cpp_pch', 'c_args', 'cpp_args', 'cuda_args', 'cs_args', 'vala_args', 'objc_args', 'objcpp_args', 'fortran_args', 'rust_args']] 958 959 self.add_pch('c', c_pchlist) 960 self.add_pch('cpp', cpp_pchlist) 961 compiler_args = {'c': clist, 'cpp': cpplist, 'cuda': cudalist, 'cs': cslist, 'vala': valalist, 'objc': objclist, 'objcpp': objcpplist, 962 'fortran': fortranlist, 'rust': rustlist 963 } 964 for key, value in compiler_args.items(): 965 self.add_compiler_args(key, value) 966 967 if not isinstance(self, Executable) or 'export_dynamic' in kwargs: 968 self.vala_header = kwargs.get('vala_header', self.name + '.h') 969 self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') 970 self.vala_gir = kwargs.get('vala_gir', None) 971 972 dlist = stringlistify(kwargs.get('d_args', [])) 973 self.add_compiler_args('d', dlist) 974 dfeatures = dict() 975 dfeature_unittest = kwargs.get('d_unittest', False) 976 if dfeature_unittest: 977 dfeatures['unittest'] = dfeature_unittest 978 dfeature_versions = kwargs.get('d_module_versions', []) 979 if dfeature_versions: 980 dfeatures['versions'] = dfeature_versions 981 dfeature_debug = kwargs.get('d_debug', []) 982 if dfeature_debug: 983 dfeatures['debug'] = dfeature_debug 984 if 'd_import_dirs' in kwargs: 985 dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs') 986 for d in dfeature_import_dirs: 987 if not isinstance(d, IncludeDirs): 988 raise InvalidArguments('Arguments to d_import_dirs must be include_directories.') 989 dfeatures['import_dirs'] = dfeature_import_dirs 990 if dfeatures: 991 self.d_features = dfeatures 992 993 self.link_args = extract_as_list(kwargs, 'link_args') 994 for i in self.link_args: 995 if not isinstance(i, str): 996 raise InvalidArguments('Link_args arguments must be strings.') 997 for l in self.link_args: 998 if '-Wl,-rpath' in l or l.startswith('-rpath'): 999 mlog.warning(textwrap.dedent('''\ 1000 Please do not define rpath with a linker argument, use install_rpath 1001 or build_rpath properties instead. 1002 This will become a hard error in a future Meson release.\ 1003 ''')) 1004 self.process_link_depends(kwargs.get('link_depends', []), environment) 1005 # Target-specific include dirs must be added BEFORE include dirs from 1006 # internal deps (added inside self.add_deps()) to override them. 1007 inclist = extract_as_list(kwargs, 'include_directories') 1008 self.add_include_dirs(inclist) 1009 # Add dependencies (which also have include_directories) 1010 deplist = extract_as_list(kwargs, 'dependencies') 1011 self.add_deps(deplist) 1012 # If an item in this list is False, the output corresponding to 1013 # the list index of that item will not be installed 1014 self.install_dir = typeslistify(kwargs.get('install_dir', [None]), 1015 (str, bool)) 1016 self.install_mode = kwargs.get('install_mode', None) 1017 main_class = kwargs.get('main_class', '') 1018 if not isinstance(main_class, str): 1019 raise InvalidArguments('Main class must be a string') 1020 self.main_class = main_class 1021 if isinstance(self, Executable): 1022 # This kwarg is deprecated. The value of "none" means that the kwarg 1023 # was not specified and win_subsystem should be used instead. 1024 self.gui_app = None 1025 if 'gui_app' in kwargs: 1026 if 'win_subsystem' in kwargs: 1027 raise InvalidArguments('Can specify only gui_app or win_subsystem for a target, not both.') 1028 self.gui_app = kwargs['gui_app'] 1029 if not isinstance(self.gui_app, bool): 1030 raise InvalidArguments('Argument gui_app must be boolean.') 1031 self.win_subsystem = self.validate_win_subsystem(kwargs.get('win_subsystem', 'console')) 1032 elif 'gui_app' in kwargs: 1033 raise InvalidArguments('Argument gui_app can only be used on executables.') 1034 elif 'win_subsystem' in kwargs: 1035 raise InvalidArguments('Argument win_subsystem can only be used on executables.') 1036 extra_files = extract_as_list(kwargs, 'extra_files') 1037 for i in extra_files: 1038 assert(isinstance(i, File)) 1039 trial = os.path.join(environment.get_source_dir(), i.subdir, i.fname) 1040 if not(os.path.isfile(trial)): 1041 raise InvalidArguments(f'Tried to add non-existing extra file {i}.') 1042 self.extra_files = extra_files 1043 self.install_rpath: str = kwargs.get('install_rpath', '') 1044 if not isinstance(self.install_rpath, str): 1045 raise InvalidArguments('Install_rpath is not a string.') 1046 self.build_rpath = kwargs.get('build_rpath', '') 1047 if not isinstance(self.build_rpath, str): 1048 raise InvalidArguments('Build_rpath is not a string.') 1049 resources = extract_as_list(kwargs, 'resources') 1050 for r in resources: 1051 if not isinstance(r, str): 1052 raise InvalidArguments('Resource argument is not a string.') 1053 trial = os.path.join(environment.get_source_dir(), self.subdir, r) 1054 if not os.path.isfile(trial): 1055 raise InvalidArguments(f'Tried to add non-existing resource {r}.') 1056 self.resources = resources 1057 if 'name_prefix' in kwargs: 1058 name_prefix = kwargs['name_prefix'] 1059 if isinstance(name_prefix, list): 1060 if name_prefix: 1061 raise InvalidArguments('name_prefix array must be empty to signify default.') 1062 else: 1063 if not isinstance(name_prefix, str): 1064 raise InvalidArguments('name_prefix must be a string.') 1065 self.prefix = name_prefix 1066 self.name_prefix_set = True 1067 if 'name_suffix' in kwargs: 1068 name_suffix = kwargs['name_suffix'] 1069 if isinstance(name_suffix, list): 1070 if name_suffix: 1071 raise InvalidArguments('name_suffix array must be empty to signify default.') 1072 else: 1073 if not isinstance(name_suffix, str): 1074 raise InvalidArguments('name_suffix must be a string.') 1075 if name_suffix == '': 1076 raise InvalidArguments('name_suffix should not be an empty string. ' 1077 'If you want meson to use the default behaviour ' 1078 'for each platform pass `[]` (empty array)') 1079 self.suffix = name_suffix 1080 self.name_suffix_set = True 1081 if isinstance(self, StaticLibrary): 1082 # You can't disable PIC on OS X. The compiler ignores -fno-PIC. 1083 # PIC is always on for Windows (all code is position-independent 1084 # since library loading is done differently) 1085 m = self.environment.machines[self.for_machine] 1086 if m.is_darwin() or m.is_windows(): 1087 self.pic = True 1088 else: 1089 self.pic = self._extract_pic_pie(kwargs, 'pic', environment, 'b_staticpic') 1090 if isinstance(self, Executable) or (isinstance(self, StaticLibrary) and not self.pic): 1091 # Executables must be PIE on Android 1092 if self.environment.machines[self.for_machine].is_android(): 1093 self.pie = True 1094 else: 1095 self.pie = self._extract_pic_pie(kwargs, 'pie', environment, 'b_pie') 1096 self.implicit_include_directories = kwargs.get('implicit_include_directories', True) 1097 if not isinstance(self.implicit_include_directories, bool): 1098 raise InvalidArguments('Implicit_include_directories must be a boolean.') 1099 self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') 1100 if not isinstance(self.gnu_symbol_visibility, str): 1101 raise InvalidArguments('GNU symbol visibility must be a string.') 1102 if self.gnu_symbol_visibility != '': 1103 permitted = ['default', 'internal', 'hidden', 'protected', 'inlineshidden'] 1104 if self.gnu_symbol_visibility not in permitted: 1105 raise InvalidArguments('GNU symbol visibility arg {} not one of: {}'.format(self.symbol_visibility, ', '.join(permitted))) 1106 1107 def validate_win_subsystem(self, value: str) -> str: 1108 value = value.lower() 1109 if re.fullmatch(r'(boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(,\d+(\.\d+)?)?', value) is None: 1110 raise InvalidArguments(f'Invalid value for win_subsystem: {value}.') 1111 return value 1112 1113 def _extract_pic_pie(self, kwargs, arg: str, environment, option: str): 1114 # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags 1115 all_flags = self.extra_args['c'] + self.extra_args['cpp'] 1116 if '-f' + arg.lower() in all_flags or '-f' + arg.upper() in all_flags: 1117 mlog.warning(f"Use the '{arg}' kwarg instead of passing '-f{arg}' manually to {self.name!r}") 1118 return True 1119 1120 k = OptionKey(option) 1121 if arg in kwargs: 1122 val = kwargs[arg] 1123 elif k in environment.coredata.options: 1124 val = environment.coredata.options[k].value 1125 else: 1126 val = False 1127 1128 if not isinstance(val, bool): 1129 raise InvalidArguments(f'Argument {arg} to {self.name!r} must be boolean') 1130 return val 1131 1132 def get_filename(self): 1133 return self.filename 1134 1135 def get_outputs(self) -> T.List[str]: 1136 return self.outputs 1137 1138 def get_extra_args(self, language): 1139 return self.extra_args.get(language, []) 1140 1141 def get_dependencies(self, exclude=None): 1142 transitive_deps = [] 1143 if exclude is None: 1144 exclude = [] 1145 for t in itertools.chain(self.link_targets, self.link_whole_targets): 1146 if t in transitive_deps or t in exclude: 1147 continue 1148 transitive_deps.append(t) 1149 if isinstance(t, StaticLibrary): 1150 transitive_deps += t.get_dependencies(transitive_deps + exclude) 1151 return transitive_deps 1152 1153 def get_source_subdir(self): 1154 return self.subdir 1155 1156 def get_sources(self): 1157 return self.sources 1158 1159 def get_objects(self): 1160 return self.objects 1161 1162 def get_generated_sources(self): 1163 return self.generated 1164 1165 def should_install(self) -> bool: 1166 return self.need_install 1167 1168 def has_pch(self): 1169 return len(self.pch) > 0 1170 1171 def get_pch(self, language): 1172 try: 1173 return self.pch[language] 1174 except KeyError: 1175 return[] 1176 1177 def get_include_dirs(self): 1178 return self.include_dirs 1179 1180 def add_deps(self, deps): 1181 deps = listify(deps) 1182 for dep in deps: 1183 if dep in self.added_deps: 1184 continue 1185 if isinstance(dep, dependencies.InternalDependency): 1186 # Those parts that are internal. 1187 self.process_sourcelist(dep.sources) 1188 self.add_include_dirs(dep.include_directories, dep.get_include_type()) 1189 for l in dep.libraries: 1190 self.link(l) 1191 for l in dep.whole_libraries: 1192 self.link_whole(l) 1193 if dep.get_compile_args() or dep.get_link_args(): 1194 # Those parts that are external. 1195 extpart = dependencies.InternalDependency('undefined', 1196 [], 1197 dep.get_compile_args(), 1198 dep.get_link_args(), 1199 [], [], [], [], {}) 1200 self.external_deps.append(extpart) 1201 # Deps of deps. 1202 self.add_deps(dep.ext_deps) 1203 elif isinstance(dep, dependencies.Dependency): 1204 if dep not in self.external_deps: 1205 self.external_deps.append(dep) 1206 self.process_sourcelist(dep.get_sources()) 1207 self.add_deps(dep.ext_deps) 1208 elif isinstance(dep, BuildTarget): 1209 raise InvalidArguments('''Tried to use a build target as a dependency. 1210You probably should put it in link_with instead.''') 1211 else: 1212 # This is a bit of a hack. We do not want Build to know anything 1213 # about the interpreter so we can't import it and use isinstance. 1214 # This should be reliable enough. 1215 if hasattr(dep, 'project_args_frozen') or hasattr(dep, 'global_args_frozen'): 1216 raise InvalidArguments('Tried to use subproject object as a dependency.\n' 1217 'You probably wanted to use a dependency declared in it instead.\n' 1218 'Access it by calling get_variable() on the subproject object.') 1219 raise InvalidArguments(f'Argument is of an unacceptable type {type(dep).__name__!r}.\nMust be ' 1220 'either an external dependency (returned by find_library() or ' 1221 'dependency()) or an internal dependency (returned by ' 1222 'declare_dependency()).') 1223 self.added_deps.add(dep) 1224 1225 def get_external_deps(self): 1226 return self.external_deps 1227 1228 def is_internal(self): 1229 return isinstance(self, StaticLibrary) and not self.need_install 1230 1231 def link(self, target): 1232 for t in listify(target): 1233 if isinstance(self, StaticLibrary) and self.need_install: 1234 if isinstance(t, (CustomTarget, CustomTargetIndex)): 1235 if not t.should_install(): 1236 mlog.warning(f'Try to link an installed static library target {self.name} with a' 1237 'custom target that is not installed, this might cause problems' 1238 'when you try to use this static library') 1239 elif t.is_internal(): 1240 # When we're a static library and we link_with to an 1241 # internal/convenience library, promote to link_whole. 1242 return self.link_whole(t) 1243 if not isinstance(t, (Target, CustomTargetIndex)): 1244 raise InvalidArguments(f'{t!r} is not a target.') 1245 if not t.is_linkable_target(): 1246 raise InvalidArguments(f"Link target '{t!s}' is not linkable.") 1247 if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic: 1248 msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. " 1249 msg += "Use the 'pic' option to static_library to build with PIC." 1250 raise InvalidArguments(msg) 1251 if self.for_machine is not t.for_machine: 1252 msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}' 1253 if self.environment.is_cross_build(): 1254 raise InvalidArguments(msg + ' This is not possible in a cross build.') 1255 else: 1256 mlog.warning(msg + ' This will fail in cross build.') 1257 self.link_targets.append(t) 1258 1259 def link_whole(self, target): 1260 for t in listify(target): 1261 if isinstance(t, (CustomTarget, CustomTargetIndex)): 1262 if not t.is_linkable_target(): 1263 raise InvalidArguments(f'Custom target {t!r} is not linkable.') 1264 if not t.get_filename().endswith('.a'): 1265 raise InvalidArguments('Can only link_whole custom targets that are .a archives.') 1266 if isinstance(self, StaticLibrary): 1267 # FIXME: We could extract the .a archive to get object files 1268 raise InvalidArguments('Cannot link_whole a custom target into a static library') 1269 elif not isinstance(t, StaticLibrary): 1270 raise InvalidArguments(f'{t!r} is not a static library.') 1271 elif isinstance(self, SharedLibrary) and not t.pic: 1272 msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. " 1273 msg += "Use the 'pic' option to static_library to build with PIC." 1274 raise InvalidArguments(msg) 1275 if self.for_machine is not t.for_machine: 1276 msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}' 1277 if self.environment.is_cross_build(): 1278 raise InvalidArguments(msg + ' This is not possible in a cross build.') 1279 else: 1280 mlog.warning(msg + ' This will fail in cross build.') 1281 if isinstance(self, StaticLibrary): 1282 # When we're a static library and we link_whole: to another static 1283 # library, we need to add that target's objects to ourselves. 1284 self.objects += t.extract_all_objects_recurse() 1285 self.link_whole_targets.append(t) 1286 1287 def extract_all_objects_recurse(self): 1288 objs = [self.extract_all_objects()] 1289 for t in self.link_targets: 1290 if t.is_internal(): 1291 objs += t.extract_all_objects_recurse() 1292 return objs 1293 1294 def add_pch(self, language, pchlist): 1295 if not pchlist: 1296 return 1297 elif len(pchlist) == 1: 1298 if not environment.is_header(pchlist[0]): 1299 raise InvalidArguments(f'PCH argument {pchlist[0]} is not a header.') 1300 elif len(pchlist) == 2: 1301 if environment.is_header(pchlist[0]): 1302 if not environment.is_source(pchlist[1]): 1303 raise InvalidArguments('PCH definition must contain one header and at most one source.') 1304 elif environment.is_source(pchlist[0]): 1305 if not environment.is_header(pchlist[1]): 1306 raise InvalidArguments('PCH definition must contain one header and at most one source.') 1307 pchlist = [pchlist[1], pchlist[0]] 1308 else: 1309 raise InvalidArguments(f'PCH argument {pchlist[0]} is of unknown type.') 1310 1311 if (os.path.dirname(pchlist[0]) != os.path.dirname(pchlist[1])): 1312 raise InvalidArguments('PCH files must be stored in the same folder.') 1313 1314 mlog.warning('PCH source files are deprecated, only a single header file should be used.') 1315 elif len(pchlist) > 2: 1316 raise InvalidArguments('PCH definition may have a maximum of 2 files.') 1317 for f in pchlist: 1318 if not isinstance(f, str): 1319 raise MesonException('PCH arguments must be strings.') 1320 if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, f)): 1321 raise MesonException(f'File {f} does not exist.') 1322 self.pch[language] = pchlist 1323 1324 def add_include_dirs(self, args, set_is_system: T.Optional[str] = None): 1325 ids = [] 1326 for a in args: 1327 if not isinstance(a, IncludeDirs): 1328 raise InvalidArguments('Include directory to be added is not an include directory object.') 1329 ids.append(a) 1330 if set_is_system is None: 1331 set_is_system = 'preserve' 1332 if set_is_system != 'preserve': 1333 is_system = set_is_system == 'system' 1334 ids = [IncludeDirs(x.get_curdir(), x.get_incdirs(), is_system, x.get_extra_build_dirs()) for x in ids] 1335 self.include_dirs += ids 1336 1337 def add_compiler_args(self, language: str, args: T.List['FileOrString']) -> None: 1338 args = listify(args) 1339 for a in args: 1340 if not isinstance(a, (str, File)): 1341 raise InvalidArguments('A non-string passed to compiler args.') 1342 if language in self.extra_args: 1343 self.extra_args[language] += args 1344 else: 1345 self.extra_args[language] = args 1346 1347 def get_aliases(self) -> T.Dict[str, str]: 1348 return {} 1349 1350 def get_langs_used_by_deps(self) -> T.List[str]: 1351 ''' 1352 Sometimes you want to link to a C++ library that exports C API, which 1353 means the linker must link in the C++ stdlib, and we must use a C++ 1354 compiler for linking. The same is also applicable for objc/objc++, etc, 1355 so we can keep using clink_langs for the priority order. 1356 1357 See: https://github.com/mesonbuild/meson/issues/1653 1358 ''' 1359 langs = [] # type: T.List[str] 1360 1361 # Check if any of the external libraries were written in this language 1362 for dep in self.external_deps: 1363 if dep.language is None: 1364 continue 1365 if dep.language not in langs: 1366 langs.append(dep.language) 1367 # Check if any of the internal libraries this target links to were 1368 # written in this language 1369 for link_target in itertools.chain(self.link_targets, self.link_whole_targets): 1370 if isinstance(link_target, (CustomTarget, CustomTargetIndex)): 1371 continue 1372 for language in link_target.compilers: 1373 if language not in langs: 1374 langs.append(language) 1375 1376 return langs 1377 1378 def get_prelinker(self): 1379 all_compilers = self.environment.coredata.compilers[self.for_machine] 1380 if self.link_language: 1381 comp = all_compilers[self.link_language] 1382 return comp 1383 for l in clink_langs: 1384 if l in self.compilers: 1385 try: 1386 prelinker = all_compilers[l] 1387 except KeyError: 1388 raise MesonException( 1389 f'Could not get a prelinker linker for build target {self.name!r}. ' 1390 f'Requires a compiler for language "{l}", but that is not ' 1391 'a project language.') 1392 return prelinker 1393 raise MesonException(f'Could not determine prelinker for {self.name!r}.') 1394 1395 def get_clink_dynamic_linker_and_stdlibs(self): 1396 ''' 1397 We use the order of languages in `clink_langs` to determine which 1398 linker to use in case the target has sources compiled with multiple 1399 compilers. All languages other than those in this list have their own 1400 linker. 1401 Note that Vala outputs C code, so Vala sources can use any linker 1402 that can link compiled C. We don't actually need to add an exception 1403 for Vala here because of that. 1404 ''' 1405 # Populate list of all compilers, not just those being used to compile 1406 # sources in this target 1407 all_compilers = self.environment.coredata.compilers[self.for_machine] 1408 1409 # If the user set the link_language, just return that. 1410 if self.link_language: 1411 comp = all_compilers[self.link_language] 1412 return comp, comp.language_stdlib_only_link_flags() 1413 1414 # Languages used by dependencies 1415 dep_langs = self.get_langs_used_by_deps() 1416 # Pick a compiler based on the language priority-order 1417 for l in clink_langs: 1418 if l in self.compilers or l in dep_langs: 1419 try: 1420 linker = all_compilers[l] 1421 except KeyError: 1422 raise MesonException( 1423 f'Could not get a dynamic linker for build target {self.name!r}. ' 1424 f'Requires a linker for language "{l}", but that is not ' 1425 'a project language.') 1426 stdlib_args = [] 1427 added_languages = set() 1428 for dl in itertools.chain(self.compilers, dep_langs): 1429 if dl != linker.language: 1430 stdlib_args += all_compilers[dl].language_stdlib_only_link_flags() 1431 added_languages.add(dl) 1432 # Type of var 'linker' is Compiler. 1433 # Pretty hard to fix because the return value is passed everywhere 1434 return linker, stdlib_args 1435 1436 raise AssertionError(f'Could not get a dynamic linker for build target {self.name!r}') 1437 1438 def uses_rust(self) -> bool: 1439 """Is this target a rust target.""" 1440 if self.sources: 1441 first_file = self.sources[0] 1442 if first_file.fname.endswith('.rs'): 1443 return True 1444 elif self.generated: 1445 if self.generated[0].get_outputs()[0].endswith('.rs'): 1446 return True 1447 return False 1448 1449 def get_using_msvc(self): 1450 ''' 1451 Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary, 1452 and SharedLibrary for deciding when to use MSVC-specific file naming 1453 and debug filenames. 1454 1455 If at least some code is built with MSVC and the final library is 1456 linked with MSVC, we can be sure that some debug info will be 1457 generated. We only check the dynamic linker here because the static 1458 linker is guaranteed to be of the same type. 1459 1460 Interesting cases: 1461 1. The Vala compiler outputs C code to be compiled by whatever 1462 C compiler we're using, so all objects will still be created by the 1463 MSVC compiler. 1464 2. If the target contains only objects, process_compilers guesses and 1465 picks the first compiler that smells right. 1466 ''' 1467 # Rustc can use msvc style linkers 1468 if self.uses_rust(): 1469 compiler = self.environment.coredata.compilers[self.for_machine]['rust'] 1470 else: 1471 compiler, _ = self.get_clink_dynamic_linker_and_stdlibs() 1472 # Mixing many languages with MSVC is not supported yet so ignore stdlibs. 1473 return compiler and compiler.get_linker_id() in {'link', 'lld-link', 'xilink', 'optlink'} 1474 1475 def check_module_linking(self): 1476 ''' 1477 Warn if shared modules are linked with target: (link_with) #2865 1478 ''' 1479 for link_target in self.link_targets: 1480 if isinstance(link_target, SharedModule): 1481 if self.environment.machines[self.for_machine].is_darwin(): 1482 raise MesonException( 1483 'target links against shared modules. This is not permitted on OSX') 1484 else: 1485 mlog.warning('target links against shared modules. This ' 1486 'is not recommended as it is not supported on some ' 1487 'platforms') 1488 return 1489 1490class Generator(HoldableObject): 1491 def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], 1492 arguments: T.List[str], 1493 output: T.List[str], 1494 *, 1495 depfile: T.Optional[str] = None, 1496 capture: bool = False, 1497 depends: T.Optional[T.List[T.Union[BuildTarget, 'CustomTarget']]] = None, 1498 name: str = 'Generator'): 1499 self.exe = exe 1500 self.depfile = depfile 1501 self.capture = capture 1502 self.depends: T.List[T.Union[BuildTarget, 'CustomTarget']] = depends or [] 1503 self.arglist = arguments 1504 self.outputs = output 1505 self.name = name 1506 1507 def __repr__(self) -> str: 1508 repr_str = "<{0}: {1}>" 1509 return repr_str.format(self.__class__.__name__, self.exe) 1510 1511 def get_exe(self) -> T.Union['Executable', programs.ExternalProgram]: 1512 return self.exe 1513 1514 def get_base_outnames(self, inname: str) -> T.List[str]: 1515 plainname = os.path.basename(inname) 1516 basename = os.path.splitext(plainname)[0] 1517 bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] 1518 return bases 1519 1520 def get_dep_outname(self, inname: str) -> T.List[str]: 1521 if self.depfile is None: 1522 raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.') 1523 plainname = os.path.basename(inname) 1524 basename = os.path.splitext(plainname)[0] 1525 return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) 1526 1527 def get_arglist(self, inname: str) -> T.List[str]: 1528 plainname = os.path.basename(inname) 1529 basename = os.path.splitext(plainname)[0] 1530 return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] 1531 1532 @staticmethod 1533 def is_parent_path(parent: str, trial: str) -> bool: 1534 relpath = pathlib.PurePath(trial).relative_to(parent) 1535 return relpath.parts[0] != '..' # For subdirs we can only go "down". 1536 1537 def process_files(self, files: T.Iterable[T.Union[str, File, 'CustomTarget', 'CustomTargetIndex', 'GeneratedList']], 1538 state: T.Union['Interpreter', 'ModuleState'], 1539 preserve_path_from: T.Optional[str] = None, 1540 extra_args: T.Optional[T.List[str]] = None) -> 'GeneratedList': 1541 output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) 1542 1543 for e in files: 1544 if isinstance(e, CustomTarget): 1545 output.depends.add(e) 1546 if isinstance(e, CustomTargetIndex): 1547 output.depends.add(e.target) 1548 1549 if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)): 1550 self.depends.append(e) # BUG: this should go in the GeneratedList object, not this object. 1551 fs = [File.from_built_file(state.subdir, f) for f in e.get_outputs()] 1552 elif isinstance(e, str): 1553 fs = [File.from_source_file(state.environment.source_dir, state.subdir, e)] 1554 else: 1555 fs = [e] 1556 1557 for f in fs: 1558 if preserve_path_from: 1559 abs_f = f.absolute_path(state.environment.source_dir, state.environment.build_dir) 1560 if not self.is_parent_path(preserve_path_from, abs_f): 1561 raise InvalidArguments('generator.process: When using preserve_path_from, all input files must be in a subdirectory of the given dir.') 1562 output.add_file(f, state) 1563 return output 1564 1565 1566class GeneratedList(HoldableObject): 1567 1568 """The output of generator.process.""" 1569 1570 def __init__(self, generator: Generator, subdir: str, 1571 preserve_path_from: T.Optional[str], 1572 extra_args: T.List[str]): 1573 self.generator = generator 1574 self.name = generator.exe 1575 self.depends: T.Set['CustomTarget'] = set() # Things this target depends on (because e.g. a custom target was used as input) 1576 self.subdir = subdir 1577 self.infilelist: T.List['File'] = [] 1578 self.outfilelist: T.List[str] = [] 1579 self.outmap: T.Dict[File, T.List[str]] = {} 1580 self.extra_depends = [] # XXX: Doesn't seem to be used? 1581 self.depend_files: T.List[File] = [] 1582 self.preserve_path_from = preserve_path_from 1583 self.extra_args: T.List[str] = extra_args if extra_args is not None else [] 1584 1585 if isinstance(self.generator.exe, programs.ExternalProgram): 1586 if not self.generator.exe.found(): 1587 raise InvalidArguments('Tried to use not-found external program as generator') 1588 path = self.generator.exe.get_path() 1589 if os.path.isabs(path): 1590 # Can only add a dependency on an external program which we 1591 # know the absolute path of 1592 self.depend_files.append(File.from_absolute_file(path)) 1593 1594 def add_preserved_path_segment(self, infile: File, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]: 1595 result: T.List[str] = [] 1596 in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) 1597 assert os.path.isabs(self.preserve_path_from) 1598 rel = os.path.relpath(in_abs, self.preserve_path_from) 1599 path_segment = os.path.dirname(rel) 1600 for of in outfiles: 1601 result.append(os.path.join(path_segment, of)) 1602 return result 1603 1604 def add_file(self, newfile: File, state: T.Union['Interpreter', 'ModuleState']) -> None: 1605 self.infilelist.append(newfile) 1606 outfiles = self.generator.get_base_outnames(newfile.fname) 1607 if self.preserve_path_from: 1608 outfiles = self.add_preserved_path_segment(newfile, outfiles, state) 1609 self.outfilelist += outfiles 1610 self.outmap[newfile] = outfiles 1611 1612 def get_inputs(self) -> T.List['File']: 1613 return self.infilelist 1614 1615 def get_outputs(self) -> T.List[str]: 1616 return self.outfilelist 1617 1618 def get_outputs_for(self, filename: 'File') -> T.List[str]: 1619 return self.outmap[filename] 1620 1621 def get_generator(self) -> 'Generator': 1622 return self.generator 1623 1624 def get_extra_args(self) -> T.List[str]: 1625 return self.extra_args 1626 1627 def get_subdir(self) -> str: 1628 return self.subdir 1629 1630 1631class Executable(BuildTarget): 1632 known_kwargs = known_exe_kwargs 1633 1634 def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, 1635 sources: T.List[File], objects, environment: environment.Environment, kwargs): 1636 self.typename = 'executable' 1637 key = OptionKey('b_pie') 1638 if 'pie' not in kwargs and key in environment.coredata.options: 1639 kwargs['pie'] = environment.coredata.options[key].value 1640 super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) 1641 # Unless overridden, executables have no suffix or prefix. Except on 1642 # Windows and with C#/Mono executables where the suffix is 'exe' 1643 if not hasattr(self, 'prefix'): 1644 self.prefix = '' 1645 if not hasattr(self, 'suffix'): 1646 machine = environment.machines[for_machine] 1647 # Executable for Windows or C#/Mono 1648 if machine.is_windows() or machine.is_cygwin() or 'cs' in self.compilers: 1649 self.suffix = 'exe' 1650 elif machine.system.startswith('wasm') or machine.system == 'emscripten': 1651 self.suffix = 'js' 1652 elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('arm') or 1653 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('arm')): 1654 self.suffix = 'axf' 1655 elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('ccrx') or 1656 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('ccrx')): 1657 self.suffix = 'abs' 1658 elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('xc16')): 1659 self.suffix = 'elf' 1660 elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('c2000') or 1661 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('c2000')): 1662 self.suffix = 'out' 1663 else: 1664 self.suffix = environment.machines[for_machine].get_exe_suffix() 1665 self.filename = self.name 1666 if self.suffix: 1667 self.filename += '.' + self.suffix 1668 self.outputs = [self.filename] 1669 1670 # The import library this target will generate 1671 self.import_filename = None 1672 # The import library that Visual Studio would generate (and accept) 1673 self.vs_import_filename = None 1674 # The import library that GCC would generate (and prefer) 1675 self.gcc_import_filename = None 1676 # The debugging information file this target will generate 1677 self.debug_filename = None 1678 1679 # Check for export_dynamic 1680 self.export_dynamic = False 1681 if kwargs.get('export_dynamic'): 1682 if not isinstance(kwargs['export_dynamic'], bool): 1683 raise InvalidArguments('"export_dynamic" keyword argument must be a boolean') 1684 self.export_dynamic = True 1685 if kwargs.get('implib'): 1686 self.export_dynamic = True 1687 if self.export_dynamic and kwargs.get('implib') is False: 1688 raise InvalidArguments('"implib" keyword argument must not be false for if "export_dynamic" is true') 1689 1690 m = environment.machines[for_machine] 1691 1692 # If using export_dynamic, set the import library name 1693 if self.export_dynamic: 1694 implib_basename = self.name + '.exe' 1695 if not isinstance(kwargs.get('implib', False), bool): 1696 implib_basename = kwargs['implib'] 1697 if m.is_windows() or m.is_cygwin(): 1698 self.vs_import_filename = f'{implib_basename}.lib' 1699 self.gcc_import_filename = f'lib{implib_basename}.a' 1700 if self.get_using_msvc(): 1701 self.import_filename = self.vs_import_filename 1702 else: 1703 self.import_filename = self.gcc_import_filename 1704 1705 if m.is_windows() and ('cs' in self.compilers or 1706 self.uses_rust() or 1707 self.get_using_msvc()): 1708 self.debug_filename = self.name + '.pdb' 1709 1710 # Only linkwithable if using export_dynamic 1711 self.is_linkwithable = self.export_dynamic 1712 1713 # Remember that this exe was returned by `find_program()` through an override 1714 self.was_returned_by_find_program = False 1715 1716 def get_default_install_dir(self, environment: environment.Environment) -> str: 1717 return environment.get_bindir() 1718 1719 def description(self): 1720 '''Human friendly description of the executable''' 1721 return self.name 1722 1723 def type_suffix(self): 1724 return "@exe" 1725 1726 def get_import_filename(self): 1727 """ 1728 The name of the import library that will be outputted by the compiler 1729 1730 Returns None if there is no import library required for this platform 1731 """ 1732 return self.import_filename 1733 1734 def get_import_filenameslist(self): 1735 if self.import_filename: 1736 return [self.vs_import_filename, self.gcc_import_filename] 1737 return [] 1738 1739 def get_debug_filename(self): 1740 """ 1741 The name of debuginfo file that will be created by the compiler 1742 1743 Returns None if the build won't create any debuginfo file 1744 """ 1745 return self.debug_filename 1746 1747 def is_linkable_target(self): 1748 return self.is_linkwithable 1749 1750class StaticLibrary(BuildTarget): 1751 known_kwargs = known_stlib_kwargs 1752 1753 def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): 1754 self.typename = 'static library' 1755 super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) 1756 if 'cs' in self.compilers: 1757 raise InvalidArguments('Static libraries not supported for C#.') 1758 if 'rust' in self.compilers: 1759 # If no crate type is specified, or it's the generic lib type, use rlib 1760 if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': 1761 mlog.debug('Defaulting Rust static library target crate type to rlib') 1762 self.rust_crate_type = 'rlib' 1763 # Don't let configuration proceed with a non-static crate type 1764 elif self.rust_crate_type not in ['rlib', 'staticlib']: 1765 raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for static libraries; must be "rlib" or "staticlib"') 1766 # By default a static library is named libfoo.a even on Windows because 1767 # MSVC does not have a consistent convention for what static libraries 1768 # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses 1769 # it and GCC only looks for static libraries called foo.lib and 1770 # libfoo.a. However, we cannot use foo.lib because that's the same as 1771 # the import library. Using libfoo.a is ok because people using MSVC 1772 # always pass the library filename while linking anyway. 1773 if not hasattr(self, 'prefix'): 1774 self.prefix = 'lib' 1775 if not hasattr(self, 'suffix'): 1776 if 'rust' in self.compilers: 1777 if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'rlib': 1778 # default Rust static library suffix 1779 self.suffix = 'rlib' 1780 elif self.rust_crate_type == 'staticlib': 1781 self.suffix = 'a' 1782 else: 1783 self.suffix = 'a' 1784 self.filename = self.prefix + self.name + '.' + self.suffix 1785 self.outputs = [self.filename] 1786 self.prelink = kwargs.get('prelink', False) 1787 if not isinstance(self.prelink, bool): 1788 raise InvalidArguments('Prelink keyword argument must be a boolean.') 1789 1790 def get_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 1791 return {} 1792 1793 def get_default_install_dir(self, environment): 1794 return environment.get_static_lib_dir() 1795 1796 def type_suffix(self): 1797 return "@sta" 1798 1799 def process_kwargs(self, kwargs, environment): 1800 super().process_kwargs(kwargs, environment) 1801 if 'rust_crate_type' in kwargs: 1802 rust_crate_type = kwargs['rust_crate_type'] 1803 if isinstance(rust_crate_type, str): 1804 self.rust_crate_type = rust_crate_type 1805 else: 1806 raise InvalidArguments(f'Invalid rust_crate_type "{rust_crate_type}": must be a string.') 1807 1808 def is_linkable_target(self): 1809 return True 1810 1811class SharedLibrary(BuildTarget): 1812 known_kwargs = known_shlib_kwargs 1813 1814 def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): 1815 self.typename = 'shared library' 1816 self.soversion = None 1817 self.ltversion = None 1818 # Max length 2, first element is compatibility_version, second is current_version 1819 self.darwin_versions = [] 1820 self.vs_module_defs = None 1821 # The import library this target will generate 1822 self.import_filename = None 1823 # The import library that Visual Studio would generate (and accept) 1824 self.vs_import_filename = None 1825 # The import library that GCC would generate (and prefer) 1826 self.gcc_import_filename = None 1827 # The debugging information file this target will generate 1828 self.debug_filename = None 1829 # Use by the pkgconfig module 1830 self.shared_library_only = False 1831 super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) 1832 if 'rust' in self.compilers: 1833 # If no crate type is specified, or it's the generic lib type, use dylib 1834 if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': 1835 mlog.debug('Defaulting Rust dynamic library target crate type to "dylib"') 1836 self.rust_crate_type = 'dylib' 1837 # Don't let configuration proceed with a non-dynamic crate type 1838 elif self.rust_crate_type not in ['dylib', 'cdylib']: 1839 raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for dynamic libraries; must be "dylib" or "cdylib"') 1840 if not hasattr(self, 'prefix'): 1841 self.prefix = None 1842 if not hasattr(self, 'suffix'): 1843 self.suffix = None 1844 self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1845 self.determine_filenames(environment) 1846 1847 def get_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 1848 result: T.Dict[str, str] = {} 1849 mappings = self.get_transitive_link_deps_mapping(prefix, environment) 1850 old = get_target_macos_dylib_install_name(self) 1851 if old not in mappings: 1852 fname = self.get_filename() 1853 outdirs, _ = self.get_install_dir(self.environment) 1854 new = os.path.join(prefix, outdirs[0], fname) 1855 result.update({old: new}) 1856 mappings.update(result) 1857 return mappings 1858 1859 def get_default_install_dir(self, environment): 1860 return environment.get_shared_lib_dir() 1861 1862 def determine_filenames(self, env): 1863 """ 1864 See https://github.com/mesonbuild/meson/pull/417 for details. 1865 1866 First we determine the filename template (self.filename_tpl), then we 1867 set the output filename (self.filename). 1868 1869 The template is needed while creating aliases (self.get_aliases), 1870 which are needed while generating .so shared libraries for Linux. 1871 1872 Besides this, there's also the import library name, which is only used 1873 on Windows since on that platform the linker uses a separate library 1874 called the "import library" during linking instead of the shared 1875 library (DLL). The toolchain will output an import library in one of 1876 two formats: GCC or Visual Studio. 1877 1878 When we're building with Visual Studio, the import library that will be 1879 generated by the toolchain is self.vs_import_filename, and with 1880 MinGW/GCC, it's self.gcc_import_filename. self.import_filename will 1881 always contain the import library name this target will generate. 1882 """ 1883 prefix = '' 1884 suffix = '' 1885 create_debug_file = False 1886 self.filename_tpl = self.basic_filename_tpl 1887 # NOTE: manual prefix/suffix override is currently only tested for C/C++ 1888 # C# and Mono 1889 if 'cs' in self.compilers: 1890 prefix = '' 1891 suffix = 'dll' 1892 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1893 create_debug_file = True 1894 # C, C++, Swift, Vala 1895 # Only Windows uses a separate import library for linking 1896 # For all other targets/platforms import_filename stays None 1897 elif env.machines[self.for_machine].is_windows(): 1898 suffix = 'dll' 1899 self.vs_import_filename = '{}{}.lib'.format(self.prefix if self.prefix is not None else '', self.name) 1900 self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) 1901 if self.uses_rust(): 1902 # Shared library is of the form foo.dll 1903 prefix = '' 1904 # Import library is called foo.dll.lib 1905 self.import_filename = f'{self.name}.dll.lib' 1906 create_debug_file = True 1907 elif self.get_using_msvc(): 1908 # Shared library is of the form foo.dll 1909 prefix = '' 1910 # Import library is called foo.lib 1911 self.import_filename = self.vs_import_filename 1912 create_debug_file = True 1913 # Assume GCC-compatible naming 1914 else: 1915 # Shared library is of the form libfoo.dll 1916 prefix = 'lib' 1917 # Import library is called libfoo.dll.a 1918 self.import_filename = self.gcc_import_filename 1919 # Shared library has the soversion if it is defined 1920 if self.soversion: 1921 self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' 1922 else: 1923 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1924 elif env.machines[self.for_machine].is_cygwin(): 1925 suffix = 'dll' 1926 self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) 1927 # Shared library is of the form cygfoo.dll 1928 # (ld --dll-search-prefix=cyg is the default) 1929 prefix = 'cyg' 1930 # Import library is called libfoo.dll.a 1931 self.import_filename = self.gcc_import_filename 1932 if self.soversion: 1933 self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' 1934 else: 1935 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1936 elif env.machines[self.for_machine].is_darwin(): 1937 prefix = 'lib' 1938 suffix = 'dylib' 1939 # On macOS, the filename can only contain the major version 1940 if self.soversion: 1941 # libfoo.X.dylib 1942 self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' 1943 else: 1944 # libfoo.dylib 1945 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1946 elif env.machines[self.for_machine].is_android(): 1947 prefix = 'lib' 1948 suffix = 'so' 1949 # Android doesn't support shared_library versioning 1950 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1951 else: 1952 prefix = 'lib' 1953 suffix = 'so' 1954 if self.ltversion: 1955 # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional) 1956 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' 1957 elif self.soversion: 1958 # libfoo.so.X 1959 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' 1960 else: 1961 # No versioning, libfoo.so 1962 self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' 1963 if self.prefix is None: 1964 self.prefix = prefix 1965 if self.suffix is None: 1966 self.suffix = suffix 1967 self.filename = self.filename_tpl.format(self) 1968 self.outputs = [self.filename] 1969 if create_debug_file: 1970 self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb' 1971 1972 @staticmethod 1973 def _validate_darwin_versions(darwin_versions): 1974 try: 1975 if isinstance(darwin_versions, int): 1976 darwin_versions = str(darwin_versions) 1977 if isinstance(darwin_versions, str): 1978 darwin_versions = 2 * [darwin_versions] 1979 if not isinstance(darwin_versions, list): 1980 raise InvalidArguments('Shared library darwin_versions: must be a string, integer,' 1981 f'or a list, not {darwin_versions!r}') 1982 if len(darwin_versions) > 2: 1983 raise InvalidArguments('Shared library darwin_versions: list must contain 2 or fewer elements') 1984 if len(darwin_versions) == 1: 1985 darwin_versions = 2 * darwin_versions 1986 for i, v in enumerate(darwin_versions[:]): 1987 if isinstance(v, int): 1988 v = str(v) 1989 if not isinstance(v, str): 1990 raise InvalidArguments('Shared library darwin_versions: list elements ' 1991 f'must be strings or integers, not {v!r}') 1992 if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v): 1993 raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z where ' 1994 'X, Y, Z are numbers, and Y and Z are optional') 1995 parts = v.split('.') 1996 if len(parts) in (1, 2, 3) and int(parts[0]) > 65535: 1997 raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 1998 'where X is [0, 65535] and Y, Z are optional') 1999 if len(parts) in (2, 3) and int(parts[1]) > 255: 2000 raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 2001 'where Y is [0, 255] and Y, Z are optional') 2002 if len(parts) == 3 and int(parts[2]) > 255: 2003 raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' 2004 'where Z is [0, 255] and Y, Z are optional') 2005 darwin_versions[i] = v 2006 except ValueError: 2007 raise InvalidArguments('Shared library darwin_versions: value is invalid') 2008 return darwin_versions 2009 2010 def process_kwargs(self, kwargs, environment): 2011 super().process_kwargs(kwargs, environment) 2012 2013 if not self.environment.machines[self.for_machine].is_android(): 2014 supports_versioning = True 2015 else: 2016 supports_versioning = False 2017 2018 if supports_versioning: 2019 # Shared library version 2020 if 'version' in kwargs: 2021 self.ltversion = kwargs['version'] 2022 if not isinstance(self.ltversion, str): 2023 raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) 2024 if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): 2025 raise InvalidArguments(f'Invalid Shared library version "{self.ltversion}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.') 2026 # Try to extract/deduce the soversion 2027 if 'soversion' in kwargs: 2028 self.soversion = kwargs['soversion'] 2029 if isinstance(self.soversion, int): 2030 self.soversion = str(self.soversion) 2031 if not isinstance(self.soversion, str): 2032 raise InvalidArguments('Shared library soversion is not a string or integer.') 2033 elif self.ltversion: 2034 # library version is defined, get the soversion from that 2035 # We replicate what Autotools does here and take the first 2036 # number of the version by default. 2037 self.soversion = self.ltversion.split('.')[0] 2038 # macOS, iOS and tvOS dylib compatibility_version and current_version 2039 if 'darwin_versions' in kwargs: 2040 self.darwin_versions = self._validate_darwin_versions(kwargs['darwin_versions']) 2041 elif self.soversion: 2042 # If unspecified, pick the soversion 2043 self.darwin_versions = 2 * [self.soversion] 2044 2045 # Visual Studio module-definitions file 2046 if 'vs_module_defs' in kwargs: 2047 path = kwargs['vs_module_defs'] 2048 if isinstance(path, str): 2049 if os.path.isabs(path): 2050 self.vs_module_defs = File.from_absolute_file(path) 2051 else: 2052 self.vs_module_defs = File.from_source_file(environment.source_dir, self.subdir, path) 2053 self.link_depends.append(self.vs_module_defs) 2054 elif isinstance(path, File): 2055 # When passing a generated file. 2056 self.vs_module_defs = path 2057 self.link_depends.append(path) 2058 elif hasattr(path, 'get_filename'): 2059 # When passing output of a Custom Target 2060 path = File.from_built_file(path.subdir, path.get_filename()) 2061 self.vs_module_defs = path 2062 self.link_depends.append(path) 2063 else: 2064 raise InvalidArguments( 2065 'Shared library vs_module_defs must be either a string, ' 2066 'a file object or a Custom Target') 2067 if 'rust_crate_type' in kwargs: 2068 rust_crate_type = kwargs['rust_crate_type'] 2069 if isinstance(rust_crate_type, str): 2070 self.rust_crate_type = rust_crate_type 2071 else: 2072 raise InvalidArguments(f'Invalid rust_crate_type "{rust_crate_type}": must be a string.') 2073 2074 def get_import_filename(self): 2075 """ 2076 The name of the import library that will be outputted by the compiler 2077 2078 Returns None if there is no import library required for this platform 2079 """ 2080 return self.import_filename 2081 2082 def get_debug_filename(self): 2083 """ 2084 The name of debuginfo file that will be created by the compiler 2085 2086 Returns None if the build won't create any debuginfo file 2087 """ 2088 return self.debug_filename 2089 2090 def get_import_filenameslist(self): 2091 if self.import_filename: 2092 return [self.vs_import_filename, self.gcc_import_filename] 2093 return [] 2094 2095 def get_all_link_deps(self): 2096 return [self] + self.get_transitive_link_deps() 2097 2098 def get_aliases(self) -> T.Dict[str, str]: 2099 """ 2100 If the versioned library name is libfoo.so.0.100.0, aliases are: 2101 * libfoo.so.0 (soversion) -> libfoo.so.0.100.0 2102 * libfoo.so (unversioned; for linking) -> libfoo.so.0 2103 Same for dylib: 2104 * libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib 2105 """ 2106 aliases: T.Dict[str, str] = {} 2107 # Aliases are only useful with .so and .dylib libraries. Also if 2108 # there's no self.soversion (no versioning), we don't need aliases. 2109 if self.suffix not in ('so', 'dylib') or not self.soversion: 2110 return aliases 2111 # With .so libraries, the minor and micro versions are also in the 2112 # filename. If ltversion != soversion we create an soversion alias: 2113 # libfoo.so.0 -> libfoo.so.0.100.0 2114 # Where libfoo.so.0.100.0 is the actual library 2115 if self.suffix == 'so' and self.ltversion and self.ltversion != self.soversion: 2116 alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') 2117 ltversion_filename = alias_tpl.format(self) 2118 aliases[ltversion_filename] = self.filename 2119 # libfoo.so.0/libfoo.0.dylib is the actual library 2120 else: 2121 ltversion_filename = self.filename 2122 # Unversioned alias: 2123 # libfoo.so -> libfoo.so.0 2124 # libfoo.dylib -> libfoo.0.dylib 2125 aliases[self.basic_filename_tpl.format(self)] = ltversion_filename 2126 return aliases 2127 2128 def type_suffix(self): 2129 return "@sha" 2130 2131 def is_linkable_target(self): 2132 return True 2133 2134# A shared library that is meant to be used with dlopen rather than linking 2135# into something else. 2136class SharedModule(SharedLibrary): 2137 known_kwargs = known_shmod_kwargs 2138 2139 def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): 2140 if 'version' in kwargs: 2141 raise MesonException('Shared modules must not specify the version kwarg.') 2142 if 'soversion' in kwargs: 2143 raise MesonException('Shared modules must not specify the soversion kwarg.') 2144 super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) 2145 self.typename = 'shared module' 2146 2147 def get_default_install_dir(self, environment): 2148 return environment.get_shared_module_dir() 2149 2150class BothLibraries(SecondLevelHolder): 2151 def __init__(self, shared: SharedLibrary, static: StaticLibrary) -> None: 2152 self._preferred_library = 'shared' 2153 self.shared = shared 2154 self.static = static 2155 self.subproject = self.shared.subproject 2156 2157 def __repr__(self) -> str: 2158 return f'<BothLibraries: static={repr(self.static)}; shared={repr(self.shared)}>' 2159 2160 def get_default_object(self) -> BuildTarget: 2161 if self._preferred_library == 'shared': 2162 return self.shared 2163 elif self._preferred_library == 'static': 2164 return self.static 2165 raise MesonBugException(f'self._preferred_library == "{self._preferred_library}" is neither "shared" nor "static".') 2166 2167class CommandBase: 2168 def flatten_command(self, cmd): 2169 cmd = listify(cmd) 2170 final_cmd = [] 2171 for c in cmd: 2172 if isinstance(c, str): 2173 final_cmd.append(c) 2174 elif isinstance(c, File): 2175 self.depend_files.append(c) 2176 final_cmd.append(c) 2177 elif isinstance(c, programs.ExternalProgram): 2178 if not c.found(): 2179 raise InvalidArguments('Tried to use not-found external program in "command"') 2180 path = c.get_path() 2181 if os.path.isabs(path): 2182 # Can only add a dependency on an external program which we 2183 # know the absolute path of 2184 self.depend_files.append(File.from_absolute_file(path)) 2185 final_cmd += c.get_command() 2186 elif isinstance(c, (BuildTarget, CustomTarget)): 2187 self.dependencies.append(c) 2188 final_cmd.append(c) 2189 elif isinstance(c, list): 2190 final_cmd += self.flatten_command(c) 2191 else: 2192 raise InvalidArguments(f'Argument {c!r} in "command" is invalid') 2193 return final_cmd 2194 2195class CustomTarget(Target, CommandBase): 2196 known_kwargs = { 2197 'input', 2198 'output', 2199 'command', 2200 'capture', 2201 'feed', 2202 'install', 2203 'install_dir', 2204 'install_mode', 2205 'build_always', 2206 'build_always_stale', 2207 'depends', 2208 'depend_files', 2209 'depfile', 2210 'build_by_default', 2211 'override_options', 2212 'console', 2213 'env', 2214 } 2215 2216 def __init__(self, name: str, subdir: str, subproject: str, kwargs: T.Dict[str, T.Any], 2217 absolute_paths: bool = False, backend: T.Optional['Backend'] = None): 2218 self.typename = 'custom' 2219 # TODO expose keyword arg to make MachineChoice.HOST configurable 2220 super().__init__(name, subdir, subproject, False, MachineChoice.HOST) 2221 self.dependencies: T.List[T.Union[CustomTarget, BuildTarget]] = [] 2222 self.extra_depends = [] 2223 self.depend_files = [] # Files that this target depends on but are not on the command line. 2224 self.depfile = None 2225 self.process_kwargs(kwargs, backend) 2226 # Whether to use absolute paths for all files on the commandline 2227 self.absolute_paths = absolute_paths 2228 unknowns = [] 2229 for k in kwargs: 2230 if k not in CustomTarget.known_kwargs: 2231 unknowns.append(k) 2232 if unknowns: 2233 mlog.warning('Unknown keyword arguments in target {}: {}'.format(self.name, ', '.join(unknowns))) 2234 2235 def get_default_install_dir(self, environment): 2236 return None 2237 2238 def __repr__(self): 2239 repr_str = "<{0} {1}: {2}>" 2240 return repr_str.format(self.__class__.__name__, self.get_id(), self.command) 2241 2242 def get_target_dependencies(self): 2243 deps = self.dependencies[:] 2244 deps += self.extra_depends 2245 for c in self.sources: 2246 if isinstance(c, (BuildTarget, CustomTarget)): 2247 deps.append(c) 2248 return deps 2249 2250 def get_transitive_build_target_deps(self): 2251 ''' 2252 Recursively fetch the build targets that this custom target depends on, 2253 whether through `command:`, `depends:`, or `sources:` The recursion is 2254 only performed on custom targets. 2255 This is useful for setting PATH on Windows for finding required DLLs. 2256 F.ex, if you have a python script that loads a C module that links to 2257 other DLLs in your project. 2258 ''' 2259 bdeps = set() 2260 deps = self.get_target_dependencies() 2261 for d in deps: 2262 if isinstance(d, BuildTarget): 2263 bdeps.add(d) 2264 elif isinstance(d, CustomTarget): 2265 bdeps.update(d.get_transitive_build_target_deps()) 2266 return bdeps 2267 2268 def process_kwargs(self, kwargs, backend): 2269 self.process_kwargs_base(kwargs) 2270 self.sources = extract_as_list(kwargs, 'input') 2271 if 'output' not in kwargs: 2272 raise InvalidArguments('Missing keyword argument "output".') 2273 self.outputs = listify(kwargs['output']) 2274 # This will substitute values from the input into output and return it. 2275 inputs = get_sources_string_names(self.sources, backend) 2276 values = get_filenames_templates_dict(inputs, []) 2277 for i in self.outputs: 2278 if not(isinstance(i, str)): 2279 raise InvalidArguments('Output argument not a string.') 2280 if i == '': 2281 raise InvalidArguments('Output must not be empty.') 2282 if i.strip() == '': 2283 raise InvalidArguments('Output must not consist only of whitespace.') 2284 if has_path_sep(i): 2285 raise InvalidArguments(f'Output {i!r} must not contain a path segment.') 2286 if '@INPUT@' in i or '@INPUT0@' in i: 2287 m = 'Output cannot contain @INPUT@ or @INPUT0@, did you ' \ 2288 'mean @PLAINNAME@ or @BASENAME@?' 2289 raise InvalidArguments(m) 2290 # We already check this during substitution, but the error message 2291 # will be unclear/confusing, so check it here. 2292 if len(inputs) != 1 and ('@PLAINNAME@' in i or '@BASENAME@' in i): 2293 m = "Output cannot contain @PLAINNAME@ or @BASENAME@ when " \ 2294 "there is more than one input (we can't know which to use)" 2295 raise InvalidArguments(m) 2296 self.outputs = substitute_values(self.outputs, values) 2297 self.capture = kwargs.get('capture', False) 2298 if self.capture and len(self.outputs) != 1: 2299 raise InvalidArguments('Capturing can only output to a single file.') 2300 self.feed = kwargs.get('feed', False) 2301 if self.feed and len(self.sources) != 1: 2302 raise InvalidArguments('Feeding can only input from a single file.') 2303 self.console = kwargs.get('console', False) 2304 if not isinstance(self.console, bool): 2305 raise InvalidArguments('"console" kwarg only accepts booleans') 2306 if self.capture and self.console: 2307 raise InvalidArguments("Can't both capture output and output to console") 2308 if 'command' not in kwargs: 2309 raise InvalidArguments('Missing keyword argument "command".') 2310 if 'depfile' in kwargs: 2311 depfile = kwargs['depfile'] 2312 if not isinstance(depfile, str): 2313 raise InvalidArguments('Depfile must be a string.') 2314 if os.path.basename(depfile) != depfile: 2315 raise InvalidArguments('Depfile must be a plain filename without a subdirectory.') 2316 self.depfile = depfile 2317 self.command = self.flatten_command(kwargs['command']) 2318 for c in self.command: 2319 if self.capture and isinstance(c, str) and '@OUTPUT@' in c: 2320 raise InvalidArguments('@OUTPUT@ is not allowed when capturing output.') 2321 if self.feed and isinstance(c, str) and '@INPUT@' in c: 2322 raise InvalidArguments('@INPUT@ is not allowed when feeding input.') 2323 if 'install' in kwargs: 2324 self.install = kwargs['install'] 2325 if not isinstance(self.install, bool): 2326 raise InvalidArguments('"install" must be boolean.') 2327 if self.install: 2328 if 'install_dir' not in kwargs: 2329 raise InvalidArguments('"install_dir" must be specified ' 2330 'when installing a target') 2331 2332 if isinstance(kwargs['install_dir'], list): 2333 FeatureNew.single_use('multiple install_dir for custom_target', '0.40.0', self.subproject) 2334 # If an item in this list is False, the output corresponding to 2335 # the list index of that item will not be installed 2336 self.install_dir = typeslistify(kwargs['install_dir'], (str, bool)) 2337 self.install_mode = kwargs.get('install_mode', None) 2338 else: 2339 self.install = False 2340 self.install_dir = [None] 2341 self.install_mode = None 2342 if 'build_always' in kwargs and 'build_always_stale' in kwargs: 2343 raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.') 2344 elif 'build_always' in kwargs: 2345 if 'build_by_default' not in kwargs: 2346 self.build_by_default = kwargs['build_always'] 2347 self.build_always_stale = kwargs['build_always'] 2348 elif 'build_always_stale' in kwargs: 2349 self.build_always_stale = kwargs['build_always_stale'] 2350 if not isinstance(self.build_always_stale, bool): 2351 raise InvalidArguments('Argument build_always_stale must be a boolean.') 2352 extra_deps, depend_files = [extract_as_list(kwargs, c, pop=False) for c in ['depends', 'depend_files']] 2353 for ed in extra_deps: 2354 if not isinstance(ed, (CustomTarget, BuildTarget)): 2355 raise InvalidArguments('Can only depend on toplevel targets: custom_target or build_target ' 2356 f'(executable or a library) got: {type(ed)}({ed})') 2357 self.extra_depends.append(ed) 2358 for i in depend_files: 2359 if isinstance(i, (File, str)): 2360 self.depend_files.append(i) 2361 else: 2362 mlog.debug(i) 2363 raise InvalidArguments(f'Unknown type {type(i).__name__!r} in depend_files.') 2364 self.env = kwargs.get('env') 2365 2366 def get_dependencies(self): 2367 return self.dependencies 2368 2369 def should_install(self) -> bool: 2370 return self.install 2371 2372 def get_custom_install_dir(self): 2373 return self.install_dir 2374 2375 def get_custom_install_mode(self): 2376 return self.install_mode 2377 2378 def get_outputs(self) -> T.List[str]: 2379 return self.outputs 2380 2381 def get_filename(self): 2382 return self.outputs[0] 2383 2384 def get_sources(self): 2385 return self.sources 2386 2387 def get_generated_lists(self): 2388 genlists = [] 2389 for c in self.sources: 2390 if isinstance(c, GeneratedList): 2391 genlists.append(c) 2392 return genlists 2393 2394 def get_generated_sources(self): 2395 return self.get_generated_lists() 2396 2397 def get_dep_outname(self, infilenames): 2398 if self.depfile is None: 2399 raise InvalidArguments('Tried to get depfile name for custom_target that does not have depfile defined.') 2400 if infilenames: 2401 plainname = os.path.basename(infilenames[0]) 2402 basename = os.path.splitext(plainname)[0] 2403 return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) 2404 else: 2405 if '@BASENAME@' in self.depfile or '@PLAINNAME@' in self.depfile: 2406 raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.') 2407 return self.depfile 2408 2409 def is_linkable_target(self): 2410 if len(self.outputs) != 1: 2411 return False 2412 suf = os.path.splitext(self.outputs[0])[-1] 2413 if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so' or suf == '.dylib': 2414 return True 2415 2416 def get_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 2417 return {} 2418 2419 def get_link_dep_subdirs(self): 2420 return OrderedSet() 2421 2422 def get_all_link_deps(self): 2423 return [] 2424 2425 def is_internal(self) -> bool: 2426 if not self.should_install(): 2427 return True 2428 for out in self.get_outputs(): 2429 # Can't check if this is a static library, so try to guess 2430 if not out.endswith(('.a', '.lib')): 2431 return False 2432 return True 2433 2434 def extract_all_objects_recurse(self): 2435 return self.get_outputs() 2436 2437 def type_suffix(self): 2438 return "@cus" 2439 2440 def __getitem__(self, index: int) -> 'CustomTargetIndex': 2441 return CustomTargetIndex(self, self.outputs[index]) 2442 2443 def __setitem__(self, index, value): 2444 raise NotImplementedError 2445 2446 def __delitem__(self, index): 2447 raise NotImplementedError 2448 2449 def __iter__(self): 2450 for i in self.outputs: 2451 yield CustomTargetIndex(self, i) 2452 2453class RunTarget(Target, CommandBase): 2454 def __init__(self, name, command, dependencies, subdir, subproject, env=None): 2455 self.typename = 'run' 2456 # These don't produce output artifacts 2457 super().__init__(name, subdir, subproject, False, MachineChoice.BUILD) 2458 self.dependencies = dependencies 2459 self.depend_files = [] 2460 self.command = self.flatten_command(command) 2461 self.absolute_paths = False 2462 self.env = env 2463 2464 def __repr__(self): 2465 repr_str = "<{0} {1}: {2}>" 2466 return repr_str.format(self.__class__.__name__, self.get_id(), self.command[0]) 2467 2468 def process_kwargs(self, kwargs): 2469 return self.process_kwargs_base(kwargs) 2470 2471 def get_dependencies(self): 2472 return self.dependencies 2473 2474 def get_generated_sources(self): 2475 return [] 2476 2477 def get_sources(self): 2478 return [] 2479 2480 def should_install(self) -> bool: 2481 return False 2482 2483 def get_filename(self) -> str: 2484 return self.name 2485 2486 def get_outputs(self) -> T.List[str]: 2487 if isinstance(self.name, str): 2488 return [self.name] 2489 elif isinstance(self.name, list): 2490 return self.name 2491 else: 2492 raise RuntimeError('RunTarget: self.name is neither a list nor a string. This is a bug') 2493 2494 def type_suffix(self): 2495 return "@run" 2496 2497class AliasTarget(RunTarget): 2498 def __init__(self, name, dependencies, subdir, subproject): 2499 super().__init__(name, [], dependencies, subdir, subproject) 2500 2501class Jar(BuildTarget): 2502 known_kwargs = known_jar_kwargs 2503 2504 def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs): 2505 self.typename = 'jar' 2506 super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) 2507 for s in self.sources: 2508 if not s.endswith('.java'): 2509 raise InvalidArguments(f'Jar source {s} is not a java file.') 2510 for t in self.link_targets: 2511 if not isinstance(t, Jar): 2512 raise InvalidArguments(f'Link target {t} is not a jar target.') 2513 self.filename = self.name + '.jar' 2514 self.outputs = [self.filename] 2515 self.java_args = kwargs.get('java_args', []) 2516 2517 def get_main_class(self): 2518 return self.main_class 2519 2520 def type_suffix(self): 2521 return "@jar" 2522 2523 def get_java_args(self): 2524 return self.java_args 2525 2526 def validate_install(self, environment): 2527 # All jar targets are installable. 2528 pass 2529 2530 def is_linkable_target(self): 2531 return True 2532 2533 def get_classpath_args(self): 2534 cp_paths = [os.path.join(l.get_subdir(), l.get_filename()) for l in self.link_targets] 2535 cp_string = os.pathsep.join(cp_paths) 2536 if cp_string: 2537 return ['-cp', os.pathsep.join(cp_paths)] 2538 return [] 2539 2540class CustomTargetIndex(HoldableObject): 2541 2542 """A special opaque object returned by indexing a CustomTarget. This object 2543 exists in Meson, but acts as a proxy in the backends, making targets depend 2544 on the CustomTarget it's derived from, but only adding one source file to 2545 the sources. 2546 """ 2547 2548 def __init__(self, target: CustomTarget, output: int): 2549 self.typename = 'custom' 2550 self.target = target 2551 self.output = output 2552 self.for_machine = target.for_machine 2553 2554 def __repr__(self): 2555 return '<CustomTargetIndex: {!r}[{}]>'.format( 2556 self.target, self.target.get_outputs().index(self.output)) 2557 2558 def get_outputs(self) -> T.List[str]: 2559 return [self.output] 2560 2561 def get_subdir(self) -> str: 2562 return self.target.get_subdir() 2563 2564 def get_filename(self): 2565 return self.output 2566 2567 def get_id(self): 2568 return self.target.get_id() 2569 2570 def get_all_link_deps(self): 2571 return self.target.get_all_link_deps() 2572 2573 def get_link_deps_mapping(self, prefix: str, environment: environment.Environment) -> T.Mapping[str, str]: 2574 return self.target.get_link_deps_mapping(prefix, environment) 2575 2576 def get_link_dep_subdirs(self): 2577 return self.target.get_link_dep_subdirs() 2578 2579 def is_linkable_target(self): 2580 suf = os.path.splitext(self.output)[-1] 2581 if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so': 2582 return True 2583 2584 def should_install(self) -> bool: 2585 return self.target.should_install() 2586 2587 def is_internal(self) -> bool: 2588 return self.target.is_internal() 2589 2590 def extract_all_objects_recurse(self): 2591 return self.target.extract_all_objects_recurse() 2592 2593 def get_custom_install_dir(self): 2594 return self.target.get_custom_install_dir() 2595 2596class ConfigurationData(HoldableObject): 2597 def __init__(self) -> None: 2598 super().__init__() 2599 self.values: T.Dict[ 2600 str, 2601 T.Tuple[ 2602 T.Union[str, int, bool], 2603 T.Optional[str] 2604 ] 2605 ] = {} 2606 2607 def __repr__(self): 2608 return repr(self.values) 2609 2610 def __contains__(self, value: str) -> bool: 2611 return value in self.values 2612 2613 def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: 2614 return self.values[name] # (val, desc) 2615 2616 def keys(self) -> T.Iterator[str]: 2617 return self.values.keys() 2618 2619# A bit poorly named, but this represents plain data files to copy 2620# during install. 2621class Data(HoldableObject): 2622 def __init__(self, sources: T.List[File], install_dir: str, 2623 install_mode: 'FileMode', subproject: str, 2624 rename: T.List[str] = None): 2625 self.sources = sources 2626 self.install_dir = install_dir 2627 self.install_mode = install_mode 2628 if rename is None: 2629 self.rename = [os.path.basename(f.fname) for f in self.sources] 2630 else: 2631 self.rename = rename 2632 self.subproject = subproject 2633 2634class TestSetup: 2635 def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, 2636 timeout_multiplier: int, env: EnvironmentVariables, 2637 exclude_suites: T.List[str]): 2638 self.exe_wrapper = exe_wrapper 2639 self.gdb = gdb 2640 self.timeout_multiplier = timeout_multiplier 2641 self.env = env 2642 self.exclude_suites = exclude_suites 2643 2644def get_sources_string_names(sources, backend): 2645 ''' 2646 For the specified list of @sources which can be strings, Files, or targets, 2647 get all the output basenames. 2648 ''' 2649 names = [] 2650 for s in sources: 2651 if isinstance(s, str): 2652 names.append(s) 2653 elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)): 2654 names += s.get_outputs() 2655 elif isinstance(s, ExtractedObjects): 2656 names += s.get_outputs(backend) 2657 elif isinstance(s, File): 2658 names.append(s.fname) 2659 else: 2660 raise AssertionError(f'Unknown source type: {s!r}') 2661 return names 2662 2663def load(build_dir: str) -> Build: 2664 filename = os.path.join(build_dir, 'meson-private', 'build.dat') 2665 load_fail_msg = f'Build data file {filename!r} is corrupted. Try with a fresh build tree.' 2666 nonexisting_fail_msg = f'No such build data file as "{filename!r}".' 2667 try: 2668 with open(filename, 'rb') as f: 2669 obj = pickle.load(f) 2670 except FileNotFoundError: 2671 raise MesonException(nonexisting_fail_msg) 2672 except (pickle.UnpicklingError, EOFError): 2673 raise MesonException(load_fail_msg) 2674 except AttributeError: 2675 raise MesonException( 2676 f"Build data file {filename!r} references functions or classes that don't " 2677 "exist. This probably means that it was generated with an old " 2678 "version of meson. Try running from the source directory " 2679 f"meson {build_dir} --wipe") 2680 if not isinstance(obj, Build): 2681 raise MesonException(load_fail_msg) 2682 return obj 2683 2684def save(obj: Build, filename: str) -> None: 2685 with open(filename, 'wb') as f: 2686 pickle.dump(obj, f) 2687