1# Copyright 2012-2020 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
15import itertools
16import os, platform, re, sys, shutil
17import typing as T
18import collections
19
20from . import coredata
21from . import mesonlib
22from .mesonlib import (
23    MesonException, EnvironmentException, MachineChoice, Popen_safe, PerMachine,
24    PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey,
25    search_version, MesonBugException
26)
27from . import mlog
28from .programs import (
29    ExternalProgram, EmptyExternalProgram
30)
31
32from .envconfig import (
33    BinaryTable, MachineInfo, Properties, known_cpu_families, CMakeVariables,
34)
35from . import compilers
36from .compilers import (
37    Compiler,
38    is_assembly,
39    is_header,
40    is_library,
41    is_llvm_ir,
42    is_object,
43    is_source,
44)
45
46from functools import lru_cache
47from mesonbuild import envconfig
48
49if T.TYPE_CHECKING:
50    from configparser import ConfigParser
51
52    from .wrap.wrap import Resolver
53
54build_filename = 'meson.build'
55
56CompilersDict = T.Dict[str, Compiler]
57
58if T.TYPE_CHECKING:
59    import argparse
60
61
62def _get_env_var(for_machine: MachineChoice, is_cross: bool, var_name: str) -> T.Optional[str]:
63    """
64    Returns the exact env var and the value.
65    """
66    candidates = PerMachine(
67        # The prefixed build version takes priority, but if we are native
68        # compiling we fall back on the unprefixed host version. This
69        # allows native builds to never need to worry about the 'BUILD_*'
70        # ones.
71        ([var_name + '_FOR_BUILD'] if is_cross else [var_name]),
72        # Always just the unprefixed host versions
73        [var_name]
74    )[for_machine]
75    for var in candidates:
76        value = os.environ.get(var)
77        if value is not None:
78            break
79    else:
80        formatted = ', '.join([f'{var!r}' for var in candidates])
81        mlog.debug(f'None of {formatted} are defined in the environment, not changing global flags.')
82        return None
83    mlog.debug(f'Using {var!r} from environment with value: {value!r}')
84    return value
85
86
87def detect_gcovr(min_version='3.3', log=False):
88    gcovr_exe = 'gcovr'
89    try:
90        p, found = Popen_safe([gcovr_exe, '--version'])[0:2]
91    except (FileNotFoundError, PermissionError):
92        # Doesn't exist in PATH or isn't executable
93        return None, None
94    found = search_version(found)
95    if p.returncode == 0 and mesonlib.version_compare(found, '>=' + min_version):
96        if log:
97            mlog.log('Found gcovr-{} at {}'.format(found, quote_arg(shutil.which(gcovr_exe))))
98        return gcovr_exe, found
99    return None, None
100
101def detect_llvm_cov():
102    tools = get_llvm_tool_names('llvm-cov')
103    for tool in tools:
104        if mesonlib.exe_exists([tool, '--version']):
105            return tool
106    return None
107
108def find_coverage_tools() -> T.Tuple[T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str], T.Optional[str]]:
109    gcovr_exe, gcovr_version = detect_gcovr()
110
111    llvm_cov_exe = detect_llvm_cov()
112
113    lcov_exe = 'lcov'
114    genhtml_exe = 'genhtml'
115
116    if not mesonlib.exe_exists([lcov_exe, '--version']):
117        lcov_exe = None
118    if not mesonlib.exe_exists([genhtml_exe, '--version']):
119        genhtml_exe = None
120
121    return gcovr_exe, gcovr_version, lcov_exe, genhtml_exe, llvm_cov_exe
122
123def detect_ninja(version: str = '1.8.2', log: bool = False) -> T.List[str]:
124    r = detect_ninja_command_and_version(version, log)
125    return r[0] if r else None
126
127def detect_ninja_command_and_version(version: str = '1.8.2', log: bool = False) -> T.Tuple[T.List[str], str]:
128    env_ninja = os.environ.get('NINJA', None)
129    for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']:
130        prog = ExternalProgram(n, silent=True)
131        if not prog.found():
132            continue
133        try:
134            p, found = Popen_safe(prog.command + ['--version'])[0:2]
135        except (FileNotFoundError, PermissionError):
136            # Doesn't exist in PATH or isn't executable
137            continue
138        found = found.strip()
139        # Perhaps we should add a way for the caller to know the failure mode
140        # (not found or too old)
141        if p.returncode == 0 and mesonlib.version_compare(found, '>=' + version):
142            if log:
143                name = os.path.basename(n)
144                if name.endswith('-' + found):
145                    name = name[0:-1 - len(found)]
146                if name == 'ninja-build':
147                    name = 'ninja'
148                if name == 'samu':
149                    name = 'samurai'
150                mlog.log('Found {}-{} at {}'.format(name, found,
151                         ' '.join([quote_arg(x) for x in prog.command])))
152            return (prog.command, found)
153
154def get_llvm_tool_names(tool: str) -> T.List[str]:
155    # Ordered list of possible suffixes of LLVM executables to try. Start with
156    # base, then try newest back to oldest (3.5 is arbitrary), and finally the
157    # devel version. Please note that the development snapshot in Debian does
158    # not have a distinct name. Do not move it to the beginning of the list
159    # unless it becomes a stable release.
160    suffixes = [
161        '', # base (no suffix)
162        '-12',  '12',
163        '-11',  '11',
164        '-10',  '10',
165        '-9',   '90',
166        '-8',   '80',
167        '-7',   '70',
168        '-6.0', '60',
169        '-5.0', '50',
170        '-4.0', '40',
171        '-3.9', '39',
172        '-3.8', '38',
173        '-3.7', '37',
174        '-3.6', '36',
175        '-3.5', '35',
176        '-13',    # Debian development snapshot
177        '-devel', # FreeBSD development snapshot
178    ]
179    names = []
180    for suffix in suffixes:
181        names.append(tool + suffix)
182    return names
183
184def detect_scanbuild() -> T.List[str]:
185    """ Look for scan-build binary on build platform
186
187    First, if a SCANBUILD env variable has been provided, give it precedence
188    on all platforms.
189
190    For most platforms, scan-build is found is the PATH contains a binary
191    named "scan-build". However, some distribution's package manager (FreeBSD)
192    don't. For those, loop through a list of candidates to see if one is
193    available.
194
195    Return: a single-element list of the found scan-build binary ready to be
196        passed to Popen()
197    """
198    exelist = []
199    if 'SCANBUILD' in os.environ:
200        exelist = split_args(os.environ['SCANBUILD'])
201
202    else:
203        tools = get_llvm_tool_names('scan-build')
204        for tool in tools:
205            if shutil.which(tool) is not None:
206                exelist = [shutil.which(tool)]
207                break
208
209    if exelist:
210        tool = exelist[0]
211        if os.path.isfile(tool) and os.access(tool, os.X_OK):
212            return [tool]
213    return []
214
215def detect_clangformat() -> T.List[str]:
216    """ Look for clang-format binary on build platform
217
218    Do the same thing as detect_scanbuild to find clang-format except it
219    currently does not check the environment variable.
220
221    Return: a single-element list of the found clang-format binary ready to be
222        passed to Popen()
223    """
224    tools = get_llvm_tool_names('clang-format')
225    for tool in tools:
226        path = shutil.which(tool)
227        if path is not None:
228            return [path]
229    return []
230
231def detect_native_windows_arch():
232    """
233    The architecture of Windows itself: x86, amd64 or arm64
234    """
235    # These env variables are always available. See:
236    # https://msdn.microsoft.com/en-us/library/aa384274(VS.85).aspx
237    # https://blogs.msdn.microsoft.com/david.wang/2006/03/27/howto-detect-process-bitness/
238    arch = os.environ.get('PROCESSOR_ARCHITEW6432', '').lower()
239    if not arch:
240        try:
241            # If this doesn't exist, something is messing with the environment
242            arch = os.environ['PROCESSOR_ARCHITECTURE'].lower()
243        except KeyError:
244            raise EnvironmentException('Unable to detect native OS architecture')
245    return arch
246
247def detect_windows_arch(compilers: CompilersDict) -> str:
248    """
249    Detecting the 'native' architecture of Windows is not a trivial task. We
250    cannot trust that the architecture that Python is built for is the 'native'
251    one because you can run 32-bit apps on 64-bit Windows using WOW64 and
252    people sometimes install 32-bit Python on 64-bit Windows.
253
254    We also can't rely on the architecture of the OS itself, since it's
255    perfectly normal to compile and run 32-bit applications on Windows as if
256    they were native applications. It's a terrible experience to require the
257    user to supply a cross-info file to compile 32-bit applications on 64-bit
258    Windows. Thankfully, the only way to compile things with Visual Studio on
259    Windows is by entering the 'msvc toolchain' environment, which can be
260    easily detected.
261
262    In the end, the sanest method is as follows:
263    1. Check environment variables that are set by Windows and WOW64 to find out
264       if this is x86 (possibly in WOW64), if so use that as our 'native'
265       architecture.
266    2. If the compiler toolchain target architecture is x86, use that as our
267      'native' architecture.
268    3. Otherwise, use the actual Windows architecture
269
270    """
271    os_arch = detect_native_windows_arch()
272    if os_arch == 'x86':
273        return os_arch
274    # If we're on 64-bit Windows, 32-bit apps can be compiled without
275    # cross-compilation. So if we're doing that, just set the native arch as
276    # 32-bit and pretend like we're running under WOW64. Else, return the
277    # actual Windows architecture that we deduced above.
278    for compiler in compilers.values():
279        if compiler.id == 'msvc' and (compiler.target == 'x86' or compiler.target == '80x86'):
280            return 'x86'
281        if compiler.id == 'clang-cl' and compiler.target == 'x86':
282            return 'x86'
283        if compiler.id == 'gcc' and compiler.has_builtin_define('__i386__'):
284            return 'x86'
285    return os_arch
286
287def any_compiler_has_define(compilers: CompilersDict, define):
288    for c in compilers.values():
289        try:
290            if c.has_builtin_define(define):
291                return True
292        except mesonlib.MesonException:
293            # Ignore compilers that do not support has_builtin_define.
294            pass
295    return False
296
297def detect_cpu_family(compilers: CompilersDict) -> str:
298    """
299    Python is inconsistent in its platform module.
300    It returns different values for the same cpu.
301    For x86 it might return 'x86', 'i686' or somesuch.
302    Do some canonicalization.
303    """
304    if mesonlib.is_windows():
305        trial = detect_windows_arch(compilers)
306    elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_qnx() or mesonlib.is_aix() or mesonlib.is_dragonflybsd():
307        trial = platform.processor().lower()
308    else:
309        trial = platform.machine().lower()
310    if trial.startswith('i') and trial.endswith('86'):
311        trial = 'x86'
312    elif trial == 'bepc':
313        trial = 'x86'
314    elif trial == 'arm64':
315        trial = 'aarch64'
316    elif trial.startswith('aarch64'):
317        # This can be `aarch64_be`
318        trial = 'aarch64'
319    elif trial.startswith('arm') or trial.startswith('earm'):
320        trial = 'arm'
321    elif trial.startswith(('powerpc64', 'ppc64')):
322        trial = 'ppc64'
323    elif trial.startswith(('powerpc', 'ppc')) or trial in {'macppc', 'power macintosh'}:
324        trial = 'ppc'
325    elif trial in ('amd64', 'x64', 'i86pc'):
326        trial = 'x86_64'
327    elif trial in {'sun4u', 'sun4v'}:
328        trial = 'sparc64'
329    elif trial.startswith('mips'):
330        if '64' not in trial:
331            trial = 'mips'
332        else:
333            trial = 'mips64'
334    elif trial in {'ip30', 'ip35'}:
335        trial = 'mips64'
336
337    # On Linux (and maybe others) there can be any mixture of 32/64 bit code in
338    # the kernel, Python, system, 32-bit chroot on 64-bit host, etc. The only
339    # reliable way to know is to check the compiler defines.
340    if trial == 'x86_64':
341        if any_compiler_has_define(compilers, '__i386__'):
342            trial = 'x86'
343    elif trial == 'aarch64':
344        if any_compiler_has_define(compilers, '__arm__'):
345            trial = 'arm'
346    # Add more quirks here as bugs are reported. Keep in sync with detect_cpu()
347    # below.
348    elif trial == 'parisc64':
349        # ATM there is no 64 bit userland for PA-RISC. Thus always
350        # report it as 32 bit for simplicity.
351        trial = 'parisc'
352    elif trial == 'ppc':
353        # AIX always returns powerpc, check here for 64-bit
354        if any_compiler_has_define(compilers, '__64BIT__'):
355            trial = 'ppc64'
356
357    if trial not in known_cpu_families:
358        mlog.warning(f'Unknown CPU family {trial!r}, please report this at '
359                     'https://github.com/mesonbuild/meson/issues/new with the '
360                     'output of `uname -a` and `cat /proc/cpuinfo`')
361
362    return trial
363
364def detect_cpu(compilers: CompilersDict) -> str:
365    if mesonlib.is_windows():
366        trial = detect_windows_arch(compilers)
367    elif mesonlib.is_freebsd() or mesonlib.is_netbsd() or mesonlib.is_openbsd() or mesonlib.is_aix() or mesonlib.is_dragonflybsd():
368        trial = platform.processor().lower()
369    else:
370        trial = platform.machine().lower()
371
372    if trial in ('amd64', 'x64', 'i86pc'):
373        trial = 'x86_64'
374    if trial == 'x86_64':
375        # Same check as above for cpu_family
376        if any_compiler_has_define(compilers, '__i386__'):
377            trial = 'i686' # All 64 bit cpus have at least this level of x86 support.
378    elif trial.startswith('aarch64'):
379        # Same check as above for cpu_family
380        if any_compiler_has_define(compilers, '__arm__'):
381            trial = 'arm'
382        else:
383            # for aarch64_be
384            trial = 'aarch64'
385    elif trial.startswith('earm'):
386        trial = 'arm'
387    elif trial == 'e2k':
388        # Make more precise CPU detection for Elbrus platform.
389        trial = platform.processor().lower()
390    elif trial.startswith('mips'):
391        if '64' not in trial:
392            trial = 'mips'
393        else:
394            trial = 'mips64'
395    elif trial == 'ppc':
396        # AIX always returns powerpc, check here for 64-bit
397        if any_compiler_has_define(compilers, '__64BIT__'):
398            trial = 'ppc64'
399
400    # Add more quirks here as bugs are reported. Keep in sync with
401    # detect_cpu_family() above.
402    return trial
403
404def detect_system() -> str:
405    if sys.platform == 'cygwin':
406        return 'cygwin'
407    return platform.system().lower()
408
409def detect_msys2_arch() -> T.Optional[str]:
410    return os.environ.get('MSYSTEM_CARCH', None)
411
412def detect_machine_info(compilers: T.Optional[CompilersDict] = None) -> MachineInfo:
413    """Detect the machine we're running on
414
415    If compilers are not provided, we cannot know as much. None out those
416    fields to avoid accidentally depending on partial knowledge. The
417    underlying ''detect_*'' method can be called to explicitly use the
418    partial information.
419    """
420    return MachineInfo(
421        detect_system(),
422        detect_cpu_family(compilers) if compilers is not None else None,
423        detect_cpu(compilers) if compilers is not None else None,
424        sys.byteorder)
425
426# TODO make this compare two `MachineInfo`s purely. How important is the
427# `detect_cpu_family({})` distinction? It is the one impediment to that.
428def machine_info_can_run(machine_info: MachineInfo):
429    """Whether we can run binaries for this machine on the current machine.
430
431    Can almost always run 32-bit binaries on 64-bit natively if the host
432    and build systems are the same. We don't pass any compilers to
433    detect_cpu_family() here because we always want to know the OS
434    architecture, not what the compiler environment tells us.
435    """
436    if machine_info.system != detect_system():
437        return False
438    true_build_cpu_family = detect_cpu_family({})
439    return \
440        (machine_info.cpu_family == true_build_cpu_family) or \
441        ((true_build_cpu_family == 'x86_64') and (machine_info.cpu_family == 'x86')) or \
442        ((true_build_cpu_family == 'aarch64') and (machine_info.cpu_family == 'arm'))
443
444class Environment:
445    private_dir = 'meson-private'
446    log_dir = 'meson-logs'
447    info_dir = 'meson-info'
448
449    def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], options: 'argparse.Namespace') -> None:
450        self.source_dir = source_dir
451        self.build_dir = build_dir
452        # Do not try to create build directories when build_dir is none.
453        # This reduced mode is used by the --buildoptions introspector
454        if build_dir is not None:
455            self.scratch_dir = os.path.join(build_dir, Environment.private_dir)
456            self.log_dir = os.path.join(build_dir, Environment.log_dir)
457            self.info_dir = os.path.join(build_dir, Environment.info_dir)
458            os.makedirs(self.scratch_dir, exist_ok=True)
459            os.makedirs(self.log_dir, exist_ok=True)
460            os.makedirs(self.info_dir, exist_ok=True)
461            try:
462                self.coredata = coredata.load(self.get_build_dir())  # type: coredata.CoreData
463                self.first_invocation = False
464            except FileNotFoundError:
465                self.create_new_coredata(options)
466            except coredata.MesonVersionMismatchException as e:
467                # This is routine, but tell the user the update happened
468                mlog.log('Regenerating configuration from scratch:', str(e))
469                coredata.read_cmd_line_file(self.build_dir, options)
470                self.create_new_coredata(options)
471            except MesonException as e:
472                # If we stored previous command line options, we can recover from
473                # a broken/outdated coredata.
474                if os.path.isfile(coredata.get_cmd_line_file(self.build_dir)):
475                    mlog.warning('Regenerating configuration from scratch.')
476                    mlog.log('Reason:', mlog.red(str(e)))
477                    coredata.read_cmd_line_file(self.build_dir, options)
478                    self.create_new_coredata(options)
479                else:
480                    raise e
481        else:
482            # Just create a fresh coredata in this case
483            self.scratch_dir = ''
484            self.create_new_coredata(options)
485
486        ## locally bind some unfrozen configuration
487
488        # Stores machine infos, the only *three* machine one because we have a
489        # target machine info on for the user (Meson never cares about the
490        # target machine.)
491        machines: PerThreeMachineDefaultable[MachineInfo] = PerThreeMachineDefaultable()
492
493        # Similar to coredata.compilers, but lower level in that there is no
494        # meta data, only names/paths.
495        binaries = PerMachineDefaultable()  # type: PerMachineDefaultable[BinaryTable]
496
497        # Misc other properties about each machine.
498        properties = PerMachineDefaultable()  # type: PerMachineDefaultable[Properties]
499
500        # CMake toolchain variables
501        cmakevars = PerMachineDefaultable()  # type: PerMachineDefaultable[CMakeVariables]
502
503        ## Setup build machine defaults
504
505        # Will be fully initialized later using compilers later.
506        machines.build = detect_machine_info()
507
508        # Just uses hard-coded defaults and environment variables. Might be
509        # overwritten by a native file.
510        binaries.build = BinaryTable()
511        properties.build = Properties()
512
513        # Options with the key parsed into an OptionKey type.
514        #
515        # Note that order matters because of 'buildtype', if it is after
516        # 'optimization' and 'debug' keys, it override them.
517        self.options: T.MutableMapping[OptionKey, T.Union[str, T.List[str]]] = collections.OrderedDict()
518
519        ## Read in native file(s) to override build machine configuration
520
521        if self.coredata.config_files is not None:
522            config = coredata.parse_machine_files(self.coredata.config_files)
523            binaries.build = BinaryTable(config.get('binaries', {}))
524            properties.build = Properties(config.get('properties', {}))
525            cmakevars.build = CMakeVariables(config.get('cmake', {}))
526            self._load_machine_file_options(
527                config, properties.build,
528                MachineChoice.BUILD if self.coredata.cross_files else MachineChoice.HOST)
529
530        ## Read in cross file(s) to override host machine configuration
531
532        if self.coredata.cross_files:
533            config = coredata.parse_machine_files(self.coredata.cross_files)
534            properties.host = Properties(config.get('properties', {}))
535            binaries.host = BinaryTable(config.get('binaries', {}))
536            cmakevars.host = CMakeVariables(config.get('cmake', {}))
537            if 'host_machine' in config:
538                machines.host = MachineInfo.from_literal(config['host_machine'])
539            if 'target_machine' in config:
540                machines.target = MachineInfo.from_literal(config['target_machine'])
541            # Keep only per machine options from the native file. The cross
542            # file takes precedence over all other options.
543            for key, value in list(self.options.items()):
544                if self.coredata.is_per_machine_option(key):
545                    self.options[key.as_build()] = value
546            self._load_machine_file_options(config, properties.host, MachineChoice.HOST)
547
548        ## "freeze" now initialized configuration, and "save" to the class.
549
550        self.machines = machines.default_missing()
551        self.binaries = binaries.default_missing()
552        self.properties = properties.default_missing()
553        self.cmakevars = cmakevars.default_missing()
554
555        # Command line options override those from cross/native files
556        self.options.update(options.cmd_line_options)
557
558        # Take default value from env if not set in cross/native files or command line.
559        self._set_default_options_from_env()
560        self._set_default_binaries_from_env()
561        self._set_default_properties_from_env()
562
563        # Warn if the user is using two different ways of setting build-type
564        # options that override each other
565        bt = OptionKey('buildtype')
566        db = OptionKey('debug')
567        op = OptionKey('optimization')
568        if bt in self.options and (db in self.options or op in self.options):
569            mlog.warning('Recommend using either -Dbuildtype or -Doptimization + -Ddebug. '
570                         'Using both is redundant since they override each other. '
571                         'See: https://mesonbuild.com/Builtin-options.html#build-type-options')
572
573        exe_wrapper = self.lookup_binary_entry(MachineChoice.HOST, 'exe_wrapper')
574        if exe_wrapper is not None:
575            self.exe_wrapper = ExternalProgram.from_bin_list(self, MachineChoice.HOST, 'exe_wrapper')
576        else:
577            self.exe_wrapper = None
578
579        self.default_cmake = ['cmake']
580        self.default_pkgconfig = ['pkg-config']
581        self.wrap_resolver: T.Optional['Resolver'] = None
582
583    def _load_machine_file_options(self, config: 'ConfigParser', properties: Properties, machine: MachineChoice) -> None:
584        """Read the contents of a Machine file and put it in the options store."""
585
586        # Look for any options in the deprecated paths section, warn about
587        # those, then assign them. They will be overwritten by the ones in the
588        # "built-in options" section if they're in both sections.
589        paths = config.get('paths')
590        if paths:
591            mlog.deprecation('The [paths] section is deprecated, use the [built-in options] section instead.')
592            for k, v in paths.items():
593                self.options[OptionKey.from_string(k).evolve(machine=machine)] = v
594
595        # Next look for compiler options in the "properties" section, this is
596        # also deprecated, and these will also be overwritten by the "built-in
597        # options" section. We need to remove these from this section, as well.
598        deprecated_properties: T.Set[str] = set()
599        for lang in compilers.all_languages:
600            deprecated_properties.add(lang + '_args')
601            deprecated_properties.add(lang + '_link_args')
602        for k, v in properties.properties.copy().items():
603            if k in deprecated_properties:
604                mlog.deprecation(f'{k} in the [properties] section of the machine file is deprecated, use the [built-in options] section.')
605                self.options[OptionKey.from_string(k).evolve(machine=machine)] = v
606                del properties.properties[k]
607
608        for section, values in config.items():
609            if ':' in section:
610                subproject, section = section.split(':')
611            else:
612                subproject = ''
613            if section == 'built-in options':
614                for k, v in values.items():
615                    key = OptionKey.from_string(k)
616                    # If we're in the cross file, and there is a `build.foo` warn about that. Later we'll remove it.
617                    if machine is MachineChoice.HOST and key.machine is not machine:
618                        mlog.deprecation('Setting build machine options in cross files, please use a native file instead, this will be removed in meson 0.60', once=True)
619                    if key.subproject:
620                        raise MesonException('Do not set subproject options in [built-in options] section, use [subproject:built-in options] instead.')
621                    self.options[key.evolve(subproject=subproject, machine=machine)] = v
622            elif section == 'project options' and machine is MachineChoice.HOST:
623                # Project options are only for the host machine, we don't want
624                # to read these from the native file
625                for k, v in values.items():
626                    # Project options are always for the host machine
627                    key = OptionKey.from_string(k)
628                    if key.subproject:
629                        raise MesonException('Do not set subproject options in [built-in options] section, use [subproject:built-in options] instead.')
630                    self.options[key.evolve(subproject=subproject)] = v
631
632    def _set_default_options_from_env(self) -> None:
633        opts: T.List[T.Tuple[str, str]] = (
634            [(v, f'{k}_args') for k, v in compilers.compilers.CFLAGS_MAPPING.items()] +
635            [
636                ('PKG_CONFIG_PATH', 'pkg_config_path'),
637                ('CMAKE_PREFIX_PATH', 'cmake_prefix_path'),
638                ('LDFLAGS', 'ldflags'),
639                ('CPPFLAGS', 'cppflags'),
640            ]
641        )
642
643        env_opts: T.DefaultDict[OptionKey, T.List[str]] = collections.defaultdict(list)
644
645        for (evar, keyname), for_machine in itertools.product(opts, MachineChoice):
646            p_env = _get_env_var(for_machine, self.is_cross_build(), evar)
647            if p_env is not None:
648                # these may contain duplicates, which must be removed, else
649                # a duplicates-in-array-option warning arises.
650                if keyname == 'cmake_prefix_path':
651                    if self.machines[for_machine].is_windows():
652                        # Cannot split on ':' on Windows because its in the drive letter
653                        _p_env = p_env.split(os.pathsep)
654                    else:
655                        # https://github.com/mesonbuild/meson/issues/7294
656                        _p_env = re.split(r':|;', p_env)
657                    p_list = list(mesonlib.OrderedSet(_p_env))
658                elif keyname == 'pkg_config_path':
659                    p_list = list(mesonlib.OrderedSet(p_env.split(':')))
660                else:
661                    p_list = split_args(p_env)
662                p_list = [e for e in p_list if e]  # filter out any empty elements
663
664                # Take env vars only on first invocation, if the env changes when
665                # reconfiguring it gets ignored.
666                # FIXME: We should remember if we took the value from env to warn
667                # if it changes on future invocations.
668                if self.first_invocation:
669                    if keyname == 'ldflags':
670                        key = OptionKey('link_args', machine=for_machine, lang='c')  # needs a language to initialize properly
671                        for lang in compilers.compilers.LANGUAGES_USING_LDFLAGS:
672                            key = key.evolve(lang=lang)
673                            env_opts[key].extend(p_list)
674                    elif keyname == 'cppflags':
675                        key = OptionKey('env_args', machine=for_machine, lang='c')
676                        for lang in compilers.compilers.LANGUAGES_USING_CPPFLAGS:
677                            key = key.evolve(lang=lang)
678                            env_opts[key].extend(p_list)
679                    else:
680                        key = OptionKey.from_string(keyname).evolve(machine=for_machine)
681                        if evar in compilers.compilers.CFLAGS_MAPPING.values():
682                            # If this is an environment variable, we have to
683                            # store it separately until the compiler is
684                            # instantiated, as we don't know whether the
685                            # compiler will want to use these arguments at link
686                            # time and compile time (instead of just at compile
687                            # time) until we're instantiating that `Compiler`
688                            # object. This is required so that passing
689                            # `-Dc_args=` on the command line and `$CFLAGS`
690                            # have subtely different behavior. `$CFLAGS` will be
691                            # added to the linker command line if the compiler
692                            # acts as a linker driver, `-Dc_args` will not.
693                            #
694                            # We still use the original key as the base here, as
695                            # we want to inhert the machine and the compiler
696                            # language
697                            key = key.evolve('env_args')
698                        env_opts[key].extend(p_list)
699
700        # Only store options that are not already in self.options,
701        # otherwise we'd override the machine files
702        for k, v in env_opts.items():
703            if k not in self.options:
704                self.options[k] = v
705
706    def _set_default_binaries_from_env(self) -> None:
707        """Set default binaries from the environment.
708
709        For example, pkg-config can be set via PKG_CONFIG, or in the machine
710        file. We want to set the default to the env variable.
711        """
712        opts = itertools.chain(envconfig.DEPRECATED_ENV_PROG_MAP.items(),
713                               envconfig.ENV_VAR_PROG_MAP.items())
714
715        for (name, evar), for_machine in itertools.product(opts, MachineChoice):
716            p_env = _get_env_var(for_machine, self.is_cross_build(), evar)
717            if p_env is not None:
718                self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env))
719
720    def _set_default_properties_from_env(self) -> None:
721        """Properties which can also be set from the environment."""
722        # name, evar, split
723        opts: T.List[T.Tuple[str, T.List[str], bool]] = [
724            ('boost_includedir', ['BOOST_INCLUDEDIR'], False),
725            ('boost_librarydir', ['BOOST_LIBRARYDIR'], False),
726            ('boost_root', ['BOOST_ROOT', 'BOOSTROOT'], True),
727            ('java_home', ['JAVA_HOME'], False),
728        ]
729
730        for (name, evars, split), for_machine in itertools.product(opts, MachineChoice):
731            for evar in evars:
732                p_env = _get_env_var(for_machine, self.is_cross_build(), evar)
733                if p_env is not None:
734                    if split:
735                        self.properties[for_machine].properties.setdefault(name, p_env.split(os.pathsep))
736                    else:
737                        self.properties[for_machine].properties.setdefault(name, p_env)
738                    break
739
740    def create_new_coredata(self, options: 'argparse.Namespace') -> None:
741        # WARNING: Don't use any values from coredata in __init__. It gets
742        # re-initialized with project options by the interpreter during
743        # build file parsing.
744        # meson_command is used by the regenchecker script, which runs meson
745        self.coredata = coredata.CoreData(options, self.scratch_dir, mesonlib.get_meson_command())
746        self.first_invocation = True
747
748    def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool:
749        return self.coredata.is_cross_build(when_building_for)
750
751    def dump_coredata(self) -> str:
752        return coredata.save(self.coredata, self.get_build_dir())
753
754    def get_log_dir(self) -> str:
755        return self.log_dir
756
757    def get_coredata(self) -> coredata.CoreData:
758        return self.coredata
759
760    @staticmethod
761    def get_build_command(unbuffered: bool = False) -> T.List[str]:
762        cmd = mesonlib.get_meson_command()
763        if cmd is None:
764            raise MesonBugException('No command?')
765        cmd = cmd.copy()
766        if unbuffered and 'python' in os.path.basename(cmd[0]):
767            cmd.insert(1, '-u')
768        return cmd
769
770    def is_header(self, fname: 'mesonlib.FileOrString') -> bool:
771        return is_header(fname)
772
773    def is_source(self, fname: 'mesonlib.FileOrString') -> bool:
774        return is_source(fname)
775
776    def is_assembly(self, fname: 'mesonlib.FileOrString') -> bool:
777        return is_assembly(fname)
778
779    def is_llvm_ir(self, fname: 'mesonlib.FileOrString') -> bool:
780        return is_llvm_ir(fname)
781
782    def is_object(self, fname: 'mesonlib.FileOrString') -> bool:
783        return is_object(fname)
784
785    @lru_cache(maxsize=None)
786    def is_library(self, fname):
787        return is_library(fname)
788
789    def lookup_binary_entry(self, for_machine: MachineChoice, name: str) -> T.Optional[T.List[str]]:
790        return self.binaries[for_machine].lookup_entry(name)
791
792    def get_scratch_dir(self) -> str:
793        return self.scratch_dir
794
795    def get_source_dir(self) -> str:
796        return self.source_dir
797
798    def get_build_dir(self) -> str:
799        return self.build_dir
800
801    def get_import_lib_dir(self) -> str:
802        "Install dir for the import library (library used for linking)"
803        return self.get_libdir()
804
805    def get_shared_module_dir(self) -> str:
806        "Install dir for shared modules that are loaded at runtime"
807        return self.get_libdir()
808
809    def get_shared_lib_dir(self) -> str:
810        "Install dir for the shared library"
811        m = self.machines.host
812        # Windows has no RPATH or similar, so DLLs must be next to EXEs.
813        if m.is_windows() or m.is_cygwin():
814            return self.get_bindir()
815        return self.get_libdir()
816
817    def get_static_lib_dir(self) -> str:
818        "Install dir for the static library"
819        return self.get_libdir()
820
821    def get_prefix(self) -> str:
822        return self.coredata.get_option(OptionKey('prefix'))
823
824    def get_libdir(self) -> str:
825        return self.coredata.get_option(OptionKey('libdir'))
826
827    def get_libexecdir(self) -> str:
828        return self.coredata.get_option(OptionKey('libexecdir'))
829
830    def get_bindir(self) -> str:
831        return self.coredata.get_option(OptionKey('bindir'))
832
833    def get_includedir(self) -> str:
834        return self.coredata.get_option(OptionKey('includedir'))
835
836    def get_mandir(self) -> str:
837        return self.coredata.get_option(OptionKey('mandir'))
838
839    def get_datadir(self) -> str:
840        return self.coredata.get_option(OptionKey('datadir'))
841
842    def get_compiler_system_dirs(self, for_machine: MachineChoice):
843        for comp in self.coredata.compilers[for_machine].values():
844            if isinstance(comp, compilers.ClangCompiler):
845                index = 1
846                break
847            elif isinstance(comp, compilers.GnuCompiler):
848                index = 2
849                break
850        else:
851            # This option is only supported by gcc and clang. If we don't get a
852            # GCC or Clang compiler return and empty list.
853            return []
854
855        p, out, _ = Popen_safe(comp.get_exelist() + ['-print-search-dirs'])
856        if p.returncode != 0:
857            raise mesonlib.MesonException('Could not calculate system search dirs')
858        out = out.split('\n')[index].lstrip('libraries: =').split(':')
859        return [os.path.normpath(p) for p in out]
860
861    def need_exe_wrapper(self, for_machine: MachineChoice = MachineChoice.HOST):
862        value = self.properties[for_machine].get('needs_exe_wrapper', None)
863        if value is not None:
864            return value
865        return not machine_info_can_run(self.machines[for_machine])
866
867    def get_exe_wrapper(self) -> ExternalProgram:
868        if not self.need_exe_wrapper():
869            return EmptyExternalProgram()
870        return self.exe_wrapper
871