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