diff options
Diffstat (limited to 'mesonbuild/environment.py')
-rw-r--r-- | mesonbuild/environment.py | 442 |
1 files changed, 41 insertions, 401 deletions
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index bbe82ec..9760b08 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -19,10 +19,14 @@ from . import coredata from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker, CcrxLinker from . import mesonlib from .mesonlib import ( - MesonException, EnvironmentException, MachineChoice, PerMachine, Popen_safe + MesonException, EnvironmentException, MachineChoice, PerMachine, Popen_safe, ) from . import mlog +from .envconfig import ( + BinaryTable, Directories, MachineInfo, MachineInfos, MesonConfigFile, + PerMachineDefaultable, Properties, known_cpu_families, +) from . import compilers from .compilers import ( CompilerType, @@ -79,28 +83,6 @@ from .compilers import ( build_filename = 'meson.build' -known_cpu_families = ( - 'aarch64', - 'arc', - 'arm', - 'e2k', - 'ia64', - 'mips', - 'mips64', - 'parisc', - 'ppc', - 'ppc64', - 'riscv32', - 'riscv64', - 'rl78', - 'rx', - 's390x', - 'sparc', - 'sparc64', - 'x86', - 'x86_64' -) - def detect_gcovr(version='3.1', log=False): gcovr_exe = 'gcovr' try: @@ -299,6 +281,37 @@ def detect_msys2_arch(): return os.environ['MSYSTEM_CARCH'] return None +def detect_machine_info(compilers = None) -> MachineInfo: + """Detect the machine we're running on + + If compilers are not provided, we cannot know as much. None out those + fields to avoid accidentally depending on partial knowledge. The + underlying ''detect_*'' method can be called to explicitly use the + partial information. + """ + return MachineInfo( + detect_system(), + detect_cpu_family(compilers) if compilers is not None else None, + detect_cpu(compilers) if compilers is not None else None, + sys.byteorder) + +# TODO make this compare two `MachineInfo`s purely. How important is the +# `detect_cpu_family({})` distinction? It is the one impediment to that. +def machine_info_can_run(machine_info: MachineInfo): + """Whether we can run binaries for this machine on the current machine. + + Can almost always run 32-bit binaries on 64-bit natively if the host + and build systems are the same. We don't pass any compilers to + detect_cpu_family() here because we always want to know the OS + architecture, not what the compiler environment tells us. + """ + if machine_info.system != detect_system(): + return False + true_build_cpu_family = detect_cpu_family({}) + return \ + (machine_info.cpu_family == true_build_cpu_family) or \ + ((true_build_cpu_family == 'x86_64') and (machine_info.cpu_family == 'x86')) + def search_version(text): # Usually of the type 4.1.4 but compiler output may contain # stuff like this: @@ -356,7 +369,7 @@ class Environment: self.machines = MachineInfos() # Will be fully initialized later using compilers later. - self.machines.detect_build() + self.detect_build_machine() # Similar to coredata.compilers and build.compilers, but lower level in # that there is no meta data, only names/paths. @@ -1162,6 +1175,9 @@ class Environment: self._handle_exceptions(popen_exceptions, linkers, 'linker') raise EnvironmentException('Unknown static linker "%s"' % ' '.join(linkers)) + def detect_build_machine(self, compilers = None): + self.machines.build = detect_machine_info(compilers) + def get_source_dir(self): return self.source_dir @@ -1236,386 +1252,10 @@ class Environment: value = self.properties[for_machine].get('needs_exe_wrapper', None) if value is not None: return value - return not self.machines[for_machine].can_run() + return not machine_info_can_run(self.machines[for_machine]) def get_exe_wrapper(self): if not self.need_exe_wrapper(): from .dependencies import EmptyExternalProgram return EmptyExternalProgram() return self.exe_wrapper - -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 detect(compilers = None): - """Detect the machine we're running on - - If compilers are not provided, we cannot know as much. None out those - fields to avoid accidentally depending on partial knowledge. The - underlying ''detect_*'' method can be called to explicitly use the - partial information. - """ - return MachineInfo( - detect_system(), - detect_cpu_family(compilers) if compilers is not None else None, - detect_cpu(compilers) if compilers is not None else None, - sys.byteorder) - - @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() - - # TODO make this compare two `MachineInfo`s purely. How important is the - # `detect_cpu_family({})` distinction? It is the one impediment to that. - def can_run(self): - """Whether we can run binaries for this machine on the current machine. - - Can almost always run 32-bit binaries on 64-bit natively if the host - and build systems are the same. We don't pass any compilers to - detect_cpu_family() here because we always want to know the OS - architecture, not what the compiler environment tells us. - """ - if self.system != detect_system(): - return False - true_build_cpu_family = detect_cpu_family({}) - return \ - (self.cpu_family == true_build_cpu_family) or \ - ((true_build_cpu_family == 'x86_64') and (self.cpu_family == 'x86')) - -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 detect_build(self, compilers = None): - self.build = MachineInfo.detect(compilers) - - 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()) |