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