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 pathlib import Path 18import enum 19import json 20import os 21import pickle 22import re 23import shlex 24import subprocess 25import textwrap 26import typing as T 27 28from .. import build 29from .. import dependencies 30from .. import mesonlib 31from .. import mlog 32from ..mesonlib import ( 33 File, MachineChoice, MesonException, OrderedSet, OptionOverrideProxy, 34 classify_unity_sources, unholder 35) 36 37if T.TYPE_CHECKING: 38 from ..interpreter import Interpreter 39 40 41class TestProtocol(enum.Enum): 42 43 EXITCODE = 0 44 TAP = 1 45 GTEST = 2 46 47 @classmethod 48 def from_str(cls, string: str) -> 'TestProtocol': 49 if string == 'exitcode': 50 return cls.EXITCODE 51 elif string == 'tap': 52 return cls.TAP 53 elif string == 'gtest': 54 return cls.GTEST 55 raise MesonException('unknown test format {}'.format(string)) 56 57 def __str__(self) -> str: 58 if self is self.EXITCODE: 59 return 'exitcode' 60 elif self is self.GTEST: 61 return 'gtest' 62 return 'tap' 63 64 65class CleanTrees: 66 ''' 67 Directories outputted by custom targets that have to be manually cleaned 68 because on Linux `ninja clean` only deletes empty directories. 69 ''' 70 def __init__(self, build_dir, trees): 71 self.build_dir = build_dir 72 self.trees = trees 73 74class InstallData: 75 def __init__(self, source_dir, build_dir, prefix, strip_bin, 76 install_umask, mesonintrospect): 77 self.source_dir = source_dir 78 self.build_dir = build_dir 79 self.prefix = prefix 80 self.strip_bin = strip_bin 81 self.install_umask = install_umask 82 self.targets = [] 83 self.headers = [] 84 self.man = [] 85 self.data = [] 86 self.po_package_name = '' 87 self.po = [] 88 self.install_scripts = [] 89 self.install_subdirs = [] 90 self.mesonintrospect = mesonintrospect 91 92class TargetInstallData: 93 def __init__(self, fname, outdir, aliases, strip, install_name_mappings, rpath_dirs_to_remove, install_rpath, install_mode, optional=False): 94 self.fname = fname 95 self.outdir = outdir 96 self.aliases = aliases 97 self.strip = strip 98 self.install_name_mappings = install_name_mappings 99 self.rpath_dirs_to_remove = rpath_dirs_to_remove 100 self.install_rpath = install_rpath 101 self.install_mode = install_mode 102 self.optional = optional 103 104class ExecutableSerialisation: 105 def __init__(self, cmd_args, env=None, exe_wrapper=None, 106 workdir=None, extra_paths=None, capture=None): 107 self.cmd_args = cmd_args 108 self.env = env or {} 109 if exe_wrapper is not None: 110 assert(isinstance(exe_wrapper, dependencies.ExternalProgram)) 111 self.exe_runner = exe_wrapper 112 self.workdir = workdir 113 self.extra_paths = extra_paths 114 self.capture = capture 115 116class TestSerialisation: 117 def __init__(self, name: str, project: str, suite: str, fname: T.List[str], 118 is_cross_built: bool, exe_wrapper: T.Optional[dependencies.ExternalProgram], 119 needs_exe_wrapper: bool, is_parallel: bool, cmd_args: T.List[str], 120 env: build.EnvironmentVariables, should_fail: bool, 121 timeout: T.Optional[int], workdir: T.Optional[str], 122 extra_paths: T.List[str], protocol: TestProtocol, priority: int, 123 cmd_is_built: bool): 124 self.name = name 125 self.project_name = project 126 self.suite = suite 127 self.fname = fname 128 self.is_cross_built = is_cross_built 129 if exe_wrapper is not None: 130 assert(isinstance(exe_wrapper, dependencies.ExternalProgram)) 131 self.exe_runner = exe_wrapper 132 self.is_parallel = is_parallel 133 self.cmd_args = cmd_args 134 self.env = env 135 self.should_fail = should_fail 136 self.timeout = timeout 137 self.workdir = workdir 138 self.extra_paths = extra_paths 139 self.protocol = protocol 140 self.priority = priority 141 self.needs_exe_wrapper = needs_exe_wrapper 142 self.cmd_is_built = cmd_is_built 143 144 145def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']: 146 if backend == 'ninja': 147 from . import ninjabackend 148 return ninjabackend.NinjaBackend(build, interpreter) 149 elif backend == 'vs': 150 from . import vs2010backend 151 return vs2010backend.autodetect_vs_version(build, interpreter) 152 elif backend == 'vs2010': 153 from . import vs2010backend 154 return vs2010backend.Vs2010Backend(build, interpreter) 155 elif backend == 'vs2015': 156 from . import vs2015backend 157 return vs2015backend.Vs2015Backend(build, interpreter) 158 elif backend == 'vs2017': 159 from . import vs2017backend 160 return vs2017backend.Vs2017Backend(build, interpreter) 161 elif backend == 'vs2019': 162 from . import vs2019backend 163 return vs2019backend.Vs2019Backend(build, interpreter) 164 elif backend == 'xcode': 165 from . import xcodebackend 166 return xcodebackend.XCodeBackend(build, interpreter) 167 return None 168 169# This class contains the basic functionality that is needed by all backends. 170# Feel free to move stuff in and out of it as you see fit. 171class Backend: 172 def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']): 173 # Make it possible to construct a dummy backend 174 # This is used for introspection without a build directory 175 if build is None: 176 self.environment = None 177 return 178 self.build = build 179 self.interpreter = interpreter 180 self.environment = build.environment 181 self.processed_targets = {} 182 self.build_dir = self.environment.get_build_dir() 183 self.source_dir = self.environment.get_source_dir() 184 self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(), 185 self.environment.get_build_dir()) 186 187 def get_target_filename(self, t, *, warn_multi_output: bool = True): 188 if isinstance(t, build.CustomTarget): 189 if warn_multi_output and len(t.get_outputs()) != 1: 190 mlog.warning('custom_target {!r} has more than one output! ' 191 'Using the first one.'.format(t.name)) 192 filename = t.get_outputs()[0] 193 elif isinstance(t, build.CustomTargetIndex): 194 filename = t.get_outputs()[0] 195 else: 196 assert(isinstance(t, build.BuildTarget)) 197 filename = t.get_filename() 198 return os.path.join(self.get_target_dir(t), filename) 199 200 def get_target_filename_abs(self, target): 201 return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) 202 203 def get_base_options_for_target(self, target): 204 return OptionOverrideProxy(target.option_overrides_base, 205 self.environment.coredata.builtins, 206 self.environment.coredata.base_options) 207 208 def get_compiler_options_for_target(self, target): 209 comp_reg = self.environment.coredata.compiler_options[target.for_machine] 210 comp_override = target.option_overrides_compiler 211 return { 212 lang: OptionOverrideProxy(comp_override[lang], comp_reg[lang]) 213 for lang in set(comp_reg.keys()) | set(comp_override.keys()) 214 } 215 216 def get_option_for_target(self, option_name, target): 217 if option_name in target.option_overrides_base: 218 override = target.option_overrides_base[option_name] 219 return self.environment.coredata.validate_option_value(option_name, override) 220 return self.environment.coredata.get_builtin_option(option_name, target.subproject) 221 222 def get_target_filename_for_linking(self, target): 223 # On some platforms (msvc for instance), the file that is used for 224 # dynamic linking is not the same as the dynamic library itself. This 225 # file is called an import library, and we want to link against that. 226 # On all other platforms, we link to the library directly. 227 if isinstance(target, build.SharedLibrary): 228 link_lib = target.get_import_filename() or target.get_filename() 229 return os.path.join(self.get_target_dir(target), link_lib) 230 elif isinstance(target, build.StaticLibrary): 231 return os.path.join(self.get_target_dir(target), target.get_filename()) 232 elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)): 233 if not target.is_linkable_target(): 234 raise MesonException('Tried to link against custom target "{}", which is not linkable.'.format(target.name)) 235 return os.path.join(self.get_target_dir(target), target.get_filename()) 236 elif isinstance(target, build.Executable): 237 if target.import_filename: 238 return os.path.join(self.get_target_dir(target), target.get_import_filename()) 239 else: 240 return None 241 raise AssertionError('BUG: Tried to link to {!r} which is not linkable'.format(target)) 242 243 @lru_cache(maxsize=None) 244 def get_target_dir(self, target): 245 if self.environment.coredata.get_builtin_option('layout') == 'mirror': 246 dirname = target.get_subdir() 247 else: 248 dirname = 'meson-out' 249 return dirname 250 251 def get_target_dir_relative_to(self, t, o): 252 '''Get a target dir relative to another target's directory''' 253 target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t)) 254 othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o)) 255 return os.path.relpath(target_dir, othert_dir) 256 257 def get_target_source_dir(self, target): 258 # if target dir is empty, avoid extraneous trailing / from os.path.join() 259 target_dir = self.get_target_dir(target) 260 if target_dir: 261 return os.path.join(self.build_to_src, target_dir) 262 return self.build_to_src 263 264 def get_target_private_dir(self, target): 265 return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') 266 267 def get_target_private_dir_abs(self, target): 268 return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) 269 270 @lru_cache(maxsize=None) 271 def get_target_generated_dir(self, target, gensrc, src): 272 """ 273 Takes a BuildTarget, a generator source (CustomTarget or GeneratedList), 274 and a generated source filename. 275 Returns the full path of the generated source relative to the build root 276 """ 277 # CustomTarget generators output to the build dir of the CustomTarget 278 if isinstance(gensrc, (build.CustomTarget, build.CustomTargetIndex)): 279 return os.path.join(self.get_target_dir(gensrc), src) 280 # GeneratedList generators output to the private build directory of the 281 # target that the GeneratedList is used in 282 return os.path.join(self.get_target_private_dir(target), src) 283 284 def get_unity_source_file(self, target, suffix, number): 285 # There is a potential conflict here, but it is unlikely that 286 # anyone both enables unity builds and has a file called foo-unity.cpp. 287 osrc = '{}-unity{}.{}'.format(target.name, number, suffix) 288 return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc) 289 290 def generate_unity_files(self, target, unity_src): 291 abs_files = [] 292 result = [] 293 compsrcs = classify_unity_sources(target.compilers.values(), unity_src) 294 unity_size = self.get_option_for_target('unity_size', target) 295 296 def init_language_file(suffix, unity_file_number): 297 unity_src = self.get_unity_source_file(target, suffix, unity_file_number) 298 outfileabs = unity_src.absolute_path(self.environment.get_source_dir(), 299 self.environment.get_build_dir()) 300 outfileabs_tmp = outfileabs + '.tmp' 301 abs_files.append(outfileabs) 302 outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp) 303 if not os.path.exists(outfileabs_tmp_dir): 304 os.makedirs(outfileabs_tmp_dir) 305 result.append(unity_src) 306 return open(outfileabs_tmp, 'w') 307 308 # For each language, generate unity source files and return the list 309 for comp, srcs in compsrcs.items(): 310 files_in_current = unity_size + 1 311 unity_file_number = 0 312 ofile = None 313 for src in srcs: 314 if files_in_current >= unity_size: 315 if ofile: 316 ofile.close() 317 ofile = init_language_file(comp.get_default_suffix(), unity_file_number) 318 unity_file_number += 1 319 files_in_current = 0 320 ofile.write('#include<{}>\n'.format(src)) 321 files_in_current += 1 322 if ofile: 323 ofile.close() 324 325 [mesonlib.replace_if_different(x, x + '.tmp') for x in abs_files] 326 return result 327 328 def relpath(self, todir, fromdir): 329 return os.path.relpath(os.path.join('dummyprefixdir', todir), 330 os.path.join('dummyprefixdir', fromdir)) 331 332 def flatten_object_list(self, target, proj_dir_to_build_root=''): 333 obj_list = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root) 334 return list(dict.fromkeys(obj_list)) 335 336 def _flatten_object_list(self, target, objects, proj_dir_to_build_root): 337 obj_list = [] 338 for obj in objects: 339 if isinstance(obj, str): 340 o = os.path.join(proj_dir_to_build_root, 341 self.build_to_src, target.get_subdir(), obj) 342 obj_list.append(o) 343 elif isinstance(obj, mesonlib.File): 344 obj_list.append(obj.rel_to_builddir(self.build_to_src)) 345 elif isinstance(obj, build.ExtractedObjects): 346 if obj.recursive: 347 obj_list += self._flatten_object_list(obj.target, obj.objlist, proj_dir_to_build_root) 348 obj_list += self.determine_ext_objs(obj, proj_dir_to_build_root) 349 else: 350 raise MesonException('Unknown data type in object list.') 351 return obj_list 352 353 def as_meson_exe_cmdline(self, tname, exe, cmd_args, workdir=None, 354 for_machine=MachineChoice.BUILD, 355 extra_bdeps=None, capture=None, force_serialize=False): 356 ''' 357 Serialize an executable for running with a generator or a custom target 358 ''' 359 import hashlib 360 machine = self.environment.machines[for_machine] 361 if machine.is_windows() or machine.is_cygwin(): 362 extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or []) 363 else: 364 extra_paths = [] 365 366 if isinstance(exe, dependencies.ExternalProgram): 367 exe_cmd = exe.get_command() 368 exe_for_machine = exe.for_machine 369 elif isinstance(exe, (build.BuildTarget, build.CustomTarget)): 370 exe_cmd = [self.get_target_filename_abs(exe)] 371 exe_for_machine = exe.for_machine 372 else: 373 exe_cmd = [exe] 374 exe_for_machine = MachineChoice.BUILD 375 376 is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine) 377 if is_cross_built and self.environment.need_exe_wrapper(): 378 exe_wrapper = self.environment.get_exe_wrapper() 379 if not exe_wrapper.found(): 380 msg = 'The exe_wrapper {!r} defined in the cross file is ' \ 381 'needed by target {!r}, but was not found. Please ' \ 382 'check the command and/or add it to PATH.' 383 raise MesonException(msg.format(exe_wrapper.name, tname)) 384 else: 385 if exe_cmd[0].endswith('.jar'): 386 exe_cmd = ['java', '-jar'] + exe_cmd 387 elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin()): 388 exe_cmd = ['mono'] + exe_cmd 389 exe_wrapper = None 390 391 force_serialize = force_serialize or extra_paths or workdir or \ 392 exe_wrapper or any('\n' in c for c in cmd_args) 393 if not force_serialize: 394 if not capture: 395 return None 396 return (self.environment.get_build_command() + 397 ['--internal', 'exe', '--capture', capture, '--'] + exe_cmd + cmd_args) 398 399 workdir = workdir or self.environment.get_build_dir() 400 env = {} 401 if isinstance(exe, (dependencies.ExternalProgram, 402 build.BuildTarget, build.CustomTarget)): 403 basename = exe.name 404 else: 405 basename = os.path.basename(exe) 406 407 # Can't just use exe.name here; it will likely be run more than once 408 # Take a digest of the cmd args, env, workdir, and capture. This avoids 409 # collisions and also makes the name deterministic over regenerations 410 # which avoids a rebuild by Ninja because the cmdline stays the same. 411 data = bytes(str(sorted(env.items())) + str(cmd_args) + str(workdir) + str(capture), 412 encoding='utf-8') 413 digest = hashlib.sha1(data).hexdigest() 414 scratch_file = 'meson_exe_{0}_{1}.dat'.format(basename, digest) 415 exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file) 416 with open(exe_data, 'wb') as f: 417 es = ExecutableSerialisation(exe_cmd + cmd_args, env, 418 exe_wrapper, workdir, 419 extra_paths, capture) 420 pickle.dump(es, f) 421 return self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data] 422 423 def serialize_tests(self): 424 test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat') 425 with open(test_data, 'wb') as datafile: 426 self.write_test_file(datafile) 427 benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat') 428 with open(benchmark_data, 'wb') as datafile: 429 self.write_benchmark_file(datafile) 430 return test_data, benchmark_data 431 432 def determine_linker_and_stdlib_args(self, target): 433 ''' 434 If we're building a static library, there is only one static linker. 435 Otherwise, we query the target for the dynamic linker. 436 ''' 437 if isinstance(target, build.StaticLibrary): 438 return self.build.static_linker[target.for_machine], [] 439 l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs() 440 return l, stdlib_args 441 442 @staticmethod 443 def _libdir_is_system(libdir, compilers, env): 444 libdir = os.path.normpath(libdir) 445 for cc in compilers.values(): 446 if libdir in cc.get_library_dirs(env): 447 return True 448 return False 449 450 def get_external_rpath_dirs(self, target): 451 dirs = set() 452 args = [] 453 # FIXME: is there a better way? 454 for lang in ['c', 'cpp']: 455 try: 456 args.extend(self.environment.coredata.get_external_link_args(target.for_machine, lang)) 457 except Exception: 458 pass 459 # Match rpath formats: 460 # -Wl,-rpath= 461 # -Wl,-rpath, 462 rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)') 463 # Match solaris style compat runpath formats: 464 # -Wl,-R 465 # -Wl,-R, 466 runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)') 467 # Match symbols formats: 468 # -Wl,--just-symbols= 469 # -Wl,--just-symbols, 470 symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)') 471 for arg in args: 472 rpath_match = rpath_regex.match(arg) 473 if rpath_match: 474 for dir in rpath_match.group(1).split(':'): 475 dirs.add(dir) 476 runpath_match = runpath_regex.match(arg) 477 if runpath_match: 478 for dir in runpath_match.group(1).split(':'): 479 # The symbols arg is an rpath if the path is a directory 480 if Path(dir).is_dir(): 481 dirs.add(dir) 482 symbols_match = symbols_regex.match(arg) 483 if symbols_match: 484 for dir in symbols_match.group(1).split(':'): 485 # Prevent usage of --just-symbols to specify rpath 486 if Path(dir).is_dir(): 487 raise MesonException('Invalid arg for --just-symbols, {} is a directory.'.format(dir)) 488 return dirs 489 490 def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True): 491 paths = [] 492 for dep in target.external_deps: 493 if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)): 494 continue 495 la = dep.link_args 496 if len(la) != 1 or not os.path.isabs(la[0]): 497 continue 498 # The only link argument is an absolute path to a library file. 499 libpath = la[0] 500 libdir = os.path.dirname(libpath) 501 if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): 502 # No point in adding system paths. 503 continue 504 # Don't remove rpaths specified in LDFLAGS. 505 if libdir in self.get_external_rpath_dirs(target): 506 continue 507 # Windows doesn't support rpaths, but we use this function to 508 # emulate rpaths by setting PATH, so also accept DLLs here 509 if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']: 510 continue 511 if libdir.startswith(self.environment.get_source_dir()): 512 rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:] 513 assert not os.path.isabs(rel_to_src), 'rel_to_src: {} is absolute'.format(rel_to_src) 514 paths.append(os.path.join(self.build_to_src, rel_to_src)) 515 else: 516 paths.append(libdir) 517 return paths 518 519 def determine_rpath_dirs(self, target): 520 if self.environment.coredata.get_builtin_option('layout') == 'mirror': 521 result = target.get_link_dep_subdirs() 522 else: 523 result = OrderedSet() 524 result.add('meson-out') 525 result.update(self.rpaths_for_bundled_shared_libraries(target)) 526 target.rpath_dirs_to_remove.update([d.encode('utf8') for d in result]) 527 return tuple(result) 528 529 @staticmethod 530 def canonicalize_filename(fname): 531 for ch in ('/', '\\', ':'): 532 fname = fname.replace(ch, '_') 533 return fname 534 535 def object_filename_from_source(self, target, source): 536 assert isinstance(source, mesonlib.File) 537 build_dir = self.environment.get_build_dir() 538 rel_src = source.rel_to_builddir(self.build_to_src) 539 540 # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o 541 if rel_src.endswith(('.vala', '.gs')): 542 # See description in generate_vala_compile for this logic. 543 if source.is_built: 544 if os.path.isabs(rel_src): 545 rel_src = rel_src[len(build_dir) + 1:] 546 rel_src = os.path.relpath(rel_src, self.get_target_private_dir(target)) 547 else: 548 rel_src = os.path.basename(rel_src) 549 # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. 550 source = 'meson-generated_' + rel_src[:-5] + '.c' 551 elif source.is_built: 552 if os.path.isabs(rel_src): 553 rel_src = rel_src[len(build_dir) + 1:] 554 targetdir = self.get_target_private_dir(target) 555 # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix. 556 source = 'meson-generated_' + os.path.relpath(rel_src, targetdir) 557 else: 558 if os.path.isabs(rel_src): 559 # Use the absolute path directly to avoid file name conflicts 560 source = rel_src 561 else: 562 source = os.path.relpath(os.path.join(build_dir, rel_src), 563 os.path.join(self.environment.get_source_dir(), target.get_subdir())) 564 machine = self.environment.machines[target.for_machine] 565 return self.canonicalize_filename(source) + '.' + machine.get_object_suffix() 566 567 def determine_ext_objs(self, extobj, proj_dir_to_build_root): 568 result = [] 569 570 # Merge sources and generated sources 571 sources = list(extobj.srclist) 572 for gensrc in extobj.genlist: 573 for s in gensrc.get_outputs(): 574 path = self.get_target_generated_dir(extobj.target, gensrc, s) 575 dirpart, fnamepart = os.path.split(path) 576 sources.append(File(True, dirpart, fnamepart)) 577 578 # Filter out headers and all non-source files 579 filtered_sources = [] 580 for s in sources: 581 if self.environment.is_source(s) and not self.environment.is_header(s): 582 filtered_sources.append(s) 583 elif self.environment.is_object(s): 584 result.append(s.relative_name()) 585 sources = filtered_sources 586 587 # extobj could contain only objects and no sources 588 if not sources: 589 return result 590 591 targetdir = self.get_target_private_dir(extobj.target) 592 593 # With unity builds, sources don't map directly to objects, 594 # we only support extracting all the objects in this mode, 595 # so just return all object files. 596 if self.is_unity(extobj.target): 597 compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources) 598 sources = [] 599 unity_size = self.get_option_for_target('unity_size', extobj.target) 600 for comp, srcs in compsrcs.items(): 601 for i in range(len(srcs) // unity_size + 1): 602 osrc = self.get_unity_source_file(extobj.target, 603 comp.get_default_suffix(), i) 604 sources.append(osrc) 605 606 for osrc in sources: 607 objname = self.object_filename_from_source(extobj.target, osrc) 608 objpath = os.path.join(proj_dir_to_build_root, targetdir, objname) 609 result.append(objpath) 610 611 return result 612 613 def get_pch_include_args(self, compiler, target): 614 args = [] 615 pchpath = self.get_target_private_dir(target) 616 includeargs = compiler.get_include_args(pchpath, False) 617 p = target.get_pch(compiler.get_language()) 618 if p: 619 args += compiler.get_pch_use_args(pchpath, p[0]) 620 return includeargs + args 621 622 def create_msvc_pch_implementation(self, target, lang, pch_header): 623 # We have to include the language in the file name, otherwise 624 # pch.c and pch.cpp will both end up as pch.obj in VS backends. 625 impl_name = 'meson_pch-{}.{}'.format(lang, lang) 626 pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name) 627 # Make sure to prepend the build dir, since the working directory is 628 # not defined. Otherwise, we might create the file in the wrong path. 629 pch_file = os.path.join(self.build_dir, pch_rel_to_build) 630 os.makedirs(os.path.dirname(pch_file), exist_ok=True) 631 632 content = '#include "{}"'.format(os.path.basename(pch_header)) 633 pch_file_tmp = pch_file + '.tmp' 634 with open(pch_file_tmp, 'w') as f: 635 f.write(content) 636 mesonlib.replace_if_different(pch_file, pch_file_tmp) 637 return pch_rel_to_build 638 639 @staticmethod 640 def escape_extra_args(compiler, args): 641 # all backslashes in defines are doubly-escaped 642 extra_args = [] 643 for arg in args: 644 if arg.startswith('-D') or arg.startswith('/D'): 645 arg = arg.replace('\\', '\\\\') 646 extra_args.append(arg) 647 648 return extra_args 649 650 def generate_basic_compiler_args(self, target, compiler, no_warn_args=False): 651 # Create an empty commands list, and start adding arguments from 652 # various sources in the order in which they must override each other 653 # starting from hard-coded defaults followed by build options and so on. 654 commands = compiler.compiler_args() 655 656 copt_proxy = self.get_compiler_options_for_target(target)[compiler.language] 657 # First, the trivial ones that are impossible to override. 658 # 659 # Add -nostdinc/-nostdinc++ if needed; can't be overridden 660 commands += self.get_cross_stdlib_args(target, compiler) 661 # Add things like /NOLOGO or -pipe; usually can't be overridden 662 commands += compiler.get_always_args() 663 # Only add warning-flags by default if the buildtype enables it, and if 664 # we weren't explicitly asked to not emit warnings (for Vala, f.ex) 665 if no_warn_args: 666 commands += compiler.get_no_warn_args() 667 elif self.get_option_for_target('buildtype', target) != 'plain': 668 commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target)) 669 # Add -Werror if werror=true is set in the build options set on the 670 # command-line or default_options inside project(). This only sets the 671 # action to be done for warnings if/when they are emitted, so it's ok 672 # to set it after get_no_warn_args() or get_warn_args(). 673 if self.get_option_for_target('werror', target): 674 commands += compiler.get_werror_args() 675 # Add compile args for c_* or cpp_* build options set on the 676 # command-line or default_options inside project(). 677 commands += compiler.get_option_compile_args(copt_proxy) 678 # Add buildtype args: optimization level, debugging, etc. 679 commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) 680 commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target)) 681 commands += compiler.get_debug_args(self.get_option_for_target('debug', target)) 682 # Add compile args added using add_project_arguments() 683 commands += self.build.get_project_args(compiler, target.subproject, target.for_machine) 684 # Add compile args added using add_global_arguments() 685 # These override per-project arguments 686 commands += self.build.get_global_args(compiler, target.for_machine) 687 # Compile args added from the env: CFLAGS/CXXFLAGS, etc, or the cross 688 # file. We want these to override all the defaults, but not the 689 # per-target compile args. 690 commands += self.environment.coredata.get_external_args(target.for_machine, compiler.get_language()) 691 # Always set -fPIC for shared libraries 692 if isinstance(target, build.SharedLibrary): 693 commands += compiler.get_pic_args() 694 # Set -fPIC for static libraries by default unless explicitly disabled 695 if isinstance(target, build.StaticLibrary) and target.pic: 696 commands += compiler.get_pic_args() 697 if isinstance(target, build.Executable) and target.pie: 698 commands += compiler.get_pie_args() 699 # Add compile args needed to find external dependencies. Link args are 700 # added while generating the link command. 701 # NOTE: We must preserve the order in which external deps are 702 # specified, so we reverse the list before iterating over it. 703 for dep in reversed(target.get_external_deps()): 704 if not dep.found(): 705 continue 706 707 if compiler.language == 'vala': 708 if isinstance(dep, dependencies.PkgConfigDependency): 709 if dep.name == 'glib-2.0' and dep.version_reqs is not None: 710 for req in dep.version_reqs: 711 if req.startswith(('>=', '==')): 712 commands += ['--target-glib', req[2:]] 713 break 714 commands += ['--pkg', dep.name] 715 elif isinstance(dep, dependencies.ExternalLibrary): 716 commands += dep.get_link_args('vala') 717 else: 718 commands += compiler.get_dependency_compile_args(dep) 719 # Qt needs -fPIC for executables 720 # XXX: We should move to -fPIC for all executables 721 if isinstance(target, build.Executable): 722 commands += dep.get_exe_args(compiler) 723 # For 'automagic' deps: Boost and GTest. Also dependency('threads'). 724 # pkg-config puts the thread flags itself via `Cflags:` 725 # Fortran requires extra include directives. 726 if compiler.language == 'fortran': 727 for lt in target.link_targets: 728 priv_dir = self.get_target_private_dir(lt) 729 commands += compiler.get_include_args(priv_dir, False) 730 return commands 731 732 def build_target_link_arguments(self, compiler, deps): 733 args = [] 734 for d in deps: 735 if not (d.is_linkable_target()): 736 raise RuntimeError('Tried to link with a non-library target "{}".'.format(d.get_basename())) 737 arg = self.get_target_filename_for_linking(d) 738 if not arg: 739 continue 740 if compiler.get_language() == 'd': 741 arg = '-Wl,' + arg 742 else: 743 arg = compiler.get_linker_lib_prefix() + arg 744 args.append(arg) 745 return args 746 747 def get_mingw_extra_paths(self, target): 748 paths = OrderedSet() 749 # The cross bindir 750 root = self.environment.properties[target.for_machine].get_root() 751 if root: 752 paths.add(os.path.join(root, 'bin')) 753 # The toolchain bindir 754 sys_root = self.environment.properties[target.for_machine].get_sys_root() 755 if sys_root: 756 paths.add(os.path.join(sys_root, 'bin')) 757 # Get program and library dirs from all target compilers 758 if isinstance(target, build.BuildTarget): 759 for cc in target.compilers.values(): 760 paths.update(cc.get_program_dirs(self.environment)) 761 paths.update(cc.get_library_dirs(self.environment)) 762 return list(paths) 763 764 def determine_windows_extra_paths(self, target: T.Union[build.BuildTarget, str], extra_bdeps): 765 '''On Windows there is no such thing as an rpath. 766 We must determine all locations of DLLs that this exe 767 links to and return them so they can be used in unit 768 tests.''' 769 result = set() 770 prospectives = set() 771 if isinstance(target, build.BuildTarget): 772 prospectives.update(target.get_transitive_link_deps()) 773 # External deps 774 for deppath in self.rpaths_for_bundled_shared_libraries(target, exclude_system=False): 775 result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath))) 776 for bdep in extra_bdeps: 777 prospectives.add(bdep) 778 prospectives.update(bdep.get_transitive_link_deps()) 779 # Internal deps 780 for ld in prospectives: 781 if ld == '' or ld == '.': 782 continue 783 dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld)) 784 result.add(dirseg) 785 if (isinstance(target, build.BuildTarget) and 786 not self.environment.machines.matches_build_machine(target.for_machine)): 787 result.update(self.get_mingw_extra_paths(target)) 788 return list(result) 789 790 def write_benchmark_file(self, datafile): 791 self.write_test_serialisation(self.build.get_benchmarks(), datafile) 792 793 def write_test_file(self, datafile): 794 self.write_test_serialisation(self.build.get_tests(), datafile) 795 796 def create_test_serialisation(self, tests): 797 arr = [] 798 for t in sorted(tests, key=lambda tst: -1 * tst.priority): 799 exe = t.get_exe() 800 if isinstance(exe, dependencies.ExternalProgram): 801 cmd = exe.get_command() 802 else: 803 cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] 804 if isinstance(exe, (build.BuildTarget, dependencies.ExternalProgram)): 805 test_for_machine = exe.for_machine 806 else: 807 # E.g. an external verifier or simulator program run on a generated executable. 808 # Can always be run without a wrapper. 809 test_for_machine = MachineChoice.BUILD 810 811 # we allow passing compiled executables to tests, which may be cross built. 812 # We need to consider these as well when considering whether the target is cross or not. 813 for a in t.cmd_args: 814 if isinstance(a, build.BuildTarget): 815 if a.for_machine is MachineChoice.HOST: 816 test_for_machine = MachineChoice.HOST 817 break 818 819 is_cross = self.environment.is_cross_build(test_for_machine) 820 if is_cross and self.environment.need_exe_wrapper(): 821 exe_wrapper = self.environment.get_exe_wrapper() 822 else: 823 exe_wrapper = None 824 machine = self.environment.machines[exe.for_machine] 825 if machine.is_windows() or machine.is_cygwin(): 826 extra_bdeps = [] 827 if isinstance(exe, build.CustomTarget): 828 extra_bdeps = exe.get_transitive_build_target_deps() 829 extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps) 830 else: 831 extra_paths = [] 832 833 cmd_args = [] 834 for a in unholder(t.cmd_args): 835 if isinstance(a, build.BuildTarget): 836 extra_paths += self.determine_windows_extra_paths(a, []) 837 if isinstance(a, mesonlib.File): 838 a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src)) 839 cmd_args.append(a) 840 elif isinstance(a, str): 841 cmd_args.append(a) 842 elif isinstance(a, build.Executable): 843 p = self.construct_target_rel_path(a, t.workdir) 844 if p == a.get_filename(): 845 p = './' + p 846 cmd_args.append(p) 847 elif isinstance(a, build.Target): 848 cmd_args.append(self.construct_target_rel_path(a, t.workdir)) 849 else: 850 raise MesonException('Bad object in test command.') 851 ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross, 852 exe_wrapper, self.environment.need_exe_wrapper(), 853 t.is_parallel, cmd_args, t.env, 854 t.should_fail, t.timeout, t.workdir, 855 extra_paths, t.protocol, t.priority, 856 isinstance(exe, build.Executable)) 857 arr.append(ts) 858 return arr 859 860 def write_test_serialisation(self, tests, datafile): 861 pickle.dump(self.create_test_serialisation(tests), datafile) 862 863 def construct_target_rel_path(self, a, workdir): 864 if workdir is None: 865 return self.get_target_filename(a) 866 assert(os.path.isabs(workdir)) 867 abs_path = self.get_target_filename_abs(a) 868 return os.path.relpath(abs_path, workdir) 869 870 def generate_depmf_install(self, d): 871 if self.build.dep_manifest_name is None: 872 return 873 ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json') 874 ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name) 875 mfobj = {'type': 'dependency manifest', 'version': '1.0', 'projects': self.build.dep_manifest} 876 with open(ifilename, 'w') as f: 877 f.write(json.dumps(mfobj)) 878 # Copy file from, to, and with mode unchanged 879 d.data.append([ifilename, ofilename, None]) 880 881 def get_regen_filelist(self): 882 '''List of all files whose alteration means that the build 883 definition needs to be regenerated.''' 884 deps = [os.path.join(self.build_to_src, df) 885 for df in self.interpreter.get_build_def_files()] 886 if self.environment.is_cross_build(): 887 deps.extend(self.environment.coredata.cross_files) 888 deps.extend(self.environment.coredata.config_files) 889 deps.append('meson-private/coredata.dat') 890 self.check_clock_skew(deps) 891 return deps 892 893 def check_clock_skew(self, file_list): 894 # If a file that leads to reconfiguration has a time 895 # stamp in the future, it will trigger an eternal reconfigure 896 # loop. 897 import time 898 now = time.time() 899 for f in file_list: 900 absf = os.path.join(self.environment.get_build_dir(), f) 901 ftime = os.path.getmtime(absf) 902 delta = ftime - now 903 # On Windows disk time stamps sometimes point 904 # to the future by a minuscule amount, less than 905 # 0.001 seconds. I don't know why. 906 if delta > 0.001: 907 raise MesonException('Clock skew detected. File {} has a time stamp {:.4f}s in the future.'.format(absf, delta)) 908 909 def build_target_to_cmd_array(self, bt): 910 if isinstance(bt, build.BuildTarget): 911 if isinstance(bt, build.Executable) and bt.for_machine is not MachineChoice.BUILD: 912 if (self.environment.is_cross_build() and 913 self.environment.exe_wrapper is None and 914 self.environment.need_exe_wrapper()): 915 s = textwrap.dedent(''' 916 Cannot use target {} as a generator because it is built for the 917 host machine and no exe wrapper is defined or needs_exe_wrapper is 918 true. You might want to set `native: true` instead to build it for 919 the build machine.'''.format(bt.name)) 920 raise MesonException(s) 921 arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))] 922 else: 923 arr = bt.get_command() 924 return arr 925 926 def replace_extra_args(self, args, genlist): 927 final_args = [] 928 for a in args: 929 if a == '@EXTRA_ARGS@': 930 final_args += genlist.get_extra_args() 931 else: 932 final_args.append(a) 933 return final_args 934 935 def replace_outputs(self, args, private_dir, output_list): 936 newargs = [] 937 regex = re.compile(r'@OUTPUT(\d+)@') 938 for arg in args: 939 m = regex.search(arg) 940 while m is not None: 941 index = int(m.group(1)) 942 src = '@OUTPUT{}@'.format(index) 943 arg = arg.replace(src, os.path.join(private_dir, output_list[index])) 944 m = regex.search(arg) 945 newargs.append(arg) 946 return newargs 947 948 def get_build_by_default_targets(self): 949 result = OrderedDict() 950 # Get all build and custom targets that must be built by default 951 for name, t in self.build.get_targets().items(): 952 if t.build_by_default: 953 result[name] = t 954 # Get all targets used as test executables and arguments. These must 955 # also be built by default. XXX: Sometime in the future these should be 956 # built only before running tests. 957 for t in self.build.get_tests(): 958 exe = unholder(t.exe) 959 if isinstance(exe, (build.CustomTarget, build.BuildTarget)): 960 result[exe.get_id()] = exe 961 for arg in unholder(t.cmd_args): 962 if not isinstance(arg, (build.CustomTarget, build.BuildTarget)): 963 continue 964 result[arg.get_id()] = arg 965 for dep in t.depends: 966 assert isinstance(dep, (build.CustomTarget, build.BuildTarget)) 967 result[dep.get_id()] = dep 968 return result 969 970 @lru_cache(maxsize=None) 971 def get_custom_target_provided_by_generated_source(self, generated_source): 972 libs = [] 973 for f in generated_source.get_outputs(): 974 if self.environment.is_library(f): 975 libs.append(os.path.join(self.get_target_dir(generated_source), f)) 976 return libs 977 978 @lru_cache(maxsize=None) 979 def get_custom_target_provided_libraries(self, target): 980 libs = [] 981 for t in target.get_generated_sources(): 982 if not isinstance(t, build.CustomTarget): 983 continue 984 l = self.get_custom_target_provided_by_generated_source(t) 985 libs = libs + l 986 return libs 987 988 def is_unity(self, target): 989 optval = self.get_option_for_target('unity', target) 990 if optval == 'on' or (optval == 'subprojects' and target.subproject != ''): 991 return True 992 return False 993 994 def get_custom_target_sources(self, target): 995 ''' 996 Custom target sources can be of various object types; strings, File, 997 BuildTarget, even other CustomTargets. 998 Returns the path to them relative to the build root directory. 999 ''' 1000 srcs = [] 1001 for i in unholder(target.get_sources()): 1002 if isinstance(i, str): 1003 fname = [os.path.join(self.build_to_src, target.subdir, i)] 1004 elif isinstance(i, build.BuildTarget): 1005 fname = [self.get_target_filename(i)] 1006 elif isinstance(i, (build.CustomTarget, build.CustomTargetIndex)): 1007 fname = [os.path.join(self.get_target_dir(i), p) for p in i.get_outputs()] 1008 elif isinstance(i, build.GeneratedList): 1009 fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()] 1010 elif isinstance(i, build.ExtractedObjects): 1011 fname = [os.path.join(self.get_target_private_dir(i.target), p) for p in i.get_outputs(self)] 1012 else: 1013 fname = [i.rel_to_builddir(self.build_to_src)] 1014 if target.absolute_paths: 1015 fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname] 1016 srcs += fname 1017 return srcs 1018 1019 def get_custom_target_depend_files(self, target, absolute_paths=False): 1020 deps = [] 1021 for i in target.depend_files: 1022 if isinstance(i, mesonlib.File): 1023 if absolute_paths: 1024 deps.append(i.absolute_path(self.environment.get_source_dir(), 1025 self.environment.get_build_dir())) 1026 else: 1027 deps.append(i.rel_to_builddir(self.build_to_src)) 1028 else: 1029 if absolute_paths: 1030 deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i)) 1031 else: 1032 deps.append(os.path.join(self.build_to_src, target.subdir, i)) 1033 return deps 1034 1035 def eval_custom_target_command(self, target, absolute_outputs=False): 1036 # We want the outputs to be absolute only when using the VS backend 1037 # XXX: Maybe allow the vs backend to use relative paths too? 1038 source_root = self.build_to_src 1039 build_root = '.' 1040 outdir = self.get_target_dir(target) 1041 if absolute_outputs: 1042 source_root = self.environment.get_source_dir() 1043 build_root = self.environment.get_build_dir() 1044 outdir = os.path.join(self.environment.get_build_dir(), outdir) 1045 outputs = [] 1046 for i in target.get_outputs(): 1047 outputs.append(os.path.join(outdir, i)) 1048 inputs = self.get_custom_target_sources(target) 1049 # Evaluate the command list 1050 cmd = [] 1051 for i in target.command: 1052 if isinstance(i, build.BuildTarget): 1053 cmd += self.build_target_to_cmd_array(i) 1054 continue 1055 elif isinstance(i, build.CustomTarget): 1056 # GIR scanner will attempt to execute this binary but 1057 # it assumes that it is in path, so always give it a full path. 1058 tmp = i.get_outputs()[0] 1059 i = os.path.join(self.get_target_dir(i), tmp) 1060 elif isinstance(i, mesonlib.File): 1061 i = i.rel_to_builddir(self.build_to_src) 1062 if target.absolute_paths: 1063 i = os.path.join(self.environment.get_build_dir(), i) 1064 # FIXME: str types are blindly added ignoring 'target.absolute_paths' 1065 # because we can't know if they refer to a file or just a string 1066 elif not isinstance(i, str): 1067 err_msg = 'Argument {0} is of unknown type {1}' 1068 raise RuntimeError(err_msg.format(str(i), str(type(i)))) 1069 else: 1070 if '@SOURCE_ROOT@' in i: 1071 i = i.replace('@SOURCE_ROOT@', source_root) 1072 if '@BUILD_ROOT@' in i: 1073 i = i.replace('@BUILD_ROOT@', build_root) 1074 if '@DEPFILE@' in i: 1075 if target.depfile is None: 1076 msg = 'Custom target {!r} has @DEPFILE@ but no depfile ' \ 1077 'keyword argument.'.format(target.name) 1078 raise MesonException(msg) 1079 dfilename = os.path.join(outdir, target.depfile) 1080 i = i.replace('@DEPFILE@', dfilename) 1081 if '@PRIVATE_DIR@' in i: 1082 if target.absolute_paths: 1083 pdir = self.get_target_private_dir_abs(target) 1084 else: 1085 pdir = self.get_target_private_dir(target) 1086 i = i.replace('@PRIVATE_DIR@', pdir) 1087 if '@PRIVATE_OUTDIR_' in i: 1088 match = re.search(r'@PRIVATE_OUTDIR_(ABS_)?([^/\s*]*)@', i) 1089 if not match: 1090 msg = 'Custom target {!r} has an invalid argument {!r}' \ 1091 ''.format(target.name, i) 1092 raise MesonException(msg) 1093 source = match.group(0) 1094 if match.group(1) is None and not target.absolute_paths: 1095 lead_dir = '' 1096 else: 1097 lead_dir = self.environment.get_build_dir() 1098 i = i.replace(source, os.path.join(lead_dir, outdir)) 1099 cmd.append(i) 1100 # Substitute the rest of the template strings 1101 values = mesonlib.get_filenames_templates_dict(inputs, outputs) 1102 cmd = mesonlib.substitute_values(cmd, values) 1103 # This should not be necessary but removing it breaks 1104 # building GStreamer on Windows. The underlying issue 1105 # is problems with quoting backslashes on Windows 1106 # which is the seventh circle of hell. The downside is 1107 # that this breaks custom targets whose command lines 1108 # have backslashes. If you try to fix this be sure to 1109 # check that it does not break GST. 1110 # 1111 # The bug causes file paths such as c:\foo to get escaped 1112 # into c:\\foo. 1113 # 1114 # Unfortunately we have not been able to come up with an 1115 # isolated test case for this so unless you manage to come up 1116 # with one, the only way is to test the building with Gst's 1117 # setup. Note this in your MR or ping us and we will get it 1118 # fixed. 1119 # 1120 # https://github.com/mesonbuild/meson/pull/737 1121 cmd = [i.replace('\\', '/') for i in cmd] 1122 return inputs, outputs, cmd 1123 1124 def run_postconf_scripts(self): 1125 env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(), 1126 'MESON_BUILD_ROOT': self.environment.get_build_dir(), 1127 'MESONINTROSPECT': ' '.join([shlex.quote(x) for x in self.environment.get_build_command() + ['introspect']]), 1128 } 1129 child_env = os.environ.copy() 1130 child_env.update(env) 1131 1132 for s in self.build.postconf_scripts: 1133 cmd = s['exe'] + s['args'] 1134 subprocess.check_call(cmd, env=child_env) 1135 1136 def create_install_data(self): 1137 strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip') 1138 if strip_bin is None: 1139 if self.environment.is_cross_build(): 1140 mlog.warning('Cross file does not specify strip binary, result will not be stripped.') 1141 else: 1142 # TODO go through all candidates, like others 1143 strip_bin = [self.environment.default_strip[0]] 1144 d = InstallData(self.environment.get_source_dir(), 1145 self.environment.get_build_dir(), 1146 self.environment.get_prefix(), 1147 strip_bin, 1148 self.environment.coredata.get_builtin_option('install_umask'), 1149 self.environment.get_build_command() + ['introspect']) 1150 self.generate_depmf_install(d) 1151 self.generate_target_install(d) 1152 self.generate_header_install(d) 1153 self.generate_man_install(d) 1154 self.generate_data_install(d) 1155 self.generate_custom_install_script(d) 1156 self.generate_subdir_install(d) 1157 return d 1158 1159 def create_install_data_files(self): 1160 install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') 1161 with open(install_data_file, 'wb') as ofile: 1162 pickle.dump(self.create_install_data(), ofile) 1163 1164 def generate_target_install(self, d): 1165 for t in self.build.get_targets().values(): 1166 if not t.should_install(): 1167 continue 1168 outdirs, custom_install_dir = t.get_install_dir(self.environment) 1169 # Sanity-check the outputs and install_dirs 1170 num_outdirs, num_out = len(outdirs), len(t.get_outputs()) 1171 if num_outdirs != 1 and num_outdirs != num_out: 1172 m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \ 1173 "Pass 'false' for outputs that should not be installed and 'true' for\n" \ 1174 'using the default installation directory for an output.' 1175 raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs)) 1176 install_mode = t.get_custom_install_mode() 1177 # Install the target output(s) 1178 if isinstance(t, build.BuildTarget): 1179 # In general, stripping static archives is tricky and full of pitfalls. 1180 # Wholesale stripping of static archives with a command such as 1181 # 1182 # strip libfoo.a 1183 # 1184 # is broken, as GNU's strip will remove *every* symbol in a static 1185 # archive. One solution to this nonintuitive behaviour would be 1186 # to only strip local/debug symbols. Unfortunately, strip arguments 1187 # are not specified by POSIX and therefore not portable. GNU's `-g` 1188 # option (i.e. remove debug symbols) is equivalent to Apple's `-S`. 1189 # 1190 # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more 1191 # fine-grained stripping of static archives. 1192 should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target('strip', t) 1193 # Install primary build output (library/executable/jar, etc) 1194 # Done separately because of strip/aliases/rpath 1195 if outdirs[0] is not False: 1196 mappings = t.get_link_deps_mapping(d.prefix, self.environment) 1197 i = TargetInstallData(self.get_target_filename(t), outdirs[0], 1198 t.get_aliases(), should_strip, mappings, 1199 t.rpath_dirs_to_remove, 1200 t.install_rpath, install_mode) 1201 d.targets.append(i) 1202 1203 if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): 1204 # On toolchains/platforms that use an import library for 1205 # linking (separate from the shared library with all the 1206 # code), we need to install that too (dll.a/.lib). 1207 if t.get_import_filename(): 1208 if custom_install_dir: 1209 # If the DLL is installed into a custom directory, 1210 # install the import library into the same place so 1211 # it doesn't go into a surprising place 1212 implib_install_dir = outdirs[0] 1213 else: 1214 implib_install_dir = self.environment.get_import_lib_dir() 1215 # Install the import library; may not exist for shared modules 1216 i = TargetInstallData(self.get_target_filename_for_linking(t), 1217 implib_install_dir, {}, False, {}, set(), '', install_mode, 1218 optional=isinstance(t, build.SharedModule)) 1219 d.targets.append(i) 1220 1221 if not should_strip and t.get_debug_filename(): 1222 debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) 1223 i = TargetInstallData(debug_file, outdirs[0], 1224 {}, False, {}, set(), '', 1225 install_mode, optional=True) 1226 d.targets.append(i) 1227 # Install secondary outputs. Only used for Vala right now. 1228 if num_outdirs > 1: 1229 for output, outdir in zip(t.get_outputs()[1:], outdirs[1:]): 1230 # User requested that we not install this output 1231 if outdir is False: 1232 continue 1233 f = os.path.join(self.get_target_dir(t), output) 1234 i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode) 1235 d.targets.append(i) 1236 elif isinstance(t, build.CustomTarget): 1237 # If only one install_dir is specified, assume that all 1238 # outputs will be installed into it. This is for 1239 # backwards-compatibility and because it makes sense to 1240 # avoid repetition since this is a common use-case. 1241 # 1242 # To selectively install only some outputs, pass `false` as 1243 # the install_dir for the corresponding output by index 1244 if num_outdirs == 1 and num_out > 1: 1245 for output in t.get_outputs(): 1246 f = os.path.join(self.get_target_dir(t), output) 1247 i = TargetInstallData(f, outdirs[0], {}, False, {}, set(), None, install_mode, 1248 optional=not t.build_by_default) 1249 d.targets.append(i) 1250 else: 1251 for output, outdir in zip(t.get_outputs(), outdirs): 1252 # User requested that we not install this output 1253 if outdir is False: 1254 continue 1255 f = os.path.join(self.get_target_dir(t), output) 1256 i = TargetInstallData(f, outdir, {}, False, {}, set(), None, install_mode, 1257 optional=not t.build_by_default) 1258 d.targets.append(i) 1259 1260 def generate_custom_install_script(self, d): 1261 result = [] 1262 srcdir = self.environment.get_source_dir() 1263 builddir = self.environment.get_build_dir() 1264 for i in self.build.install_scripts: 1265 exe = i['exe'] 1266 args = i['args'] 1267 fixed_args = [] 1268 for a in args: 1269 a = a.replace('@SOURCE_ROOT@', srcdir) 1270 a = a.replace('@BUILD_ROOT@', builddir) 1271 fixed_args.append(a) 1272 result.append(build.RunScript(exe, fixed_args)) 1273 d.install_scripts = result 1274 1275 def generate_header_install(self, d): 1276 incroot = self.environment.get_includedir() 1277 headers = self.build.get_headers() 1278 1279 srcdir = self.environment.get_source_dir() 1280 builddir = self.environment.get_build_dir() 1281 for h in headers: 1282 outdir = h.get_custom_install_dir() 1283 if outdir is None: 1284 outdir = os.path.join(incroot, h.get_install_subdir()) 1285 for f in h.get_sources(): 1286 if not isinstance(f, File): 1287 msg = 'Invalid header type {!r} can\'t be installed' 1288 raise MesonException(msg.format(f)) 1289 abspath = f.absolute_path(srcdir, builddir) 1290 i = [abspath, outdir, h.get_custom_install_mode()] 1291 d.headers.append(i) 1292 1293 def generate_man_install(self, d): 1294 manroot = self.environment.get_mandir() 1295 man = self.build.get_man() 1296 for m in man: 1297 for f in m.get_sources(): 1298 num = f.split('.')[-1] 1299 subdir = m.get_custom_install_dir() 1300 if subdir is None: 1301 subdir = os.path.join(manroot, 'man' + num) 1302 srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir()) 1303 dstabs = os.path.join(subdir, os.path.basename(f.fname)) 1304 i = [srcabs, dstabs, m.get_custom_install_mode()] 1305 d.man.append(i) 1306 1307 def generate_data_install(self, d): 1308 data = self.build.get_data() 1309 srcdir = self.environment.get_source_dir() 1310 builddir = self.environment.get_build_dir() 1311 for de in data: 1312 assert(isinstance(de, build.Data)) 1313 subdir = de.install_dir 1314 if not subdir: 1315 subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name) 1316 for src_file, dst_name in zip(de.sources, de.rename): 1317 assert(isinstance(src_file, mesonlib.File)) 1318 dst_abs = os.path.join(subdir, dst_name) 1319 i = [src_file.absolute_path(srcdir, builddir), dst_abs, de.install_mode] 1320 d.data.append(i) 1321 1322 def generate_subdir_install(self, d): 1323 for sd in self.build.get_install_subdirs(): 1324 src_dir = os.path.join(self.environment.get_source_dir(), 1325 sd.source_subdir, 1326 sd.installable_subdir).rstrip('/') 1327 dst_dir = os.path.join(self.environment.get_prefix(), 1328 sd.install_dir) 1329 if not sd.strip_directory: 1330 dst_dir = os.path.join(dst_dir, os.path.basename(src_dir)) 1331 d.install_subdirs.append([src_dir, dst_dir, sd.install_mode, 1332 sd.exclude]) 1333 1334 def get_introspection_data(self, target_id, target): 1335 ''' 1336 Returns a list of source dicts with the following format for a given target: 1337 [ 1338 { 1339 "language": "<LANG>", 1340 "compiler": ["result", "of", "comp.get_exelist()"], 1341 "parameters": ["list", "of", "compiler", "parameters], 1342 "sources": ["list", "of", "all", "<LANG>", "source", "files"], 1343 "generated_sources": ["list", "of", "generated", "source", "files"] 1344 } 1345 ] 1346 1347 This is a limited fallback / reference implementation. The backend should override this method. 1348 ''' 1349 if isinstance(target, (build.CustomTarget, build.BuildTarget)): 1350 source_list_raw = target.sources + target.extra_files 1351 source_list = [] 1352 for j in source_list_raw: 1353 if isinstance(j, mesonlib.File): 1354 source_list += [j.absolute_path(self.source_dir, self.build_dir)] 1355 elif isinstance(j, str): 1356 source_list += [os.path.join(self.source_dir, j)] 1357 source_list = list(map(lambda x: os.path.normpath(x), source_list)) 1358 1359 compiler = [] 1360 if isinstance(target, build.CustomTarget): 1361 tmp_compiler = target.command 1362 if not isinstance(compiler, list): 1363 tmp_compiler = [compiler] 1364 for j in tmp_compiler: 1365 if isinstance(j, mesonlib.File): 1366 compiler += [j.absolute_path(self.source_dir, self.build_dir)] 1367 elif isinstance(j, str): 1368 compiler += [j] 1369 elif isinstance(j, (build.BuildTarget, build.CustomTarget)): 1370 compiler += j.get_outputs() 1371 else: 1372 raise RuntimeError('Type "{}" is not supported in get_introspection_data. This is a bug'.format(type(j).__name__)) 1373 1374 return [{ 1375 'language': 'unknown', 1376 'compiler': compiler, 1377 'parameters': [], 1378 'sources': source_list, 1379 'generated_sources': [] 1380 }] 1381 1382 return [] 1383