1# Copyright 2012-2016 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 17from itertools import chain 18from pathlib import Path 19import enum 20import json 21import os 22import pickle 23import re 24import typing as T 25import hashlib 26 27from .. import build 28from .. import dependencies 29from .. import programs 30from .. import mesonlib 31from .. import mlog 32from ..compilers import LANGUAGES_USING_LDFLAGS, detect 33from ..mesonlib import ( 34 File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy, 35 classify_unity_sources, OptionKey, join_args 36) 37 38if T.TYPE_CHECKING: 39 from .._typing import ImmutableListProtocol 40 from ..arglist import CompilerArgs 41 from ..compilers import Compiler 42 from ..environment import Environment 43 from ..interpreter import Interpreter, Test 44 from ..linkers import StaticLinker 45 from ..mesonlib import FileMode, FileOrString 46 from ..wrap import WrapMode 47 48 from typing_extensions import TypedDict 49 50 class TargetIntrospectionData(TypedDict): 51 52 language: str 53 compiler : T.List[str] 54 parameters: T.List[str] 55 sources: T.List[str] 56 generated_sources: T.List[str] 57 58 59# Languages that can mix with C or C++ but don't support unity builds yet 60# because the syntax we use for unity builds is specific to C/++/ObjC/++. 61# Assembly files cannot be unitified and neither can LLVM IR files 62LANGS_CANT_UNITY = ('d', 'fortran', 'vala') 63 64class RegenInfo: 65 def __init__(self, source_dir: str, build_dir: str, depfiles: T.List[str]): 66 self.source_dir = source_dir 67 self.build_dir = build_dir 68 self.depfiles = depfiles 69 70class TestProtocol(enum.Enum): 71 72 EXITCODE = 0 73 TAP = 1 74 GTEST = 2 75 RUST = 3 76 77 @classmethod 78 def from_str(cls, string: str) -> 'TestProtocol': 79 if string == 'exitcode': 80 return cls.EXITCODE 81 elif string == 'tap': 82 return cls.TAP 83 elif string == 'gtest': 84 return cls.GTEST 85 elif string == 'rust': 86 return cls.RUST 87 raise MesonException(f'unknown test format {string}') 88 89 def __str__(self) -> str: 90 cls = type(self) 91 if self is cls.EXITCODE: 92 return 'exitcode' 93 elif self is cls.GTEST: 94 return 'gtest' 95 elif self is cls.RUST: 96 return 'rust' 97 return 'tap' 98 99 100class CleanTrees: 101 ''' 102 Directories outputted by custom targets that have to be manually cleaned 103 because on Linux `ninja clean` only deletes empty directories. 104 ''' 105 def __init__(self, build_dir: str, trees: T.List[str]): 106 self.build_dir = build_dir 107 self.trees = trees 108 109class InstallData: 110 def __init__(self, source_dir: str, build_dir: str, prefix: str, 111 strip_bin: T.List[str], install_umask: T.Union[str, int], 112 mesonintrospect: T.List[str], version: str): 113 # TODO: in python 3.8 or with typing_Extensions install_umask could be: 114 # `T.Union[T.Literal['preserve'], int]`, which would be more accurate. 115 self.source_dir = source_dir 116 self.build_dir = build_dir 117 self.prefix = prefix 118 self.strip_bin = strip_bin 119 self.install_umask = install_umask 120 self.targets: T.List[TargetInstallData] = [] 121 self.headers: T.List[InstallDataBase] = [] 122 self.man: T.List[InstallDataBase] = [] 123 self.emptydir: T.List[InstallEmptyDir] = [] 124 self.data: T.List[InstallDataBase] = [] 125 self.install_scripts: T.List[ExecutableSerialisation] = [] 126 self.install_subdirs: T.List[SubdirInstallData] = [] 127 self.mesonintrospect = mesonintrospect 128 self.version = version 129 130class TargetInstallData: 131 132 # TODO: install_mode should just always be a FileMode object 133 134 def __init__(self, fname: str, outdir: str, outdir_name: str, aliases: T.Dict[str, str], 135 strip: bool, install_name_mappings: T.Mapping[str, str], rpath_dirs_to_remove: T.Set[bytes], 136 install_rpath: str, install_mode: T.Optional['FileMode'], 137 subproject: str, optional: bool = False, tag: T.Optional[str] = None): 138 self.fname = fname 139 self.outdir = outdir 140 self.out_name = os.path.join(outdir_name, os.path.basename(fname)) 141 self.aliases = aliases 142 self.strip = strip 143 self.install_name_mappings = install_name_mappings 144 self.rpath_dirs_to_remove = rpath_dirs_to_remove 145 self.install_rpath = install_rpath 146 self.install_mode = install_mode 147 self.subproject = subproject 148 self.optional = optional 149 self.tag = tag 150 151class InstallEmptyDir: 152 def __init__(self, path: str, install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None): 153 self.path = path 154 self.install_mode = install_mode 155 self.subproject = subproject 156 self.tag = tag 157 158class InstallDataBase: 159 def __init__(self, path: str, install_path: str, install_path_name: str, 160 install_mode: 'FileMode', subproject: str, tag: T.Optional[str] = None, 161 data_type: T.Optional[str] = None): 162 self.path = path 163 self.install_path = install_path 164 self.install_path_name = install_path_name 165 self.install_mode = install_mode 166 self.subproject = subproject 167 self.tag = tag 168 self.data_type = data_type 169 170class SubdirInstallData(InstallDataBase): 171 def __init__(self, path: str, install_path: str, install_path_name: str, 172 install_mode: 'FileMode', exclude: T.Tuple[T.Set[str], T.Set[str]], 173 subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None): 174 super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type) 175 self.exclude = exclude 176 177class ExecutableSerialisation: 178 179 # XXX: should capture and feed default to False, instead of None? 180 181 def __init__(self, cmd_args: T.List[str], 182 env: T.Optional[build.EnvironmentVariables] = None, 183 exe_wrapper: T.Optional['programs.ExternalProgram'] = None, 184 workdir: T.Optional[str] = None, 185 extra_paths: T.Optional[T.List] = None, 186 capture: T.Optional[bool] = None, 187 feed: T.Optional[bool] = None, 188 tag: T.Optional[str] = None, 189 verbose: bool = False, 190 ) -> None: 191 self.cmd_args = cmd_args 192 self.env = env 193 if exe_wrapper is not None: 194 assert isinstance(exe_wrapper, programs.ExternalProgram) 195 self.exe_runner = exe_wrapper 196 self.workdir = workdir 197 self.extra_paths = extra_paths 198 self.capture = capture 199 self.feed = feed 200 self.pickled = False 201 self.skip_if_destdir = False 202 self.verbose = verbose 203 self.subproject = '' 204 self.tag = tag 205 206class TestSerialisation: 207 def __init__(self, name: str, project: str, suite: T.List[str], fname: T.List[str], 208 is_cross_built: bool, exe_wrapper: T.Optional[programs.ExternalProgram], 209 needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str], 210 env: build.EnvironmentVariables, should_fail: bool, 211 timeout: T.Optional[int], workdir: T.Optional[str], 212 extra_paths: T.List[str], protocol: TestProtocol, priority: int, 213 cmd_is_built: bool, depends: T.List[str], version: str): 214 self.name = name 215 self.project_name = project 216 self.suite = suite 217 self.fname = fname 218 self.is_cross_built = is_cross_built 219 if exe_wrapper is not None: 220 assert isinstance(exe_wrapper, programs.ExternalProgram) 221 self.exe_runner = exe_wrapper 222 self.is_parallel = is_parallel 223 self.cmd_args = cmd_args 224 self.env = env 225 self.should_fail = should_fail 226 self.timeout = timeout 227 self.workdir = workdir 228 self.extra_paths = extra_paths 229 self.protocol = protocol 230 self.priority = priority 231 self.needs_exe_wrapper = needs_exe_wrapper 232 self.cmd_is_built = cmd_is_built 233 self.depends = depends 234 self.version = version 235 236 237def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: 238 if backend == 'ninja': 239 from . import ninjabackend 240 return ninjabackend.NinjaBackend(build, interpreter) 241 elif backend == 'vs': 242 from . import vs2010backend 243 return vs2010backend.autodetect_vs_version(build, interpreter) 244 elif backend == 'vs2010': 245 from . import vs2010backend 246 return vs2010backend.Vs2010Backend(build, interpreter) 247 elif backend == 'vs2012': 248 from . import vs2012backend 249 return vs2012backend.Vs2012Backend(build, interpreter) 250 elif backend == 'vs2013': 251 from . import vs2013backend 252 return vs2013backend.Vs2013Backend(build, interpreter) 253 elif backend == 'vs2015': 254 from . import vs2015backend 255 return vs2015backend.Vs2015Backend(build, interpreter) 256 elif backend == 'vs2017': 257 from . import vs2017backend 258 return vs2017backend.Vs2017Backend(build, interpreter) 259 elif backend == 'vs2019': 260 from . import vs2019backend 261 return vs2019backend.Vs2019Backend(build, interpreter) 262 elif backend == 'xcode': 263 from . import xcodebackend 264 return xcodebackend.XCodeBackend(build, interpreter) 265 return None 266 267# This class contains the basic functionality that is needed by all backends. 268# Feel free to move stuff in and out of it as you see fit. 269class Backend: 270 271 environment: T.Optional['Environment'] 272 273 def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']): 274 # Make it possible to construct a dummy backend 275 # This is used for introspection without a build directory 276 if build is None: 277 self.environment = None 278 return 279 self.build = build 280 self.interpreter = interpreter 281 self.environment = build.environment 282 self.processed_targets: T.Set[str] = set() 283 self.name = '<UNKNOWN>' 284 self.build_dir = self.environment.get_build_dir() 285 self.source_dir = self.environment.get_source_dir() 286 self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(), 287 self.environment.get_build_dir()) 288 self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(), 289 self.environment.get_source_dir()) 290 291 def generate(self) -> None: 292 raise RuntimeError(f'generate is not implemented in {type(self).__name__}') 293 294 def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str: 295 if isinstance(t, build.CustomTarget): 296 if warn_multi_output and len(t.get_outputs()) != 1: 297 mlog.warning(f'custom_target {t.name!r} has more than one output! ' 298 'Using the first one.') 299 filename = t.get_outputs()[0] 300 elif isinstance(t, build.CustomTargetIndex): 301 filename = t.get_outputs()[0] 302 else: 303 assert isinstance(t, build.BuildTarget) 304 filename = t.get_filename() 305 return os.path.join(self.get_target_dir(t), filename) 306 307 def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: 308 return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) 309 310 def get_base_options_for_target(self, target: build.BuildTarget) -> OptionOverrideProxy: 311 return OptionOverrideProxy(target.option_overrides_base, 312 {k: v for k, v in self.environment.coredata.options.items() 313 if k.type in {OptionType.BASE, OptionType.BUILTIN}}) 314 315 def get_compiler_options_for_target(self, target: build.BuildTarget) -> OptionOverrideProxy: 316 comp_reg = {k: v for k, v in self.environment.coredata.options.items() if k.is_compiler()} 317 comp_override = target.option_overrides_compiler 318 return OptionOverrideProxy(comp_override, comp_reg) 319 320 def get_option_for_target(self, option_name: 'OptionKey', target: build.BuildTarget) -> T.Union[str, int, bool, 'WrapMode']: 321 if option_name in target.option_overrides_base: 322 override = target.option_overrides_base[option_name] 323 v = self.environment.coredata.validate_option_value(option_name, override) 324 else: 325 v = self.environment.coredata.get_option(option_name.evolve(subproject=target.subproject)) 326 # We don't actually have wrapmode here to do an assert, so just do a 327 # cast, we know what's in coredata anyway. 328 # TODO: if it's possible to annotate get_option or validate_option_value 329 # in the future we might be able to remove the cast here 330 return T.cast(T.Union[str, int, bool, 'WrapMode'], v) 331 332 def get_source_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: 333 curdir = target.get_subdir() 334 if absolute_path: 335 lead = self.source_dir 336 else: 337 lead = self.build_to_src 338 tmppath = os.path.normpath(os.path.join(lead, curdir)) 339 return compiler.get_include_args(tmppath, False) 340 341 def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]: 342 if absolute_path: 343 curdir = os.path.join(self.build_dir, target.get_subdir()) 344 else: 345 curdir = target.get_subdir() 346 if curdir == '': 347 curdir = '.' 348 return compiler.get_include_args(curdir, False) 349 350 def get_target_filename_for_linking(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> T.Optional[str]: 351 # On some platforms (msvc for instance), the file that is used for 352 # dynamic linking is not the same as the dynamic library itself. This 353 # file is called an import library, and we want to link against that. 354 # On all other platforms, we link to the library directly. 355 if isinstance(target, build.SharedLibrary): 356 link_lib = target.get_import_filename() or target.get_filename() 357 return os.path.join(self.get_target_dir(target), link_lib) 358 elif isinstance(target, build.StaticLibrary): 359 return os.path.join(self.get_target_dir(target), target.get_filename()) 360 elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)): 361 if not target.is_linkable_target(): 362 raise MesonException(f'Tried to link against custom target "{target.name}", which is not linkable.') 363 return os.path.join(self.get_target_dir(target), target.get_filename()) 364 elif isinstance(target, build.Executable): 365 if target.import_filename: 366 return os.path.join(self.get_target_dir(target), target.get_import_filename()) 367 else: 368 return None 369 raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable') 370 371 @lru_cache(maxsize=None) 372 def get_target_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: 373 if isinstance(target, build.RunTarget): 374 # this produces no output, only a dummy top-level name 375 dirname = '' 376 elif self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': 377 dirname = target.get_subdir() 378 else: 379 dirname = 'meson-out' 380 return dirname 381 382 def get_target_dir_relative_to(self, t: build.Target, o: build.Target) -> str: 383 '''Get a target dir relative to another target's directory''' 384 target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) 385 othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o)) 386 return os.path.relpath(target_dir, othert_dir) 387 388 def get_target_source_dir(self, target: build.Target) -> str: 389 # if target dir is empty, avoid extraneous trailing / from os.path.join() 390 target_dir = self.get_target_dir(target) 391 if target_dir: 392 return os.path.join(self.build_to_src, target_dir) 393 return self.build_to_src 394 395 def get_target_private_dir(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: 396 return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') 397 398 def get_target_private_dir_abs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: 399 return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) 400 401 @lru_cache(maxsize=None) 402 def get_target_generated_dir( 403 self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], 404 gensrc: T.Union[build.CustomTarget, build.CustomTargetIndex, build.GeneratedList], 405 src: str) -> str: 406 """ 407 Takes a BuildTarget, a generator source (CustomTarget or GeneratedList), 408 and a generated source filename. 409 Returns the full path of the generated source relative to the build root 410 """ 411 # CustomTarget generators output to the build dir of the CustomTarget 412 if isinstance(gensrc, (build.CustomTarget, build.CustomTargetIndex)): 413 return os.path.join(self.get_target_dir(gensrc), src) 414 # GeneratedList generators output to the private build directory of the 415 # target that the GeneratedList is used in 416 return os.path.join(self.get_target_private_dir(target), src) 417 418 def get_unity_source_file(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], 419 suffix: str, number: int) -> mesonlib.File: 420 # There is a potential conflict here, but it is unlikely that 421 # anyone both enables unity builds and has a file called foo-unity.cpp. 422 osrc = f'{target.name}-unity{number}.{suffix}' 423 return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc) 424 425 def generate_unity_files(self, target: build.BuildTarget, unity_src: str) -> T.List[mesonlib.File]: 426 abs_files: T.List[str] = [] 427 result: T.List[mesonlib.File] = [] 428 compsrcs = classify_unity_sources(target.compilers.values(), unity_src) 429 unity_size = self.get_option_for_target(OptionKey('unity_size'), target) 430 assert isinstance(unity_size, int), 'for mypy' 431 432 def init_language_file(suffix: str, unity_file_number: int) -> T.TextIO: 433 unity_src = self.get_unity_source_file(target, suffix, unity_file_number) 434 outfileabs = unity_src.absolute_path(self.environment.get_source_dir(), 435 self.environment.get_build_dir()) 436 outfileabs_tmp = outfileabs + '.tmp' 437 abs_files.append(outfileabs) 438 outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp) 439 if not os.path.exists(outfileabs_tmp_dir): 440 os.makedirs(outfileabs_tmp_dir) 441 result.append(unity_src) 442 return open(outfileabs_tmp, 'w', encoding='utf-8') 443 444 # For each language, generate unity source files and return the list 445 for comp, srcs in compsrcs.items(): 446 files_in_current = unity_size + 1 447 unity_file_number = 0 448 # TODO: this could be simplified with an algorithm that pre-sorts 449 # the sources into the size of chunks we want 450 ofile = None 451 for src in srcs: 452 if files_in_current >= unity_size: 453 if ofile: 454 ofile.close() 455 ofile = init_language_file(comp.get_default_suffix(), unity_file_number) 456 unity_file_number += 1 457 files_in_current = 0 458 ofile.write(f'#include<{src}>\n') 459 files_in_current += 1 460 if ofile: 461 ofile.close() 462 463 for x in abs_files: 464 mesonlib.replace_if_different(x, x + '.tmp') 465 return result 466 467 @staticmethod 468 def relpath(todir: str, fromdir: str) -> str: 469 return os.path.relpath(os.path.join('dummyprefixdir', todir), 470 os.path.join('dummyprefixdir', fromdir)) 471 472 def flatten_object_list(self, target: build.BuildTarget, proj_dir_to_build_root: str = '') -> T.List[str]: 473 obj_list = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root) 474 return list(dict.fromkeys(obj_list)) 475 476 def _flatten_object_list(self, target: build.BuildTarget, 477 objects: T.Sequence[T.Union[str, 'File', build.ExtractedObjects]], 478 proj_dir_to_build_root: str) -> T.List[str]: 479 obj_list: T.List[str] = [] 480 for obj in objects: 481 if isinstance(obj, str): 482 o = os.path.join(proj_dir_to_build_root, 483 self.build_to_src, target.get_subdir(), obj) 484 obj_list.append(o) 485 elif isinstance(obj, mesonlib.File): 486 if obj.is_built: 487 o = os.path.join(proj_dir_to_build_root, 488 obj.rel_to_builddir(self.build_to_src)) 489 obj_list.append(o) 490 else: 491 o = os.path.join(proj_dir_to_build_root, 492 self.build_to_src) 493 obj_list.append(obj.rel_to_builddir(o)) 494 elif isinstance(obj, build.ExtractedObjects): 495 if obj.recursive: 496 obj_list += self._flatten_object_list(obj.target, obj.objlist, proj_dir_to_build_root) 497 obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) 498 else: 499 raise MesonException('Unknown data type in object list.') 500 return obj_list 501 502 @staticmethod 503 def is_swift_target(target: build.BuildTarget) -> bool: 504 for s in target.sources: 505 if s.endswith('swift'): 506 return True 507 return False 508 509 def determine_swift_dep_dirs(self, target: build.BuildTarget) -> T.List[str]: 510 result: T.List[str] = [] 511 for l in target.link_targets: 512 result.append(self.get_target_private_dir_abs(l)) 513 return result 514 515 def get_executable_serialisation( 516 self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]], 517 workdir: T.Optional[str] = None, 518 extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, 519 capture: T.Optional[bool] = None, 520 feed: T.Optional[bool] = None, 521 env: T.Optional[build.EnvironmentVariables] = None, 522 tag: T.Optional[str] = None, 523 verbose: bool = False) -> 'ExecutableSerialisation': 524 525 # XXX: cmd_args either need to be lowered to strings, or need to be checked for non-string arguments, right? 526 exe, *raw_cmd_args = cmd 527 if isinstance(exe, programs.ExternalProgram): 528 exe_cmd = exe.get_command() 529 exe_for_machine = exe.for_machine 530 elif isinstance(exe, build.BuildTarget): 531 exe_cmd = [self.get_target_filename_abs(exe)] 532 exe_for_machine = exe.for_machine 533 elif isinstance(exe, build.CustomTarget): 534 # The output of a custom target can either be directly runnable 535 # or not, that is, a script, a native binary or a cross compiled 536 # binary when exe wrapper is available and when it is not. 537 # This implementation is not exhaustive but it works in the 538 # common cases. 539 exe_cmd = [self.get_target_filename_abs(exe)] 540 exe_for_machine = MachineChoice.BUILD 541 elif isinstance(exe, mesonlib.File): 542 exe_cmd = [exe.rel_to_builddir(self.environment.source_dir)] 543 exe_for_machine = MachineChoice.BUILD 544 else: 545 exe_cmd = [exe] 546 exe_for_machine = MachineChoice.BUILD 547 548 cmd_args: T.List[str] = [] 549 for c in raw_cmd_args: 550 if isinstance(c, programs.ExternalProgram): 551 p = c.get_path() 552 assert isinstance(p, str) 553 cmd_args.append(p) 554 elif isinstance(c, (build.BuildTarget, build.CustomTarget)): 555 cmd_args.append(self.get_target_filename_abs(c)) 556 elif isinstance(c, mesonlib.File): 557 cmd_args.append(c.rel_to_builddir(self.environment.source_dir)) 558 else: 559 cmd_args.append(c) 560 561 machine = self.environment.machines[exe_for_machine] 562 if machine.is_windows() or machine.is_cygwin(): 563 extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or []) 564 else: 565 extra_paths = [] 566 567 is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) 568 if is_cross_built and self.environment.need_exe_wrapper(): 569 exe_wrapper = self.environment.get_exe_wrapper() 570 if not exe_wrapper or not exe_wrapper.found(): 571 msg = 'An exe_wrapper is needed but was not found. Please define one ' \ 572 'in cross file and check the command and/or add it to PATH.' 573 raise MesonException(msg) 574 else: 575 if exe_cmd[0].endswith('.jar'): 576 exe_cmd = ['java', '-jar'] + exe_cmd 577 elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl()): 578 exe_cmd = ['mono'] + exe_cmd 579 exe_wrapper = None 580 581 workdir = workdir or self.environment.get_build_dir() 582 return ExecutableSerialisation(exe_cmd + cmd_args, env, 583 exe_wrapper, workdir, 584 extra_paths, capture, feed, tag, verbose) 585 586 def as_meson_exe_cmdline(self, exe: T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram], 587 cmd_args: T.Sequence[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]], 588 workdir: T.Optional[str] = None, 589 extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None, 590 capture: T.Optional[bool] = None, 591 feed: T.Optional[bool] = None, 592 force_serialize: bool = False, 593 env: T.Optional[build.EnvironmentVariables] = None, 594 verbose: bool = False) -> T.Tuple[T.Sequence[T.Union[str, File, build.Target, programs.ExternalProgram]], str]: 595 ''' 596 Serialize an executable for running with a generator or a custom target 597 ''' 598 cmd: T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]] = [] 599 cmd.append(exe) 600 cmd.extend(cmd_args) 601 es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env, verbose=verbose) 602 reasons: T.List[str] = [] 603 if es.extra_paths: 604 reasons.append('to set PATH') 605 606 if es.exe_runner: 607 reasons.append('to use exe_wrapper') 608 609 if workdir: 610 reasons.append('to set workdir') 611 612 if any('\n' in c for c in es.cmd_args): 613 reasons.append('because command contains newlines') 614 615 if es.env and es.env.varnames: 616 reasons.append('to set env') 617 618 force_serialize = force_serialize or bool(reasons) 619 620 if capture: 621 reasons.append('to capture output') 622 if feed: 623 reasons.append('to feed input') 624 625 if not force_serialize: 626 if not capture and not feed: 627 return es.cmd_args, '' 628 args: T.List[str] = [] 629 if capture: 630 args += ['--capture', str(capture)] 631 if feed: 632 args += ['--feed', str(feed)] 633 634 return ( 635 self.environment.get_build_command() + ['--internal', 'exe'] + args + ['--'] + es.cmd_args, 636 ', '.join(reasons) 637 ) 638 639 if isinstance(exe, (programs.ExternalProgram, 640 build.BuildTarget, build.CustomTarget)): 641 basename = exe.name 642 elif isinstance(exe, mesonlib.File): 643 basename = os.path.basename(exe.fname) 644 else: 645 basename = os.path.basename(exe) 646 647 # Can't just use exe.name here; it will likely be run more than once 648 # Take a digest of the cmd args, env, workdir, capture, and feed. This 649 # avoids collisions and also makes the name deterministic over 650 # regenerations which avoids a rebuild by Ninja because the cmdline 651 # stays the same. 652 data = bytes(str(es.env) + str(es.cmd_args) + str(es.workdir) + str(capture) + str(feed), 653 encoding='utf-8') 654 digest = hashlib.sha1(data).hexdigest() 655 scratch_file = f'meson_exe_{basename}_{digest}.dat' 656 exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) 657 with open(exe_data, 'wb') as f: 658 pickle.dump(es, f) 659 return (self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data], 660 ', '.join(reasons)) 661 662 def serialize_tests(self) -> T.Tuple[str, str]: 663 test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') 664 with open(test_data, 'wb') as datafile: 665 self.write_test_file(datafile) 666 benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') 667 with open(benchmark_data, 'wb') as datafile: 668 self.write_benchmark_file(datafile) 669 return test_data, benchmark_data 670 671 def determine_linker_and_stdlib_args(self, target: build.BuildTarget) -> T.Tuple[T.Union['Compiler', 'StaticLinker'], T.List[str]]: 672 ''' 673 If we're building a static library, there is only one static linker. 674 Otherwise, we query the target for the dynamic linker. 675 ''' 676 if isinstance(target, build.StaticLibrary): 677 return self.build.static_linker[target.for_machine], [] 678 l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs() 679 return l, stdlib_args 680 681 @staticmethod 682 def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool: 683 libdir = os.path.normpath(libdir) 684 for cc in compilers.values(): 685 if libdir in cc.get_library_dirs(env): 686 return True 687 return False 688 689 def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]: 690 dirs: T.Set[str] = set() 691 args: T.List[str] = [] 692 for lang in LANGUAGES_USING_LDFLAGS: 693 try: 694 e = self.environment.coredata.get_external_link_args(target.for_machine, lang) 695 if isinstance(e, str): 696 args.append(e) 697 else: 698 args.extend(e) 699 except Exception: 700 pass 701 # Match rpath formats: 702 # -Wl,-rpath= 703 # -Wl,-rpath, 704 rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') 705 # Match solaris style compat runpath formats: 706 # -Wl,-R 707 # -Wl,-R, 708 runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') 709 # Match symbols formats: 710 # -Wl,--just-symbols= 711 # -Wl,--just-symbols, 712 symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') 713 for arg in args: 714 rpath_match = rpath_regex.match(arg) 715 if rpath_match: 716 for dir in rpath_match.group(1).split(':'): 717 dirs.add(dir) 718 runpath_match = runpath_regex.match(arg) 719 if runpath_match: 720 for dir in runpath_match.group(1).split(':'): 721 # The symbols arg is an rpath if the path is a directory 722 if Path(dir).is_dir(): 723 dirs.add(dir) 724 symbols_match = symbols_regex.match(arg) 725 if symbols_match: 726 for dir in symbols_match.group(1).split(':'): 727 # Prevent usage of --just-symbols to specify rpath 728 if Path(dir).is_dir(): 729 raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.') 730 return dirs 731 732 @lru_cache(maxsize=None) 733 def rpaths_for_bundled_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]': 734 paths: T.List[str] = [] 735 for dep in target.external_deps: 736 if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)): 737 continue 738 la = dep.get_link_args(raw=True) 739 if len(la) != 1 or not os.path.isabs(la[0]): 740 continue 741 # The only link argument is an absolute path to a library file. 742 libpath = la[0] 743 libdir = os.path.dirname(libpath) 744 if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): 745 # No point in adding system paths. 746 continue 747 # Don't remove rpaths specified in LDFLAGS. 748 if libdir in self.get_external_rpath_dirs(target): 749 continue 750 # Windows doesn't support rpaths, but we use this function to 751 # emulate rpaths by setting PATH, so also accept DLLs here 752 if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']: 753 continue 754 if libdir.startswith(self.environment.get_source_dir()): 755 rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:] 756 assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' 757 paths.append(os.path.join(self.build_to_src, rel_to_src)) 758 else: 759 paths.append(libdir) 760 for i in chain(target.link_targets, target.link_whole_targets): 761 if isinstance(i, build.BuildTarget): 762 paths.extend(self.rpaths_for_bundled_shared_libraries(i, exclude_system)) 763 return paths 764 765 # This may take other types 766 def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex] 767 ) -> T.Tuple[str, ...]: 768 result: OrderedSet[str] 769 if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': 770 # Need a copy here 771 result = OrderedSet(target.get_link_dep_subdirs()) 772 else: 773 result = OrderedSet() 774 result.add('meson-out') 775 if isinstance(target, build.BuildTarget): 776 result.update(self.rpaths_for_bundled_shared_libraries(target)) 777 target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) 778 return tuple(result) 779 780 @staticmethod 781 def canonicalize_filename(fname: str) -> str: 782 for ch in ('/', '\\', ':'): 783 fname = fname.replace(ch, '_') 784 return fname 785 786 def object_filename_from_source(self, target: build.BuildTarget, source: 'FileOrString') -> str: 787 assert isinstance(source, mesonlib.File) 788 build_dir = self.environment.get_build_dir() 789 rel_src = source.rel_to_builddir(self.build_to_src) 790 791 # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o 792 if rel_src.endswith(('.vala', '.gs')): 793 # See description in generate_vala_compile for this logic. 794 if source.is_built: 795 if os.path.isabs(rel_src): 796 rel_src = rel_src[len(build_dir) + 1:] 797 rel_src = os.path.relpath(rel_src, self.get_target_private_dir(target)) 798 else: 799 rel_src = os.path.basename(rel_src) 800 # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. 801 gen_source = 'meson-generated_' + rel_src[:-5] + '.c' 802 elif source.is_built: 803 if os.path.isabs(rel_src): 804 rel_src = rel_src[len(build_dir) + 1:] 805 targetdir = self.get_target_private_dir(target) 806 # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. 807 gen_source = 'meson-generated_' + os.path.relpath(rel_src, targetdir) 808 else: 809 if os.path.isabs(rel_src): 810 # Use the absolute path directly to avoid file name conflicts 811 gen_source = rel_src 812 else: 813 gen_source = os.path.relpath(os.path.join(build_dir, rel_src), 814 os.path.join(self.environment.get_source_dir(), target.get_subdir())) 815 machine = self.environment.machines[target.for_machine] 816 return self.canonicalize_filename(gen_source) + '.' + machine.get_object_suffix() 817 818 def determine_ext_objs(self, extobj: 'build.ExtractedObjects', proj_dir_to_build_root: str) -> T.List[str]: 819 result: T.List[str] = [] 820 821 # Merge sources and generated sources 822 raw_sources = list(extobj.srclist) 823 for gensrc in extobj.genlist: 824 for r in gensrc.get_outputs(): 825 path = self.get_target_generated_dir(extobj.target, gensrc, r) 826 dirpart, fnamepart = os.path.split(path) 827 raw_sources.append(File(True, dirpart, fnamepart)) 828 829 # Filter out headers and all non-source files 830 sources: T.List['FileOrString'] = [] 831 for s in raw_sources: 832 if self.environment.is_source(s) and not self.environment.is_header(s): 833 sources.append(s) 834 elif self.environment.is_object(s): 835 result.append(s.relative_name()) 836 837 # extobj could contain only objects and no sources 838 if not sources: 839 return result 840 841 targetdir = self.get_target_private_dir(extobj.target) 842 843 # With unity builds, sources don't map directly to objects, 844 # we only support extracting all the objects in this mode, 845 # so just return all object files. 846 if self.is_unity(extobj.target): 847 compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources) 848 sources = [] 849 unity_size = self.get_option_for_target(OptionKey('unity_size'), extobj.target) 850 assert isinstance(unity_size, int), 'for mypy' 851 852 for comp, srcs in compsrcs.items(): 853 if comp.language in LANGS_CANT_UNITY: 854 sources += srcs 855 continue 856 for i in range(len(srcs) // unity_size + 1): 857 _src = self.get_unity_source_file(extobj.target, 858 comp.get_default_suffix(), i) 859 sources.append(_src) 860 861 for osrc in sources: 862 objname = self.object_filename_from_source(extobj.target, osrc) 863 objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) 864 result.append(objpath) 865 866 return result 867 868 def get_pch_include_args(self, compiler: 'Compiler', target: build.BuildTarget) -> T.List[str]: 869 args: T.List[str] = [] 870 pchpath = self.get_target_private_dir(target) 871 includeargs = compiler.get_include_args(pchpath, False) 872 p = target.get_pch(compiler.get_language()) 873 if p: 874 args += compiler.get_pch_use_args(pchpath, p[0]) 875 return includeargs + args 876 877 def create_msvc_pch_implementation(self, target: build.BuildTarget, lang: str, pch_header: str) -> str: 878 # We have to include the language in the file name, otherwise 879 # pch.c and pch.cpp will both end up as pch.obj in VS backends. 880 impl_name = f'meson_pch-{lang}.{lang}' 881 pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name) 882 # Make sure to prepend the build dir, since the working directory is 883 # not defined. Otherwise, we might create the file in the wrong path. 884 pch_file = os.path.join(self.build_dir, pch_rel_to_build) 885 os.makedirs(os.path.dirname(pch_file), exist_ok=True) 886 887 content = f'#include "{os.path.basename(pch_header)}"' 888 pch_file_tmp = pch_file + '.tmp' 889 with open(pch_file_tmp, 'w', encoding='utf-8') as f: 890 f.write(content) 891 mesonlib.replace_if_different(pch_file, pch_file_tmp) 892 return pch_rel_to_build 893 894 @staticmethod 895 def escape_extra_args(args: T.List[str]) -> T.List[str]: 896 # all backslashes in defines are doubly-escaped 897 extra_args: T.List[str] = [] 898 for arg in args: 899 if arg.startswith(('-D', '/D')): 900 arg = arg.replace('\\', '\\\\') 901 extra_args.append(arg) 902 903 return extra_args 904 905 def get_no_stdlib_args(self, target: 'build.BuildTarget', compiler: 'Compiler') -> T.List[str]: 906 if compiler.language in self.build.stdlibs[target.for_machine]: 907 return compiler.get_no_stdinc_args() 908 return [] 909 910 def generate_basic_compiler_args(self, target: build.BuildTarget, compiler: 'Compiler', no_warn_args: bool = False) -> 'CompilerArgs': 911 # Create an empty commands list, and start adding arguments from 912 # various sources in the order in which they must override each other 913 # starting from hard-coded defaults followed by build options and so on. 914 commands = compiler.compiler_args() 915 916 copt_proxy = self.get_compiler_options_for_target(target) 917 # First, the trivial ones that are impossible to override. 918 # 919 # Add -nostdinc/-nostdinc++ if needed; can't be overridden 920 commands += self.get_no_stdlib_args(target, compiler) 921 # Add things like /NOLOGO or -pipe; usually can't be overridden 922 commands += compiler.get_always_args() 923 # Only add warning-flags by default if the buildtype enables it, and if 924 # we weren't explicitly asked to not emit warnings (for Vala, f.ex) 925 if no_warn_args: 926 commands += compiler.get_no_warn_args() 927 else: 928 # warning_level is a string, but mypy can't determine that 929 commands += compiler.get_warn_args(T.cast(str, self.get_option_for_target(OptionKey('warning_level'), target))) 930 # Add -Werror if werror=true is set in the build options set on the 931 # command-line or default_options inside project(). This only sets the 932 # action to be done for warnings if/when they are emitted, so it's ok 933 # to set it after get_no_warn_args() or get_warn_args(). 934 if self.get_option_for_target(OptionKey('werror'), target): 935 commands += compiler.get_werror_args() 936 # Add compile args for c_* or cpp_* build options set on the 937 # command-line or default_options inside project(). 938 commands += compiler.get_option_compile_args(copt_proxy) 939 940 # Add buildtype args: optimization level, debugging, etc. 941 buildtype = self.get_option_for_target(OptionKey('buildtype'), target) 942 assert isinstance(buildtype, str), 'for mypy' 943 commands += compiler.get_buildtype_args(buildtype) 944 945 optimization = self.get_option_for_target(OptionKey('optimization'), target) 946 assert isinstance(optimization, str), 'for mypy' 947 commands += compiler.get_optimization_args(optimization) 948 949 debug = self.get_option_for_target(OptionKey('debug'), target) 950 assert isinstance(debug, bool), 'for mypy' 951 commands += compiler.get_debug_args(debug) 952 953 # Add compile args added using add_project_arguments() 954 commands += self.build.get_project_args(compiler, target.subproject, target.for_machine) 955 # Add compile args added using add_global_arguments() 956 # These override per-project arguments 957 commands += self.build.get_global_args(compiler, target.for_machine) 958 # Using both /ZI and /Zi at the same times produces a compiler warning. 959 # We do not add /ZI by default. If it is being used it is because the user has explicitly enabled it. 960 # /ZI needs to be removed in that case to avoid cl's warning to that effect (D9025 : overriding '/ZI' with '/Zi') 961 if ('/ZI' in commands) and ('/Zi' in commands): 962 commands.remove('/Zi') 963 # Compile args added from the env: CFLAGS/CXXFLAGS, etc, or the cross 964 # file. We want these to override all the defaults, but not the 965 # per-target compile args. 966 commands += self.environment.coredata.get_external_args(target.for_machine, compiler.get_language()) 967 # Always set -fPIC for shared libraries 968 if isinstance(target, build.SharedLibrary): 969 commands += compiler.get_pic_args() 970 # Set -fPIC for static libraries by default unless explicitly disabled 971 if isinstance(target, build.StaticLibrary) and target.pic: 972 commands += compiler.get_pic_args() 973 elif isinstance(target, (build.StaticLibrary, build.Executable)) and target.pie: 974 commands += compiler.get_pie_args() 975 # Add compile args needed to find external dependencies. Link args are 976 # added while generating the link command. 977 # NOTE: We must preserve the order in which external deps are 978 # specified, so we reverse the list before iterating over it. 979 for dep in reversed(target.get_external_deps()): 980 if not dep.found(): 981 continue 982 983 if compiler.language == 'vala': 984 if isinstance(dep, dependencies.PkgConfigDependency): 985 if dep.name == 'glib-2.0' and dep.version_reqs is not None: 986 for req in dep.version_reqs: 987 if req.startswith(('>=', '==')): 988 commands += ['--target-glib', req[2:]] 989 break 990 commands += ['--pkg', dep.name] 991 elif isinstance(dep, dependencies.ExternalLibrary): 992 commands += dep.get_link_args('vala') 993 else: 994 commands += compiler.get_dependency_compile_args(dep) 995 # Qt needs -fPIC for executables 996 # XXX: We should move to -fPIC for all executables 997 if isinstance(target, build.Executable): 998 commands += dep.get_exe_args(compiler) 999 # For 'automagic' deps: Boost and GTest. Also dependency('threads'). 1000 # pkg-config puts the thread flags itself via `Cflags:` 1001 # Fortran requires extra include directives. 1002 if compiler.language == 'fortran': 1003 for lt in chain(target.link_targets, target.link_whole_targets): 1004 priv_dir = self.get_target_private_dir(lt) 1005 commands += compiler.get_include_args(priv_dir, False) 1006 return commands 1007 1008 def build_target_link_arguments(self, compiler: 'Compiler', deps: T.List[build.Target]) -> T.List[str]: 1009 args: T.List[str] = [] 1010 for d in deps: 1011 if not d.is_linkable_target(): 1012 raise RuntimeError(f'Tried to link with a non-library target "{d.get_basename()}".') 1013 arg = self.get_target_filename_for_linking(d) 1014 if not arg: 1015 continue 1016 if compiler.get_language() == 'd': 1017 arg = '-Wl,' + arg 1018 else: 1019 arg = compiler.get_linker_lib_prefix() + arg 1020 args.append(arg) 1021 return args 1022 1023 def get_mingw_extra_paths(self, target: build.BuildTarget) -> T.List[str]: 1024 paths: OrderedSet[str] = OrderedSet() 1025 # The cross bindir 1026 root = self.environment.properties[target.for_machine].get_root() 1027 if root: 1028 paths.add(os.path.join(root, 'bin')) 1029 # The toolchain bindir 1030 sys_root = self.environment.properties[target.for_machine].get_sys_root() 1031 if sys_root: 1032 paths.add(os.path.join(sys_root, 'bin')) 1033 # Get program and library dirs from all target compilers 1034 if isinstance(target, build.BuildTarget): 1035 for cc in target.compilers.values(): 1036 paths.update(cc.get_program_dirs(self.environment)) 1037 paths.update(cc.get_library_dirs(self.environment)) 1038 return list(paths) 1039 1040 def determine_windows_extra_paths( 1041 self, target: T.Union[build.BuildTarget, build.CustomTarget, programs.ExternalProgram, mesonlib.File, str], 1042 extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget]]) -> T.List[str]: 1043 """On Windows there is no such thing as an rpath. 1044 1045 We must determine all locations of DLLs that this exe 1046 links to and return them so they can be used in unit 1047 tests. 1048 """ 1049 result: T.Set[str] = set() 1050 prospectives: T.Set[build.Target] = set() 1051 if isinstance(target, build.BuildTarget): 1052 prospectives.update(target.get_transitive_link_deps()) 1053 # External deps 1054 for deppath in self.rpaths_for_bundled_shared_libraries(target, exclude_system=False): 1055 result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath))) 1056 for bdep in extra_bdeps: 1057 prospectives.add(bdep) 1058 if isinstance(bdep, build.BuildTarget): 1059 prospectives.update(bdep.get_transitive_link_deps()) 1060 # Internal deps 1061 for ld in prospectives: 1062 dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld)) 1063 result.add(dirseg) 1064 if (isinstance(target, build.BuildTarget) and 1065 not self.environment.machines.matches_build_machine(target.for_machine)): 1066 result.update(self.get_mingw_extra_paths(target)) 1067 return list(result) 1068 1069 def write_benchmark_file(self, datafile: T.BinaryIO) -> None: 1070 self.write_test_serialisation(self.build.get_benchmarks(), datafile) 1071 1072 def write_test_file(self, datafile: T.BinaryIO) -> None: 1073 self.write_test_serialisation(self.build.get_tests(), datafile) 1074 1075 def create_test_serialisation(self, tests: T.List['Test']) -> T.List[TestSerialisation]: 1076 arr: T.List[TestSerialisation] = [] 1077 for t in sorted(tests, key=lambda tst: -1 * tst.priority): 1078 exe = t.get_exe() 1079 if isinstance(exe, programs.ExternalProgram): 1080 cmd = exe.get_command() 1081 else: 1082 cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))] 1083 if isinstance(exe, (build.BuildTarget, programs.ExternalProgram)): 1084 test_for_machine = exe.for_machine 1085 else: 1086 # E.g. an external verifier or simulator program run on a generated executable. 1087 # Can always be run without a wrapper. 1088 test_for_machine = MachineChoice.BUILD 1089 1090 # we allow passing compiled executables to tests, which may be cross built. 1091 # We need to consider these as well when considering whether the target is cross or not. 1092 for a in t.cmd_args: 1093 if isinstance(a, build.BuildTarget): 1094 if a.for_machine is MachineChoice.HOST: 1095 test_for_machine = MachineChoice.HOST 1096 break 1097 1098 is_cross = self.environment.is_cross_build(test_for_machine) 1099 if is_cross and self.environment.need_exe_wrapper(): 1100 exe_wrapper = self.environment.get_exe_wrapper() 1101 else: 1102 exe_wrapper = None 1103 machine = self.environment.machines[exe.for_machine] 1104 if machine.is_windows() or machine.is_cygwin(): 1105 extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget]] = [] 1106 if isinstance(exe, build.CustomTarget): 1107 extra_bdeps = list(exe.get_transitive_build_target_deps()) 1108 extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps) 1109 else: 1110 extra_paths = [] 1111 1112 cmd_args: T.List[str] = [] 1113 depends: T.Set[build.Target] = set(t.depends) 1114 if isinstance(exe, build.Target): 1115 depends.add(exe) 1116 for a in t.cmd_args: 1117 if isinstance(a, build.Target): 1118 depends.add(a) 1119 if isinstance(a, build.BuildTarget): 1120 extra_paths += self.determine_windows_extra_paths(a, []) 1121 1122 if isinstance(a, mesonlib.File): 1123 a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src)) 1124 cmd_args.append(a) 1125 elif isinstance(a, str): 1126 cmd_args.append(a) 1127 elif isinstance(a, build.Executable): 1128 p = self.construct_target_rel_path(a, t.workdir) 1129 if p == a.get_filename(): 1130 p = './' + p 1131 cmd_args.append(p) 1132 elif isinstance(a, build.Target): 1133 cmd_args.append(self.construct_target_rel_path(a, t.workdir)) 1134 else: 1135 raise MesonException('Bad object in test command.') 1136 ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross, 1137 exe_wrapper, self.environment.need_exe_wrapper(), 1138 t.is_parallel, cmd_args, t.env, 1139 t.should_fail, t.timeout, t.workdir, 1140 extra_paths, t.protocol, t.priority, 1141 isinstance(exe, build.Executable), 1142 [x.get_id() for x in depends], 1143 self.environment.coredata.version) 1144 arr.append(ts) 1145 return arr 1146 1147 def write_test_serialisation(self, tests: T.List['Test'], datafile: T.BinaryIO) -> None: 1148 pickle.dump(self.create_test_serialisation(tests), datafile) 1149 1150 def construct_target_rel_path(self, a: build.Target, workdir: T.Optional[str]) -> str: 1151 if workdir is None: 1152 return self.get_target_filename(a) 1153 assert os.path.isabs(workdir) 1154 abs_path = self.get_target_filename_abs(a) 1155 return os.path.relpath(abs_path, workdir) 1156 1157 def generate_depmf_install(self, d: InstallData) -> None: 1158 if self.build.dep_manifest_name is None: 1159 return 1160 ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json') 1161 ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name) 1162 out_name = os.path.join('{prefix}', self.build.dep_manifest_name) 1163 mfobj = {'type': 'dependency manifest', 'version': '1.0', 1164 'projects': {k: v.to_json() for k, v in self.build.dep_manifest.items()}} 1165 with open(ifilename, 'w', encoding='utf-8') as f: 1166 f.write(json.dumps(mfobj)) 1167 # Copy file from, to, and with mode unchanged 1168 d.data.append(InstallDataBase(ifilename, ofilename, out_name, None, '', 1169 tag='devel', data_type='depmf')) 1170 1171 def get_regen_filelist(self) -> T.List[str]: 1172 '''List of all files whose alteration means that the build 1173 definition needs to be regenerated.''' 1174 deps = [str(Path(self.build_to_src) / df) 1175 for df in self.interpreter.get_build_def_files()] 1176 if self.environment.is_cross_build(): 1177 deps.extend(self.environment.coredata.cross_files) 1178 deps.extend(self.environment.coredata.config_files) 1179 deps.append('meson-private/coredata.dat') 1180 self.check_clock_skew(deps) 1181 return deps 1182 1183 def generate_regen_info(self) -> None: 1184 deps = self.get_regen_filelist() 1185 regeninfo = RegenInfo(self.environment.get_source_dir(), 1186 self.environment.get_build_dir(), 1187 deps) 1188 filename = os.path.join(self.environment.get_scratch_dir(), 1189 'regeninfo.dump') 1190 with open(filename, 'wb') as f: 1191 pickle.dump(regeninfo, f) 1192 1193 def check_clock_skew(self, file_list: T.List[str]) -> None: 1194 # If a file that leads to reconfiguration has a time 1195 # stamp in the future, it will trigger an eternal reconfigure 1196 # loop. 1197 import time 1198 now = time.time() 1199 for f in file_list: 1200 absf = os.path.join(self.environment.get_build_dir(), f) 1201 ftime = os.path.getmtime(absf) 1202 delta = ftime - now 1203 # On Windows disk time stamps sometimes point 1204 # to the future by a minuscule amount, less than 1205 # 0.001 seconds. I don't know why. 1206 if delta > 0.001: 1207 raise MesonException(f'Clock skew detected. File {absf} has a time stamp {delta:.4f}s in the future.') 1208 1209 def build_target_to_cmd_array(self, bt: T.Union[build.BuildTarget, programs.ExternalProgram]) -> T.List[str]: 1210 if isinstance(bt, build.BuildTarget): 1211 arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))] 1212 else: 1213 arr = bt.get_command() 1214 return arr 1215 1216 def replace_extra_args(self, args: T.List[str], genlist: 'build.GeneratedList') -> T.List[str]: 1217 final_args: T.List[str] = [] 1218 for a in args: 1219 if a == '@EXTRA_ARGS@': 1220 final_args += genlist.get_extra_args() 1221 else: 1222 final_args.append(a) 1223 return final_args 1224 1225 def replace_outputs(self, args: T.List[str], private_dir: str, output_list: T.List[str]) -> T.List[str]: 1226 newargs: T.List[str] = [] 1227 regex = re.compile(r'@OUTPUT(\d+)@') 1228 for arg in args: 1229 m = regex.search(arg) 1230 while m is not None: 1231 index = int(m.group(1)) 1232 src = f'@OUTPUT{index}@' 1233 arg = arg.replace(src, os.path.join(private_dir, output_list[index])) 1234 m = regex.search(arg) 1235 newargs.append(arg) 1236 return newargs 1237 1238 def get_build_by_default_targets(self) -> 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]': 1239 result: 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]' = OrderedDict() 1240 # Get all build and custom targets that must be built by default 1241 for name, b in self.build.get_targets().items(): 1242 if b.build_by_default: 1243 result[name] = b 1244 # Get all targets used as test executables and arguments. These must 1245 # also be built by default. XXX: Sometime in the future these should be 1246 # built only before running tests. 1247 for t in self.build.get_tests(): 1248 exe = t.exe 1249 if isinstance(exe, (build.CustomTarget, build.BuildTarget)): 1250 result[exe.get_id()] = exe 1251 for arg in t.cmd_args: 1252 if not isinstance(arg, (build.CustomTarget, build.BuildTarget)): 1253 continue 1254 result[arg.get_id()] = arg 1255 for dep in t.depends: 1256 assert isinstance(dep, (build.CustomTarget, build.BuildTarget)) 1257 result[dep.get_id()] = dep 1258 return result 1259 1260 @lru_cache(maxsize=None) 1261 def get_custom_target_provided_by_generated_source(self, generated_source: build.CustomTarget) -> 'ImmutableListProtocol[str]': 1262 libs: T.List[str] = [] 1263 for f in generated_source.get_outputs(): 1264 if self.environment.is_library(f): 1265 libs.append(os.path.join(self.get_target_dir(generated_source), f)) 1266 return libs 1267 1268 @lru_cache(maxsize=None) 1269 def get_custom_target_provided_libraries(self, target: T.Union[build.BuildTarget, build.CustomTarget]) -> 'ImmutableListProtocol[str]': 1270 libs: T.List[str] = [] 1271 for t in target.get_generated_sources(): 1272 if not isinstance(t, build.CustomTarget): 1273 continue 1274 libs.extend(self.get_custom_target_provided_by_generated_source(t)) 1275 return libs 1276 1277 def is_unity(self, target: build.BuildTarget) -> bool: 1278 optval = self.get_option_for_target(OptionKey('unity'), target) 1279 return optval == 'on' or (optval == 'subprojects' and target.subproject != '') 1280 1281 def get_custom_target_sources(self, target: build.CustomTarget) -> T.List[str]: 1282 ''' 1283 Custom target sources can be of various object types; strings, File, 1284 BuildTarget, even other CustomTargets. 1285 Returns the path to them relative to the build root directory. 1286 ''' 1287 srcs: T.List[str] = [] 1288 for i in target.get_sources(): 1289 if isinstance(i, str): 1290 fname = [os.path.join(self.build_to_src, target.subdir, i)] 1291 elif isinstance(i, build.BuildTarget): 1292 fname = [self.get_target_filename(i)] 1293 elif isinstance(i, (build.CustomTarget, build.CustomTargetIndex)): 1294 fname = [os.path.join(self.get_custom_target_output_dir(i), p) for p in i.get_outputs()] 1295 elif isinstance(i, build.GeneratedList): 1296 fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()] 1297 elif isinstance(i, build.ExtractedObjects): 1298 outputs = i.get_outputs(self) 1299 fname = self.get_extracted_obj_paths(i.target, outputs) 1300 else: 1301 fname = [i.rel_to_builddir(self.build_to_src)] 1302 if target.absolute_paths: 1303 fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname] 1304 srcs += fname 1305 return srcs 1306 1307 def get_extracted_obj_paths(self, target: build.BuildTarget, outputs: T.List[str]) -> T.List[str]: 1308 return [os.path.join(self.get_target_private_dir(target), p) for p in outputs] 1309 1310 def get_custom_target_depend_files(self, target: build.CustomTarget, absolute_paths: bool = False) -> T.List[str]: 1311 deps: T.List[str] = [] 1312 for i in target.depend_files: 1313 if isinstance(i, mesonlib.File): 1314 if absolute_paths: 1315 deps.append(i.absolute_path(self.environment.get_source_dir(), 1316 self.environment.get_build_dir())) 1317 else: 1318 deps.append(i.rel_to_builddir(self.build_to_src)) 1319 else: 1320 if absolute_paths: 1321 deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i)) 1322 else: 1323 deps.append(os.path.join(self.build_to_src, target.subdir, i)) 1324 return deps 1325 1326 def get_custom_target_output_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str: 1327 # The XCode backend is special. A target foo/bar does 1328 # not go to ${BUILDDIR}/foo/bar but instead to 1329 # ${BUILDDIR}/${BUILDTYPE}/foo/bar. 1330 # Currently we set the include dir to be the former, 1331 # and not the latter. Thus we need this extra customisation 1332 # point. If in the future we make include dirs et al match 1333 # ${BUILDDIR}/${BUILDTYPE} instead, this becomes unnecessary. 1334 return self.get_target_dir(target) 1335 1336 @lru_cache(maxsize=None) 1337 def get_normpath_target(self, source: str) -> str: 1338 return os.path.normpath(source) 1339 1340 def get_custom_target_dirs(self, target: build.CustomTarget, compiler: 'Compiler', *, 1341 absolute_path: bool = False) -> T.List[str]: 1342 custom_target_include_dirs: T.List[str] = [] 1343 for i in target.get_generated_sources(): 1344 # Generator output goes into the target private dir which is 1345 # already in the include paths list. Only custom targets have their 1346 # own target build dir. 1347 if not isinstance(i, (build.CustomTarget, build.CustomTargetIndex)): 1348 continue 1349 idir = self.get_normpath_target(self.get_custom_target_output_dir(i)) 1350 if not idir: 1351 idir = '.' 1352 if absolute_path: 1353 idir = os.path.join(self.environment.get_build_dir(), idir) 1354 if idir not in custom_target_include_dirs: 1355 custom_target_include_dirs.append(idir) 1356 return custom_target_include_dirs 1357 1358 def get_custom_target_dir_include_args( 1359 self, target: build.CustomTarget, compiler: 'Compiler', *, 1360 absolute_path: bool = False) -> T.List[str]: 1361 incs: T.List[str] = [] 1362 for i in self.get_custom_target_dirs(target, compiler, absolute_path=absolute_path): 1363 incs += compiler.get_include_args(i, False) 1364 return incs 1365 1366 def eval_custom_target_command( 1367 self, target: build.CustomTarget, absolute_outputs: bool = False) -> \ 1368 T.Tuple[T.List[str], T.List[str], T.List[str]]: 1369 # We want the outputs to be absolute only when using the VS backend 1370 # XXX: Maybe allow the vs backend to use relative paths too? 1371 source_root = self.build_to_src 1372 build_root = '.' 1373 outdir = self.get_custom_target_output_dir(target) 1374 if absolute_outputs: 1375 source_root = self.environment.get_source_dir() 1376 build_root = self.environment.get_build_dir() 1377 outdir = os.path.join(self.environment.get_build_dir(), outdir) 1378 outputs = [os.path.join(outdir, i) for i in target.get_outputs()] 1379 inputs = self.get_custom_target_sources(target) 1380 # Evaluate the command list 1381 cmd: T.List[str] = [] 1382 for i in target.command: 1383 if isinstance(i, build.BuildTarget): 1384 cmd += self.build_target_to_cmd_array(i) 1385 continue 1386 elif isinstance(i, build.CustomTarget): 1387 # GIR scanner will attempt to execute this binary but 1388 # it assumes that it is in path, so always give it a full path. 1389 tmp = i.get_outputs()[0] 1390 i = os.path.join(self.get_custom_target_output_dir(i), tmp) 1391 elif isinstance(i, mesonlib.File): 1392 i = i.rel_to_builddir(self.build_to_src) 1393 if target.absolute_paths or absolute_outputs: 1394 i = os.path.join(self.environment.get_build_dir(), i) 1395 # FIXME: str types are blindly added ignoring 'target.absolute_paths' 1396 # because we can't know if they refer to a file or just a string 1397 elif isinstance(i, str): 1398 if '@SOURCE_ROOT@' in i: 1399 i = i.replace('@SOURCE_ROOT@', source_root) 1400 if '@BUILD_ROOT@' in i: 1401 i = i.replace('@BUILD_ROOT@', build_root) 1402 if '@CURRENT_SOURCE_DIR@' in i: 1403 i = i.replace('@CURRENT_SOURCE_DIR@', os.path.join(source_root, target.subdir)) 1404 if '@DEPFILE@' in i: 1405 if target.depfile is None: 1406 msg = f'Custom target {target.name!r} has @DEPFILE@ but no depfile ' \ 1407 'keyword argument.' 1408 raise MesonException(msg) 1409 dfilename = os.path.join(outdir, target.depfile) 1410 i = i.replace('@DEPFILE@', dfilename) 1411 if '@PRIVATE_DIR@' in i: 1412 if target.absolute_paths: 1413 pdir = self.get_target_private_dir_abs(target) 1414 else: 1415 pdir = self.get_target_private_dir(target) 1416 i = i.replace('@PRIVATE_DIR@', pdir) 1417 else: 1418 raise RuntimeError(f'Argument {i} is of unknown type {type(i)}') 1419 cmd.append(i) 1420 # Substitute the rest of the template strings 1421 values = mesonlib.get_filenames_templates_dict(inputs, outputs) 1422 cmd = mesonlib.substitute_values(cmd, values) 1423 # This should not be necessary but removing it breaks 1424 # building GStreamer on Windows. The underlying issue 1425 # is problems with quoting backslashes on Windows 1426 # which is the seventh circle of hell. The downside is 1427 # that this breaks custom targets whose command lines 1428 # have backslashes. If you try to fix this be sure to 1429 # check that it does not break GST. 1430 # 1431 # The bug causes file paths such as c:\foo to get escaped 1432 # into c:\\foo. 1433 # 1434 # Unfortunately we have not been able to come up with an 1435 # isolated test case for this so unless you manage to come up 1436 # with one, the only way is to test the building with Gst's 1437 # setup. Note this in your MR or ping us and we will get it 1438 # fixed. 1439 # 1440 # https://github.com/mesonbuild/meson/pull/737 1441 cmd = [i.replace('\\', '/') for i in cmd] 1442 return inputs, outputs, cmd 1443 1444 def get_run_target_env(self, target: build.RunTarget) -> build.EnvironmentVariables: 1445 env = target.env if target.env else build.EnvironmentVariables() 1446 introspect_cmd = join_args(self.environment.get_build_command() + ['introspect']) 1447 env.set('MESON_SOURCE_ROOT', [self.environment.get_source_dir()]) 1448 env.set('MESON_BUILD_ROOT', [self.environment.get_build_dir()]) 1449 env.set('MESON_SUBDIR', [target.subdir]) 1450 env.set('MESONINTROSPECT', [introspect_cmd]) 1451 return env 1452 1453 def run_postconf_scripts(self) -> None: 1454 from ..scripts.meson_exe import run_exe 1455 introspect_cmd = join_args(self.environment.get_build_command() + ['introspect']) 1456 env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 1457 'MESON_BUILD_ROOT': self.environment.get_build_dir(), 1458 'MESONINTROSPECT': introspect_cmd, 1459 } 1460 1461 for s in self.build.postconf_scripts: 1462 name = ' '.join(s.cmd_args) 1463 mlog.log(f'Running postconf script {name!r}') 1464 run_exe(s, env) 1465 1466 def create_install_data(self) -> InstallData: 1467 strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip') 1468 if strip_bin is None: 1469 if self.environment.is_cross_build(): 1470 mlog.warning('Cross file does not specify strip binary, result will not be stripped.') 1471 else: 1472 # TODO go through all candidates, like others 1473 strip_bin = [detect.defaults['strip'][0]] 1474 1475 umask = self.environment.coredata.get_option(OptionKey('install_umask')) 1476 assert isinstance(umask, (str, int)), 'for mypy' 1477 1478 d = InstallData(self.environment.get_source_dir(), 1479 self.environment.get_build_dir(), 1480 self.environment.get_prefix(), 1481 strip_bin, 1482 umask, 1483 self.environment.get_build_command() + ['introspect'], 1484 self.environment.coredata.version) 1485 self.generate_depmf_install(d) 1486 self.generate_target_install(d) 1487 self.generate_header_install(d) 1488 self.generate_man_install(d) 1489 self.generate_emptydir_install(d) 1490 self.generate_data_install(d) 1491 self.generate_custom_install_script(d) 1492 self.generate_subdir_install(d) 1493 return d 1494 1495 def create_install_data_files(self) -> None: 1496 install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') 1497 with open(install_data_file, 'wb') as ofile: 1498 pickle.dump(self.create_install_data(), ofile) 1499 1500 def guess_install_tag(self, fname: str, outdir: T.Optional[str] = None) -> T.Optional[str]: 1501 prefix = self.environment.get_prefix() 1502 bindir = Path(prefix, self.environment.get_bindir()) 1503 libdir = Path(prefix, self.environment.get_libdir()) 1504 incdir = Path(prefix, self.environment.get_includedir()) 1505 _ldir = self.environment.coredata.get_option(mesonlib.OptionKey('localedir')) 1506 assert isinstance(_ldir, str), 'for mypy' 1507 localedir = Path(prefix, _ldir) 1508 dest_path = Path(prefix, outdir, Path(fname).name) if outdir else Path(prefix, fname) 1509 if bindir in dest_path.parents: 1510 return 'runtime' 1511 elif libdir in dest_path.parents: 1512 if dest_path.suffix in {'.a', '.pc'}: 1513 return 'devel' 1514 elif dest_path.suffix in {'.so', '.dll'}: 1515 return 'runtime' 1516 elif incdir in dest_path.parents: 1517 return 'devel' 1518 elif localedir in dest_path.parents: 1519 return 'i18n' 1520 mlog.debug('Failed to guess install tag for', dest_path) 1521 return None 1522 1523 def generate_target_install(self, d: InstallData) -> None: 1524 for t in self.build.get_targets().values(): 1525 if not t.should_install(): 1526 continue 1527 outdirs, install_dir_name, custom_install_dir = t.get_install_dir(self.environment) 1528 # Sanity-check the outputs and install_dirs 1529 num_outdirs, num_out = len(outdirs), len(t.get_outputs()) 1530 if num_outdirs != 1 and num_outdirs != num_out: 1531 m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \ 1532 "Pass 'false' for outputs that should not be installed and 'true' for\n" \ 1533 'using the default installation directory for an output.' 1534 raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) 1535 assert len(t.install_tag) == num_out 1536 install_mode = t.get_custom_install_mode() 1537 # Install the target output(s) 1538 if isinstance(t, build.BuildTarget): 1539 # In general, stripping static archives is tricky and full of pitfalls. 1540 # Wholesale stripping of static archives with a command such as 1541 # 1542 # strip libfoo.a 1543 # 1544 # is broken, as GNU's strip will remove *every* symbol in a static 1545 # archive. One solution to this nonintuitive behaviour would be 1546 # to only strip local/debug symbols. Unfortunately, strip arguments 1547 # are not specified by POSIX and therefore not portable. GNU's `-g` 1548 # option (i.e. remove debug symbols) is equivalent to Apple's `-S`. 1549 # 1550 # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more 1551 # fine-grained stripping of static archives. 1552 should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target(OptionKey('strip'), t) 1553 assert isinstance(should_strip, bool), 'for mypy' 1554 # Install primary build output (library/executable/jar, etc) 1555 # Done separately because of strip/aliases/rpath 1556 if outdirs[0] is not False: 1557 tag = t.install_tag[0] or ('devel' if isinstance(t, build.StaticLibrary) else 'runtime') 1558 mappings = t.get_link_deps_mapping(d.prefix, self.environment) 1559 i = TargetInstallData(self.get_target_filename(t), outdirs[0], 1560 install_dir_name, t.get_aliases(), 1561 should_strip, mappings, t.rpath_dirs_to_remove, 1562 t.install_rpath, install_mode, t.subproject, 1563 tag=tag) 1564 d.targets.append(i) 1565 1566 if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): 1567 # On toolchains/platforms that use an import library for 1568 # linking (separate from the shared library with all the 1569 # code), we need to install that too (dll.a/.lib). 1570 if t.get_import_filename(): 1571 if custom_install_dir: 1572 # If the DLL is installed into a custom directory, 1573 # install the import library into the same place so 1574 # it doesn't go into a surprising place 1575 implib_install_dir = outdirs[0] 1576 else: 1577 implib_install_dir = self.environment.get_import_lib_dir() 1578 # Install the import library; may not exist for shared modules 1579 i = TargetInstallData(self.get_target_filename_for_linking(t), 1580 implib_install_dir, install_dir_name, 1581 {}, False, {}, set(), '', install_mode, 1582 t.subproject, optional=isinstance(t, build.SharedModule), 1583 tag='devel') 1584 d.targets.append(i) 1585 1586 if not should_strip and t.get_debug_filename(): 1587 debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) 1588 i = TargetInstallData(debug_file, outdirs[0], 1589 install_dir_name, 1590 {}, False, {}, set(), '', 1591 install_mode, t.subproject, 1592 optional=True, tag='devel') 1593 d.targets.append(i) 1594 # Install secondary outputs. Only used for Vala right now. 1595 if num_outdirs > 1: 1596 for output, outdir, tag in zip(t.get_outputs()[1:], outdirs[1:], t.install_tag[1:]): 1597 # User requested that we not install this output 1598 if outdir is False: 1599 continue 1600 f = os.path.join(self.get_target_dir(t), output) 1601 i = TargetInstallData(f, outdir, install_dir_name, {}, False, {}, set(), None, 1602 install_mode, t.subproject, 1603 tag=tag) 1604 d.targets.append(i) 1605 elif isinstance(t, build.CustomTarget): 1606 # If only one install_dir is specified, assume that all 1607 # outputs will be installed into it. This is for 1608 # backwards-compatibility and because it makes sense to 1609 # avoid repetition since this is a common use-case. 1610 # 1611 # To selectively install only some outputs, pass `false` as 1612 # the install_dir for the corresponding output by index 1613 if num_outdirs == 1 and num_out > 1: 1614 for output, tag in zip(t.get_outputs(), t.install_tag): 1615 f = os.path.join(self.get_target_dir(t), output) 1616 if not install_dir_name: 1617 dir_name = os.path.join('{prefix}', outdirs[0]) 1618 i = TargetInstallData(f, outdirs[0], dir_name, {}, 1619 False, {}, set(), None, install_mode, 1620 t.subproject, optional=not t.build_by_default, 1621 tag=tag) 1622 d.targets.append(i) 1623 else: 1624 for output, outdir, tag in zip(t.get_outputs(), outdirs, t.install_tag): 1625 # User requested that we not install this output 1626 if outdir is False: 1627 continue 1628 f = os.path.join(self.get_target_dir(t), output) 1629 if not install_dir_name: 1630 dir_name = os.path.join('{prefix}', outdir) 1631 i = TargetInstallData(f, outdir, dir_name, 1632 {}, False, {}, set(), None, install_mode, 1633 t.subproject, optional=not t.build_by_default, 1634 tag=tag) 1635 d.targets.append(i) 1636 1637 def generate_custom_install_script(self, d: InstallData) -> None: 1638 d.install_scripts = self.build.install_scripts 1639 1640 def generate_header_install(self, d: InstallData) -> None: 1641 incroot = self.environment.get_includedir() 1642 headers = self.build.get_headers() 1643 1644 srcdir = self.environment.get_source_dir() 1645 builddir = self.environment.get_build_dir() 1646 for h in headers: 1647 outdir = outdir_name = h.get_custom_install_dir() 1648 if outdir is None: 1649 subdir = h.get_install_subdir() 1650 if subdir is None: 1651 outdir = incroot 1652 outdir_name = '{includedir}' 1653 else: 1654 outdir = os.path.join(incroot, subdir) 1655 outdir_name = os.path.join('{includedir}', subdir) 1656 1657 for f in h.get_sources(): 1658 if not isinstance(f, File): 1659 raise MesonException(f'Invalid header type {f!r} can\'t be installed') 1660 abspath = f.absolute_path(srcdir, builddir) 1661 i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel') 1662 d.headers.append(i) 1663 1664 def generate_man_install(self, d: InstallData) -> None: 1665 manroot = self.environment.get_mandir() 1666 man = self.build.get_man() 1667 for m in man: 1668 for f in m.get_sources(): 1669 num = f.split('.')[-1] 1670 subdir = m.get_custom_install_dir() 1671 if subdir is None: 1672 if m.locale: 1673 subdir = os.path.join('{mandir}', m.locale, 'man' + num) 1674 else: 1675 subdir = os.path.join('{mandir}', 'man' + num) 1676 fname = f.fname 1677 if m.locale: # strip locale from file name 1678 fname = fname.replace(f'.{m.locale}', '') 1679 srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) 1680 dstname = os.path.join(subdir, os.path.basename(fname)) 1681 dstabs = dstname.replace('{mandir}', manroot) 1682 i = InstallDataBase(srcabs, dstabs, dstname, m.get_custom_install_mode(), m.subproject, tag='man') 1683 d.man.append(i) 1684 1685 def generate_emptydir_install(self, d: InstallData) -> None: 1686 emptydir: T.List[build.EmptyDir] = self.build.get_emptydir() 1687 for e in emptydir: 1688 i = InstallEmptyDir(e.path, e.install_mode, e.subproject, e.install_tag) 1689 d.emptydir.append(i) 1690 1691 def generate_data_install(self, d: InstallData) -> None: 1692 data = self.build.get_data() 1693 srcdir = self.environment.get_source_dir() 1694 builddir = self.environment.get_build_dir() 1695 for de in data: 1696 assert isinstance(de, build.Data) 1697 subdir = de.install_dir 1698 subdir_name = de.install_dir_name 1699 if not subdir: 1700 subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name) 1701 subdir_name = os.path.join('{datadir}', self.interpreter.build.project_name) 1702 for src_file, dst_name in zip(de.sources, de.rename): 1703 assert isinstance(src_file, mesonlib.File) 1704 dst_abs = os.path.join(subdir, dst_name) 1705 dstdir_name = os.path.join(subdir_name, dst_name) 1706 tag = de.install_tag or self.guess_install_tag(dst_abs) 1707 i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, dstdir_name, 1708 de.install_mode, de.subproject, tag=tag, data_type=de.data_type) 1709 d.data.append(i) 1710 1711 def generate_subdir_install(self, d: InstallData) -> None: 1712 for sd in self.build.get_install_subdirs(): 1713 if sd.from_source_dir: 1714 from_dir = self.environment.get_source_dir() 1715 else: 1716 from_dir = self.environment.get_build_dir() 1717 src_dir = os.path.join(from_dir, 1718 sd.source_subdir, 1719 sd.installable_subdir).rstrip('/') 1720 dst_dir = os.path.join(self.environment.get_prefix(), 1721 sd.install_dir) 1722 dst_name = os.path.join('{prefix}', sd.install_dir) 1723 if not sd.strip_directory: 1724 dst_dir = os.path.join(dst_dir, os.path.basename(src_dir)) 1725 dst_name = os.path.join(dst_dir, os.path.basename(src_dir)) 1726 i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, sd.install_tag) 1727 d.install_subdirs.append(i) 1728 1729 def get_introspection_data(self, target_id: str, target: build.Target) -> T.List['TargetIntrospectionData']: 1730 ''' 1731 Returns a list of source dicts with the following format for a given target: 1732 [ 1733 { 1734 "language": "<LANG>", 1735 "compiler": ["result", "of", "comp.get_exelist()"], 1736 "parameters": ["list", "of", "compiler", "parameters], 1737 "sources": ["list", "of", "all", "<LANG>", "source", "files"], 1738 "generated_sources": ["list", "of", "generated", "source", "files"] 1739 } 1740 ] 1741 1742 This is a limited fallback / reference implementation. The backend should override this method. 1743 ''' 1744 if isinstance(target, (build.CustomTarget, build.BuildTarget)): 1745 source_list_raw = target.sources 1746 source_list = [] 1747 for j in source_list_raw: 1748 if isinstance(j, mesonlib.File): 1749 source_list += [j.absolute_path(self.source_dir, self.build_dir)] 1750 elif isinstance(j, str): 1751 source_list += [os.path.join(self.source_dir, j)] 1752 elif isinstance(j, (build.CustomTarget, build.BuildTarget)): 1753 source_list += [os.path.join(self.build_dir, j.get_subdir(), o) for o in j.get_outputs()] 1754 source_list = list(map(lambda x: os.path.normpath(x), source_list)) 1755 1756 compiler: T.List[str] = [] 1757 if isinstance(target, build.CustomTarget): 1758 tmp_compiler = target.command 1759 for j in tmp_compiler: 1760 if isinstance(j, mesonlib.File): 1761 compiler += [j.absolute_path(self.source_dir, self.build_dir)] 1762 elif isinstance(j, str): 1763 compiler += [j] 1764 elif isinstance(j, (build.BuildTarget, build.CustomTarget)): 1765 compiler += j.get_outputs() 1766 else: 1767 raise RuntimeError(f'Type "{type(j).__name__}" is not supported in get_introspection_data. This is a bug') 1768 1769 return [{ 1770 'language': 'unknown', 1771 'compiler': compiler, 1772 'parameters': [], 1773 'sources': source_list, 1774 'generated_sources': [] 1775 }] 1776 1777 return [] 1778 1779 def get_devenv(self) -> build.EnvironmentVariables: 1780 env = build.EnvironmentVariables() 1781 extra_paths = set() 1782 library_paths = set() 1783 for t in self.build.get_targets().values(): 1784 cross_built = not self.environment.machines.matches_build_machine(t.for_machine) 1785 can_run = not cross_built or not self.environment.need_exe_wrapper() 1786 in_default_dir = t.should_install() and not t.get_install_dir(self.environment)[2] 1787 if not can_run or not in_default_dir: 1788 continue 1789 tdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) 1790 if isinstance(t, build.Executable): 1791 # Add binaries that are going to be installed in bindir into PATH 1792 # so they get used by default instead of searching on system when 1793 # in developer environment. 1794 extra_paths.add(tdir) 1795 if mesonlib.is_windows() or mesonlib.is_cygwin(): 1796 # On windows we cannot rely on rpath to run executables from build 1797 # directory. We have to add in PATH the location of every DLL needed. 1798 extra_paths.update(self.determine_windows_extra_paths(t, [])) 1799 elif isinstance(t, build.SharedLibrary): 1800 # Add libraries that are going to be installed in libdir into 1801 # LD_LIBRARY_PATH. This allows running system applications using 1802 # that library. 1803 library_paths.add(tdir) 1804 if mesonlib.is_windows() or mesonlib.is_cygwin(): 1805 extra_paths.update(library_paths) 1806 elif mesonlib.is_osx(): 1807 env.prepend('DYLD_LIBRARY_PATH', list(library_paths)) 1808 else: 1809 env.prepend('LD_LIBRARY_PATH', list(library_paths)) 1810 env.prepend('PATH', list(extra_paths)) 1811 return env 1812