summaryrefslogtreecommitdiffstats
path: root/mesonbuild/envconfig.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/envconfig.py')
-rw-r--r--mesonbuild/envconfig.py455
1 files changed, 455 insertions, 0 deletions
diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py
new file mode 100644
index 0000000..90117c1
--- /dev/null
+++ b/mesonbuild/envconfig.py
@@ -0,0 +1,455 @@
+# 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 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',
+ '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 heterogenious 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
+
+ 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}')
+
+ return cls(literal['system'], cpu_family, literal['cpu'], endian)
+
+ 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