The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
16 KiB

# 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.
import configparser, os, subprocess
import typing as T
from . import mesonlib
from .mesonlib import EnvironmentException, MachineChoice, PerMachine, split_args
from . import mlog
_T = T.TypeVar('_T')
# 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 an 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',
'c2000',
'e2k',
'ia64',
'm68k',
'microblaze',
'mips',
'mips64',
'parisc',
'ppc',
'ppc64',
'riscv32',
'riscv64',
'rl78',
'rx',
's390',
's390x',
'sparc',
'sparc64',
'pic24',
'dspic',
'wasm32',
'wasm64',
'x86',
'x86_64'
)
# It would feel more natural to call this "64_BIT_CPU_FAMILES", but
# python identifiers cannot start with numbers
CPU_FAMILES_64_BIT = [
'aarch64',
'alpha',
'ia64',
'mips64',
'ppc64',
'riscv64',
's390x',
'sparc64',
'wasm64',
'x86_64',
]
class MesonConfigFile:
@classmethod
def from_config_parser(cls, parser: configparser.ConfigParser) -> T.Dict[str, T.Dict[str, T.Dict[str, str]]]:
out = {}
# This is a bit hackish at the moment.
for s in parser.sections():
section = {}
for entry in parser[s]:
value = parser[s][entry]
# Windows paths...
value = value.replace('\\', '\\\\')
if ' ' in entry or '\t' in entry or "'" in entry or '"' in entry:
raise EnvironmentException('Malformed variable name %s in cross file..' % entry)
try:
res = eval(value, {'__builtins__': None}, {'true': True, 'false': False})
except Exception:
raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
for i in (res if isinstance(res, list) else [res]):
if not isinstance(i, (str, int, bool)):
raise EnvironmentException('Malformed value in cross file variable %s.' % entry)
section[entry] = res
out[s] = section
return out
def get_env_var_pair(for_machine: MachineChoice,
is_cross: bool,
var_name: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
"""
Returns the exact env var and the value.
"""
candidates = PerMachine(
# The prefixed build version takes priority, but if we are native
# compiling we fall back on the unprefixed host version. This
# allows native builds to never need to worry about the 'BUILD_*'
# ones.
[var_name + '_FOR_BUILD'] + ([] if is_cross else [var_name]),
# Always just the unprefixed host verions
[var_name],
)[for_machine]
for var in candidates:
value = os.environ.get(var)
if value is not None:
break
else:
formatted = ', '.join(['{!r}'.format(var) for var in candidates])
mlog.debug('None of {} are defined in the environment, not changing global flags.'.format(formatted))
return None
mlog.log('Using {!r} from environment with value: {!r}'.format(var, value))
return var, value
def get_env_var(for_machine: MachineChoice,
is_cross: bool,
var_name: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
ret = get_env_var_pair(for_machine, is_cross, var_name)
if ret is None:
return None
else:
var, value = ret
return value
class Properties:
def __init__(
self,
properties: T.Optional[T.Dict[str, T.Union[str, T.List[str]]]] = None,
):
self.properties = properties or {} # type: T.Dict[str, T.Union[str, 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 heterogenious dict annotations it's not practical to
# narrow them
def get_stdlib(self, language: str) -> T.Union[str, T.List[str]]:
return self.properties[language + '_stdlib']
def get_root(self) -> T.Optional[T.Union[str, T.List[str]]]:
return self.properties.get('root', None)
def get_sys_root(self) -> T.Optional[T.Union[str, T.List[str]]]:
return self.properties.get('sys_root', None)
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
return mesonlib.listify(p)
def __eq__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
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.Any:
return self.properties[key]
# TODO consider removing so Properties is less freeform
def __contains__(self, item: T.Any) -> bool:
return item in self.properties
# TODO consider removing, for same reasons as above
def get(self, key: str, default: T.Any = None) -> T.Any:
return self.properties.get(key, default)
class MachineInfo:
def __init__(self, system: str, cpu_family: str, cpu: str, endian: str):
self.system = system
self.cpu_family = cpu_family
self.cpu = cpu
self.endian = endian
self.is_64_bit = cpu_family in CPU_FAMILES_64_BIT # type: bool
def __eq__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
if self.__class__ is not other.__class__:
return NotImplemented
return \
self.system == other.system and \
self.cpu_family == other.cpu_family and \
self.cpu == other.cpu and \
self.endian == other.endian
def __ne__(self, other: T.Any) -> 'T.Union[bool, NotImplemented]':
if self.__class__ is not other.__class__:
return NotImplemented
return not self.__eq__(other)
def __repr__(self) -> str:
return '<MachineInfo: {} {} ({})>'.format(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(
'Machine info is currently {}\n'.format(literal) +
'but is missing {}.'.format(minimum_literal - set(literal)))
cpu_family = literal['cpu_family']
if cpu_family not in known_cpu_families:
mlog.warning('Unknown CPU family %s, please report this at https://github.com/mesonbuild/meson/issues/new' % cpu_family)
endian = literal['endian']
if endian not in ('little', 'big'):
mlog.warning('Unknown endian %s' % endian)
return cls(literal['system'], cpu_family, literal['cpu'], endian)
def is_windows(self) -> bool:
"""
Machine is windows?
"""
return self.system == 'windows' or 'mingw' in self.system
def is_cygwin(self) -> bool:
"""
Machine is cygwin?
"""
return self.system.startswith('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'
# 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 = binaries or {} # type: T.Dict[str, T.Union[str, T.List[str]]]
for name, command in self.binaries.items():
if not isinstance(command, (list, str)):
# TODO generalize message
raise mesonlib.MesonException(
'Invalid type {!r} for binary {!r} in cross file'
''.format(command, name))
# Map from language identifiers to environment variables.
evarMap = {
# Compilers
'c': 'CC',
'cpp': 'CXX',
'cs': 'CSC',
'd': 'DC',
'fortran': 'FC',
'objc': 'OBJC',
'objcpp': 'OBJCXX',
'rust': 'RUSTC',
'vala': 'VALAC',
# 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',
# Binutils
'strip': 'STRIP',
'ar': 'AR',
'windres': 'WINDRES',
# Other tools
'cmake': 'CMAKE',
'qmake': 'QMAKE',
'pkgconfig': 'PKG_CONFIG',
} # type: T.Dict[str, str]
# Deprecated environment variables mapped from the new variable to the old one
# Deprecated in 0.54.0
DEPRECATION_MAP = {
'DC_LD': 'D_LD',
'FC_LD': 'F_LD',
'RUSTC_LD': 'RUST_LD',
'OBJCXX_LD': 'OBJCPP_LD',
} # type: T.Dict[str, str]
@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']
@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()
else:
ccache = []
# Return value has to be a list of compiler 'choices'
return compiler, ccache
def lookup_entry(self,
for_machine: MachineChoice,
is_cross: bool,
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.
"""
# Try explicit map, don't fall back on env var
# Try explict map, then env vars
for _ in [()]: # a trick to get `break`
raw_command = self.binaries.get(name)
if raw_command is not None:
command = mesonlib.stringlistify(raw_command)
break # found
evar = self.evarMap.get(name)
if evar is not None:
raw_command = get_env_var(for_machine, is_cross, evar)
if raw_command is None:
deprecated = self.DEPRECATION_MAP.get(evar)
if deprecated is not None:
raw_command = get_env_var(for_machine, is_cross, deprecated)
if raw_command is not None:
mlog.deprecation(
'The', deprecated, 'environment variable is deprecated in favor of',
evar, once=True)
if raw_command is not None:
command = split_args(raw_command)
break # found
command = None
# Do not return empty or blank string entries
if command is not None and (len(command) == 0 or len(command[0].strip()) == 0):
command = None
return command
class Directories:
"""Data class that holds information about directories for native and cross
builds.
"""
def __init__(self, bindir: T.Optional[str] = None, datadir: T.Optional[str] = None,
includedir: T.Optional[str] = None, infodir: T.Optional[str] = None,
libdir: T.Optional[str] = None, libexecdir: T.Optional[str] = None,
localedir: T.Optional[str] = None, localstatedir: T.Optional[str] = None,
mandir: T.Optional[str] = None, prefix: T.Optional[str] = None,
sbindir: T.Optional[str] = None, sharedstatedir: T.Optional[str] = None,
sysconfdir: T.Optional[str] = None):
self.bindir = bindir
self.datadir = datadir
self.includedir = includedir
self.infodir = infodir
self.libdir = libdir
self.libexecdir = libexecdir
self.localedir = localedir
self.localstatedir = localstatedir
self.mandir = mandir
self.prefix = prefix
self.sbindir = sbindir
self.sharedstatedir = sharedstatedir
self.sysconfdir = sysconfdir
def __contains__(self, key: str) -> bool:
return hasattr(self, key)
def __getitem__(self, key: str) -> T.Optional[str]:
# Mypy can't figure out what to do with getattr here, so we'll case for it
return T.cast(T.Optional[str], getattr(self, key))
def __setitem__(self, key: str, value: T.Optional[str]) -> None:
setattr(self, key, value)
def __iter__(self) -> T.Iterator[T.Tuple[str, str]]:
return iter(self.__dict__.items())