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