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