diff options
Diffstat (limited to 'mesonbuild/envconfig.py')
-rw-r--r-- | mesonbuild/envconfig.py | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py new file mode 100644 index 0000000..e074d07 --- /dev/null +++ b/mesonbuild/envconfig.py @@ -0,0 +1,397 @@ +# 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, shlex, subprocess +import typing + +from . import mesonlib +from .mesonlib import EnvironmentException, MachineChoice, PerMachine +from . import mlog + + +# 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', + 'arc', + 'arm', + 'e2k', + 'ia64', + 'mips', + 'mips64', + 'parisc', + 'ppc', + 'ppc64', + 'riscv32', + 'riscv64', + 'rl78', + 'rx', + 's390x', + 'sparc', + 'sparc64', + 'x86', + 'x86_64' +) + +class MesonConfigFile: + @classmethod + def parse_datafile(cls, filename): + config = configparser.ConfigParser() + try: + with open(filename, 'r') as f: + config.read_file(f, filename) + except FileNotFoundError: + raise EnvironmentException('File not found: %s.' % filename) + return cls.from_config_parser(config) + + @classmethod + def from_config_parser(cls, parser: configparser.ConfigParser): + 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 + +class Properties: + def __init__( + self, + properties: typing.Optional[typing.Dict[str, typing.Union[str, typing.List[str]]]] = None): + self.properties = properties or {} + + def has_stdlib(self, language): + return language + '_stdlib' in self.properties + + def get_stdlib(self, language): + return self.properties[language + '_stdlib'] + + def get_root(self): + return self.properties.get('root', None) + + def get_sys_root(self): + return self.properties.get('sys_root', None) + + def __eq__(self, other): + if isinstance(other, type(self)): + return self.properties == other.properties + return NotImplemented + + # TODO consider removing so Properties is less freeform + def __getitem__(self, key): + return self.properties[key] + + # TODO consider removing so Properties is less freeform + def __contains__(self, item): + return item in self.properties + + # TODO consider removing, for same reasons as above + def get(self, key, default=None): + return self.properties.get(key, default) + +class MachineInfo: + def __init__(self, system, cpu_family, cpu, endian): + self.system = system + self.cpu_family = cpu_family + self.cpu = cpu + self.endian = endian + + def __eq__(self, other): + 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): + if self.__class__ is not other.__class__: + return NotImplemented + return not self.__eq__(other) + + def __repr__(self): + return '<MachineInfo: {} {} ({})>'.format(self.system, self.cpu_family, self.cpu) + + @staticmethod + def from_literal(literal): + 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 MachineInfo( + literal['system'], + cpu_family, + literal['cpu'], + endian) + + def is_windows(self): + """ + Machine is windows? + """ + return self.system == 'windows' + + def is_cygwin(self): + """ + Machine is cygwin? + """ + return self.system == 'cygwin' + + def is_linux(self): + """ + Machine is linux? + """ + return self.system == 'linux' + + def is_darwin(self): + """ + Machine is Darwin (iOS/OS X)? + """ + return self.system in ('darwin', 'ios') + + def is_android(self): + """ + Machine is Android? + """ + return self.system == 'android' + + def is_haiku(self): + """ + Machine is Haiku? + """ + return self.system == 'haiku' + + def is_openbsd(self): + """ + Machine is OpenBSD? + """ + return self.system == 'openbsd' + + # 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): + if self.is_windows() or self.is_cygwin(): + return 'exe' + else: + return '' + + def get_object_suffix(self): + if self.is_windows(): + return 'obj' + else: + return 'o' + + def libdir_layout_is_win(self): + return self.is_windows() \ + or self.is_cygwin() + +class PerMachineDefaultable(PerMachine): + """Extends `PerMachine` with the ability to default from `None`s. + """ + def __init__(self): + super().__init__(None, None, None) + + def default_missing(self): + """Default host to buid and target to host. + + This allows just specifying nothing in the native case, just host in the + cross non-compiler case, and just target in the native-built + cross-compiler case. + """ + if self.host is None: + self.host = self.build + if self.target is None: + self.target = self.host + + def miss_defaulting(self): + """Unset definition duplicated from their previous to None + + This is the inverse of ''default_missing''. By removing defaulted + machines, we can elaborate the original and then redefault them and thus + avoid repeating the elaboration explicitly. + """ + if self.target == self.host: + self.target = None + if self.host == self.build: + self.host = None + +class MachineInfos(PerMachineDefaultable): + def matches_build_machine(self, machine: MachineChoice): + return self.build == self[machine] + +class BinaryTable: + def __init__(self, binaries = {}, fallback = True): + self.binaries = binaries + self.fallback = fallback + 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', + + # Binutils + 'strip': 'STRIP', + 'ar': 'AR', + 'windres': 'WINDRES', + + 'cmake': 'CMAKE', + 'qmake': 'QMAKE', + 'pkgconfig': 'PKG_CONFIG', + } + + @classmethod + def detect_ccache(cls): + try: + has_ccache = subprocess.call(['ccache', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + except OSError: + has_ccache = 1 + if has_ccache == 0: + cmdlist = ['ccache'] + else: + cmdlist = [] + return cmdlist + + @classmethod + def _warn_about_lang_pointing_to_cross(cls, compiler_exe, evar): + evar_str = os.environ.get(evar, 'WHO_WOULD_CALL_THEIR_COMPILER_WITH_THIS_NAME') + if evar_str == compiler_exe: + mlog.warning('''Env var %s seems to point to the cross compiler. +This is probably wrong, it should always point to the native compiler.''' % evar) + + @classmethod + def parse_entry(cls, entry): + 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, name): + """Lookup binary + + Returns command with args as list if found, Returns `None` if nothing is + found. + + First tries looking in explicit map, then tries environment variable. + """ + # Try explict map, don't fall back on env var + command = self.binaries.get(name) + if command is not None: + command = mesonlib.stringlistify(command) + # Relies on there being no "" env var + evar = self.evarMap.get(name, "") + self._warn_about_lang_pointing_to_cross(command[0], evar) + elif self.fallback: + # Relies on there being no "" env var + evar = self.evarMap.get(name, "") + command = os.environ.get(evar) + if command is not None: + command = shlex.split(command) + return command + +class Directories: + + """Data class that holds information about directories for native and cross + builds. + """ + + def __init__(self, bindir: typing.Optional[str] = None, datadir: typing.Optional[str] = None, + includedir: typing.Optional[str] = None, infodir: typing.Optional[str] = None, + libdir: typing.Optional[str] = None, libexecdir: typing.Optional[str] = None, + localedir: typing.Optional[str] = None, localstatedir: typing.Optional[str] = None, + mandir: typing.Optional[str] = None, prefix: typing.Optional[str] = None, + sbindir: typing.Optional[str] = None, sharedstatedir: typing.Optional[str] = None, + sysconfdir: typing.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) -> str: + return hasattr(self, key) + + def __getitem__(self, key: str) -> str: + return getattr(self, key) + + def __setitem__(self, key: str, value: typing.Optional[str]) -> None: + setattr(self, key, value) + + def __iter__(self) -> typing.Iterator[typing.Tuple[str, str]]: + return iter(self.__dict__.items()) |