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