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