|
|
|
# Copyright 2012-2016 The Meson development team
|
|
|
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import subprocess
|
|
|
|
import typing as T
|
|
|
|
from enum import Enum
|
|
|
|
|
|
|
|
from . import mesonlib
|
|
|
|
from .mesonlib import EnvironmentException, HoldableObject
|
|
|
|
from . import mlog
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
# These classes contains all the data pulled from configuration files (native
|
|
|
|
# and cross file currently), and also assists with the reading environment
|
|
|
|
# variables.
|
|
|
|
#
|
|
|
|
# At this time there isn't an ironclad difference between this and other sources
|
|
|
|
# of state like `coredata`. But one rough guide is much what is in `coredata` is
|
|
|
|
# the *output* of the configuration process: the final decisions after tests.
|
|
|
|
# This, on the other hand has *inputs*. The config files are parsed, but
|
|
|
|
# otherwise minimally transformed. When more complex fallbacks (environment
|
|
|
|
# detection) exist, they are defined elsewhere as functions that construct
|
|
|
|
# instances of these classes.
|
|
|
|
|
|
|
|
|
|
|
|
known_cpu_families = (
|
|
|
|
'aarch64',
|
|
|
|
'alpha',
|
|
|
|
'arc',
|
|
|
|
'arm',
|
|
|
|
'avr',
|
|
|
|
'c2000',
|
|
|
|
'csky',
|
|
|
|
'dspic',
|
|
|
|
'e2k',
|
|
|
|
'ft32',
|
|
|
|
'ia64',
|
|
|
|
'loongarch64',
|
|
|
|
'm68k',
|
|
|
|
'microblaze',
|
|
|
|
'mips',
|
|
|
|
'mips64',
|
|
|
|
'msp430',
|
|
|
|
'parisc',
|
|
|
|
'pic24',
|
|
|
|
'ppc',
|
|
|
|
'ppc64',
|
|
|
|
'riscv32',
|
|
|
|
'riscv64',
|
|
|
|
'rl78',
|
|
|
|
'rx',
|
|
|
|
's390',
|
|
|
|
's390x',
|
|
|
|
'sh4',
|
|
|
|
'sparc',
|
|
|
|
'sparc64',
|
|
|
|
'wasm32',
|
|
|
|
'wasm64',
|
|
|
|
'x86',
|
|
|
|
'x86_64',
|
|
|
|
)
|
|
|
|
|
|
|
|
# It would feel more natural to call this "64_BIT_CPU_FAMILIES", but
|
|
|
|
# python identifiers cannot start with numbers
|
|
|
|
CPU_FAMILIES_64_BIT = [
|
|
|
|
'aarch64',
|
|
|
|
'alpha',
|
|
|
|
'ia64',
|
|
|
|
'loongarch64',
|
|
|
|
'mips64',
|
|
|
|
'ppc64',
|
|
|
|
'riscv64',
|
|
|
|
's390x',
|
|
|
|
'sparc64',
|
|
|
|
'wasm64',
|
|
|
|
'x86_64',
|
|
|
|
]
|
|
|
|
|
|
|
|
# Map from language identifiers to environment variables.
|
|
|
|
ENV_VAR_COMPILER_MAP: T.Mapping[str, str] = {
|
|
|
|
# Compilers
|
|
|
|
'c': 'CC',
|
|
|
|
'cpp': 'CXX',
|
|
|
|
'cs': 'CSC',
|
|
|
|
'd': 'DC',
|
|
|
|
'fortran': 'FC',
|
|
|
|
'objc': 'OBJC',
|
|
|
|
'objcpp': 'OBJCXX',
|
|
|
|
'rust': 'RUSTC',
|
|
|
|
'vala': 'VALAC',
|
|
|
|
'nasm': 'NASM',
|
|
|
|
|
|
|
|
# Linkers
|
|
|
|
'c_ld': 'CC_LD',
|
|
|
|
'cpp_ld': 'CXX_LD',
|
|
|
|
'd_ld': 'DC_LD',
|
|
|
|
'fortran_ld': 'FC_LD',
|
|
|
|
'objc_ld': 'OBJC_LD',
|
|
|
|
'objcpp_ld': 'OBJCXX_LD',
|
|
|
|
'rust_ld': 'RUSTC_LD',
|
|
|
|
}
|
|
|
|
|
|
|
|
# Map from utility names to environment variables.
|
|
|
|
ENV_VAR_TOOL_MAP: T.Mapping[str, str] = {
|
|
|
|
# Binutils
|
|
|
|
'ar': 'AR',
|
|
|
|
'as': 'AS',
|
|
|
|
'ld': 'LD',
|
|
|
|
'nm': 'NM',
|
|
|
|
'objcopy': 'OBJCOPY',
|
|
|
|
'objdump': 'OBJDUMP',
|
|
|
|
'ranlib': 'RANLIB',
|
|
|
|
'readelf': 'READELF',
|
|
|
|
'size': 'SIZE',
|
|
|
|
'strings': 'STRINGS',
|
|
|
|
'strip': 'STRIP',
|
|
|
|
'windres': 'WINDRES',
|
|
|
|
|
|
|
|
# Other tools
|
|
|
|
'cmake': 'CMAKE',
|
|
|
|
'qmake': 'QMAKE',
|
|
|
|
'pkgconfig': 'PKG_CONFIG',
|
|
|
|
'pkg-config': 'PKG_CONFIG',
|
|
|
|
'make': 'MAKE',
|
|
|
|
'vapigen': 'VAPIGEN',
|
|
|
|
'llvm-config': 'LLVM_CONFIG',
|
|
|
|
}
|
|
|
|
|
|
|
|
ENV_VAR_PROG_MAP = {**ENV_VAR_COMPILER_MAP, **ENV_VAR_TOOL_MAP}
|
|
|
|
|
|
|
|
# Deprecated environment variables mapped from the new variable to the old one
|
|
|
|
# Deprecated in 0.54.0
|
|
|
|
DEPRECATED_ENV_PROG_MAP: T.Mapping[str, str] = {
|
|
|
|
'd_ld': 'D_LD',
|
|
|
|
'fortran_ld': 'F_LD',
|
|
|
|
'rust_ld': 'RUST_LD',
|
|
|
|
'objcpp_ld': 'OBJCPP_LD',
|
|
|
|
}
|
|
|
|
|
|
|
|
class CMakeSkipCompilerTest(Enum):
|
|
|
|
ALWAYS = 'always'
|
|
|
|
NEVER = 'never'
|
|
|
|
DEP_ONLY = 'dep_only'
|
|
|
|
|
|
|
|
class Properties:
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
properties: T.Optional[T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]] = None,
|
|
|
|
):
|
|
|
|
self.properties = properties or {} # type: T.Dict[str, T.Optional[T.Union[str, bool, int, T.List[str]]]]
|
|
|
|
|
|
|
|
def has_stdlib(self, language: str) -> bool:
|
|
|
|
return language + '_stdlib' in self.properties
|
|
|
|
|
|
|
|
# Some of get_stdlib, get_root, get_sys_root are wider than is actually
|
|
|
|
# true, but without heterogeneous dict annotations it's not practical to
|
|
|
|
# narrow them
|
|
|
|
def get_stdlib(self, language: str) -> T.Union[str, T.List[str]]:
|
|
|
|
stdlib = self.properties[language + '_stdlib']
|
|
|
|
if isinstance(stdlib, str):
|
|
|
|
return stdlib
|
|
|
|
assert isinstance(stdlib, list)
|
|
|
|
for i in stdlib:
|
|
|
|
assert isinstance(i, str)
|
|
|
|
return stdlib
|
|
|
|
|
|
|
|
def get_root(self) -> T.Optional[str]:
|
|
|
|
root = self.properties.get('root', None)
|
|
|
|
assert root is None or isinstance(root, str)
|
|
|
|
return root
|
|
|
|
|
|
|
|
def get_sys_root(self) -> T.Optional[str]:
|
|
|
|
sys_root = self.properties.get('sys_root', None)
|
|
|
|
assert sys_root is None or isinstance(sys_root, str)
|
|
|
|
return sys_root
|
|
|
|
|
|
|
|
def get_pkg_config_libdir(self) -> T.Optional[T.List[str]]:
|
|
|
|
p = self.properties.get('pkg_config_libdir', None)
|
|
|
|
if p is None:
|
|
|
|
return p
|
|
|
|
res = mesonlib.listify(p)
|
|
|
|
for i in res:
|
|
|
|
assert isinstance(i, str)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def get_cmake_defaults(self) -> bool:
|
|
|
|
if 'cmake_defaults' not in self.properties:
|
|
|
|
return True
|
|
|
|
res = self.properties['cmake_defaults']
|
|
|
|
assert isinstance(res, bool)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def get_cmake_toolchain_file(self) -> T.Optional[Path]:
|
|
|
|
if 'cmake_toolchain_file' not in self.properties:
|
|
|
|
return None
|
|
|
|
raw = self.properties['cmake_toolchain_file']
|
|
|
|
assert isinstance(raw, str)
|
|
|
|
cmake_toolchain_file = Path(raw)
|
|
|
|
if not cmake_toolchain_file.is_absolute():
|
|
|
|
raise EnvironmentException(f'cmake_toolchain_file ({raw}) is not absolute')
|
|
|
|
return cmake_toolchain_file
|
|
|
|
|
|
|
|
def get_cmake_skip_compiler_test(self) -> CMakeSkipCompilerTest:
|
|
|
|
if 'cmake_skip_compiler_test' not in self.properties:
|
|
|
|
return CMakeSkipCompilerTest.DEP_ONLY
|
|
|
|
raw = self.properties['cmake_skip_compiler_test']
|
|
|
|
assert isinstance(raw, str)
|
|
|
|
try:
|
|
|
|
return CMakeSkipCompilerTest(raw)
|
|
|
|
except ValueError:
|
|
|
|
raise EnvironmentException(
|
|
|
|
'"{}" is not a valid value for cmake_skip_compiler_test. Supported values are {}'
|
|
|
|
.format(raw, [e.value for e in CMakeSkipCompilerTest]))
|
|
|
|
|
|
|
|
def get_cmake_use_exe_wrapper(self) -> bool:
|
|
|
|
if 'cmake_use_exe_wrapper' not in self.properties:
|
|
|
|
return True
|
|
|
|
res = self.properties['cmake_use_exe_wrapper']
|
|
|
|
assert isinstance(res, bool)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def get_java_home(self) -> T.Optional[Path]:
|
|
|
|
value = T.cast('T.Optional[str]', self.properties.get('java_home'))
|
|
|
|
return Path(value) if value else None
|
|
|
|
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
|
|
if isinstance(other, type(self)):
|
|
|
|
return self.properties == other.properties
|
|
|
|
return NotImplemented
|
|
|
|
|
|
|
|
# TODO consider removing so Properties is less freeform
|
|
|
|
def __getitem__(self, key: str) -> T.Optional[T.Union[str, bool, int, T.List[str]]]:
|
|
|
|
return self.properties[key]
|
|
|
|
|
|
|
|
# TODO consider removing so Properties is less freeform
|
|
|
|
def __contains__(self, item: T.Union[str, bool, int, T.List[str]]) -> bool:
|
|
|
|
return item in self.properties
|
|
|
|
|
|
|
|
# TODO consider removing, for same reasons as above
|
|
|
|
def get(self, key: str, default: T.Optional[T.Union[str, bool, int, T.List[str]]] = None) -> T.Optional[T.Union[str, bool, int, T.List[str]]]:
|
|
|
|
return self.properties.get(key, default)
|
|
|
|
|
|
|
|
@dataclass(unsafe_hash=True)
|
|
|
|
class MachineInfo(HoldableObject):
|
|
|
|
system: str
|
|
|
|
cpu_family: str
|
|
|
|
cpu: str
|
|
|
|
endian: str
|
|
|
|
kernel: T.Optional[str]
|
|
|
|
subsystem: T.Optional[str]
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
|
|
self.is_64_bit: bool = self.cpu_family in CPU_FAMILIES_64_BIT
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return f'<MachineInfo: {self.system} {self.cpu_family} ({self.cpu})>'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_literal(cls, literal: T.Dict[str, str]) -> 'MachineInfo':
|
|
|
|
minimum_literal = {'cpu', 'cpu_family', 'endian', 'system'}
|
|
|
|
if set(literal) < minimum_literal:
|
|
|
|
raise EnvironmentException(
|
|
|
|
f'Machine info is currently {literal}\n' +
|
|
|
|
'but is missing {}.'.format(minimum_literal - set(literal)))
|
|
|
|
|
|
|
|
cpu_family = literal['cpu_family']
|
|
|
|
if cpu_family not in known_cpu_families:
|
|
|
|
mlog.warning(f'Unknown CPU family {cpu_family}, please report this at https://github.com/mesonbuild/meson/issues/new')
|
|
|
|
|
|
|
|
endian = literal['endian']
|
|
|
|
if endian not in ('little', 'big'):
|
|
|
|
mlog.warning(f'Unknown endian {endian}')
|
|
|
|
|
|
|
|
system = literal['system']
|
|
|
|
kernel = literal.get('kernel', None)
|
|
|
|
subsystem = literal.get('subsystem', None)
|
|
|
|
|
|
|
|
return cls(system, cpu_family, literal['cpu'], endian, kernel, subsystem)
|
|
|
|
|
|
|
|
def is_windows(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is windows?
|
|
|
|
"""
|
|
|
|
return self.system == 'windows'
|
|
|
|
|
|
|
|
def is_cygwin(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is cygwin?
|
|
|
|
"""
|
|
|
|
return self.system == 'cygwin'
|
|
|
|
|
|
|
|
def is_linux(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is linux?
|
|
|
|
"""
|
|
|
|
return self.system == 'linux'
|
|
|
|
|
|
|
|
def is_darwin(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is Darwin (iOS/tvOS/OS X)?
|
|
|
|
"""
|
|
|
|
return self.system in {'darwin', 'ios', 'tvos'}
|
|
|
|
|
|
|
|
def is_android(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is Android?
|
|
|
|
"""
|
|
|
|
return self.system == 'android'
|
|
|
|
|
|
|
|
def is_haiku(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is Haiku?
|
|
|
|
"""
|
|
|
|
return self.system == 'haiku'
|
|
|
|
|
|
|
|
def is_netbsd(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is NetBSD?
|
|
|
|
"""
|
|
|
|
return self.system == 'netbsd'
|
|
|
|
|
|
|
|
def is_openbsd(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is OpenBSD?
|
|
|
|
"""
|
|
|
|
return self.system == 'openbsd'
|
|
|
|
|
|
|
|
def is_dragonflybsd(self) -> bool:
|
|
|
|
"""Machine is DragonflyBSD?"""
|
|
|
|
return self.system == 'dragonfly'
|
|
|
|
|
|
|
|
def is_freebsd(self) -> bool:
|
|
|
|
"""Machine is FreeBSD?"""
|
|
|
|
return self.system == 'freebsd'
|
|
|
|
|
|
|
|
def is_sunos(self) -> bool:
|
|
|
|
"""Machine is illumos or Solaris?"""
|
|
|
|
return self.system == 'sunos'
|
|
|
|
|
|
|
|
def is_hurd(self) -> bool:
|
|
|
|
"""
|
|
|
|
Machine is GNU/Hurd?
|
|
|
|
"""
|
|
|
|
return self.system == 'gnu'
|
|
|
|
|
|
|
|
def is_irix(self) -> bool:
|
|
|
|
"""Machine is IRIX?"""
|
|
|
|
return self.system.startswith('irix')
|
|
|
|
|
|
|
|
# Various prefixes and suffixes for import libraries, shared libraries,
|
|
|
|
# static libraries, and executables.
|
|
|
|
# Versioning is added to these names in the backends as-needed.
|
|
|
|
def get_exe_suffix(self) -> str:
|
|
|
|
if self.is_windows() or self.is_cygwin():
|
|
|
|
return 'exe'
|
|
|
|
else:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def get_object_suffix(self) -> str:
|
|
|
|
if self.is_windows():
|
|
|
|
return 'obj'
|
|
|
|
else:
|
|
|
|
return 'o'
|
|
|
|
|
|
|
|
def libdir_layout_is_win(self) -> bool:
|
|
|
|
return self.is_windows() or self.is_cygwin()
|
|
|
|
|
|
|
|
class BinaryTable:
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
binaries: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None,
|
|
|
|
):
|
|
|
|
self.binaries: T.Dict[str, T.List[str]] = {}
|
|
|
|
if binaries:
|
|
|
|
for name, command in binaries.items():
|
|
|
|
if not isinstance(command, (list, str)):
|
|
|
|
raise mesonlib.MesonException(
|
|
|
|
f'Invalid type {command!r} for entry {name!r} in cross file')
|
|
|
|
self.binaries[name] = mesonlib.listify(command)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def detect_ccache() -> T.List[str]:
|
|
|
|
try:
|
|
|
|
subprocess.check_call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
except (OSError, subprocess.CalledProcessError):
|
|
|
|
return []
|
|
|
|
return ['ccache']
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def detect_sccache() -> T.List[str]:
|
|
|
|
try:
|
|
|
|
subprocess.check_call(['sccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
except (OSError, subprocess.CalledProcessError):
|
|
|
|
return []
|
|
|
|
return ['sccache']
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def detect_compiler_cache() -> T.List[str]:
|
|
|
|
# Sccache is "newer" so it is assumed that people would prefer it by default.
|
|
|
|
cache = BinaryTable.detect_sccache()
|
|
|
|
if cache:
|
|
|
|
return cache
|
|
|
|
return BinaryTable.detect_ccache()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def parse_entry(cls, entry: T.Union[str, T.List[str]]) -> T.Tuple[T.List[str], T.List[str]]:
|
|
|
|
compiler = mesonlib.stringlistify(entry)
|
|
|
|
# Ensure ccache exists and remove it if it doesn't
|
|
|
|
if compiler[0] == 'ccache':
|
|
|
|
compiler = compiler[1:]
|
|
|
|
ccache = cls.detect_ccache()
|
|
|
|
elif compiler[0] == 'sccache':
|
|
|
|
compiler = compiler[1:]
|
|
|
|
ccache = cls.detect_sccache()
|
|
|
|
else:
|
|
|
|
ccache = []
|
|
|
|
# Return value has to be a list of compiler 'choices'
|
|
|
|
return compiler, ccache
|
|
|
|
|
|
|
|
def lookup_entry(self, name: str) -> T.Optional[T.List[str]]:
|
|
|
|
"""Lookup binary in cross/native file and fallback to environment.
|
|
|
|
|
|
|
|
Returns command with args as list if found, Returns `None` if nothing is
|
|
|
|
found.
|
|
|
|
"""
|
|
|
|
command = self.binaries.get(name)
|
|
|
|
if not command:
|
|
|
|
return None
|
|
|
|
elif not command[0].strip():
|
|
|
|
return None
|
|
|
|
return command
|
|
|
|
|
|
|
|
class CMakeVariables:
|
|
|
|
def __init__(self, variables: T.Optional[T.Dict[str, T.Any]] = None) -> None:
|
|
|
|
variables = variables or {}
|
|
|
|
self.variables = {} # type: T.Dict[str, T.List[str]]
|
|
|
|
|
|
|
|
for key, value in variables.items():
|
|
|
|
value = mesonlib.listify(value)
|
|
|
|
for i in value:
|
|
|
|
if not isinstance(i, str):
|
|
|
|
raise EnvironmentException(f"Value '{i}' of CMake variable '{key}' defined in a machine file is a {type(i).__name__} and not a str")
|
|
|
|
self.variables[key] = value
|
|
|
|
|
|
|
|
def get_variables(self) -> T.Dict[str, T.List[str]]:
|
|
|
|
return self.variables
|