diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index e8adc99de..22920f49b 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -343,8 +343,7 @@ class Backend: exe_is_native = True is_cross_built = (not exe_is_native) and \ self.environment.is_cross_build() and \ - self.environment.cross_info.need_cross_compiler() and \ - self.environment.cross_info.need_exe_wrapper() + self.environment.need_exe_wrapper() if is_cross_built: exe_wrapper = self.environment.get_exe_wrapper() if not exe_wrapper.found(): @@ -640,11 +639,11 @@ class Backend: def get_mingw_extra_paths(self, target): paths = OrderedSet() # The cross bindir - root = self.environment.cross_info.get_root() + root = self.environment.properties.host.get_root() if root: paths.add(os.path.join(root, 'bin')) # The toolchain bindir - sys_root = self.environment.cross_info.get_sys_root() + sys_root = self.environment.properties.host.get_sys_root() if sys_root: paths.add(os.path.join(sys_root, 'bin')) # Get program and library dirs from all target compilers @@ -693,8 +692,7 @@ class Backend: else: cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(t.get_exe()))] is_cross = self.environment.is_cross_build() and \ - self.environment.cross_info.need_cross_compiler() and \ - self.environment.cross_info.need_exe_wrapper() + self.environment.need_exe_wrapper() if isinstance(exe, build.BuildTarget): is_cross = is_cross and exe.is_cross if isinstance(exe, dependencies.ExternalProgram): @@ -765,7 +763,7 @@ class Backend: def exe_object_to_cmd_array(self, exe): if self.environment.is_cross_build() and \ isinstance(exe, build.BuildTarget) and exe.is_cross: - if self.environment.exe_wrapper is None and self.environment.cross_info.need_exe_wrapper(): + if self.environment.exe_wrapper is None and self.environment.need_exe_wrapper(): s = textwrap.dedent(''' Can not use target {} as a generator because it is cross-built and no exe wrapper is defined or needs_exe_wrapper is true. @@ -979,15 +977,13 @@ class Backend: def create_install_data_files(self): install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat') - if self.environment.is_cross_build(): - bins = self.environment.cross_info.config['binaries'] - if 'strip' not in bins: + strip_bin = self.environment.binaries.host.lookup_entry('strip') + if strip_bin is None: + if self.environment.is_cross_build(): mlog.warning('Cross file does not specify strip binary, result will not be stripped.') - strip_bin = None else: - strip_bin = mesonlib.stringlistify(bins['strip']) - else: - strip_bin = self.environment.native_strip_bin + # TODO go through all candidates, like others + strip_bin = [self.environment.default_strip[0]] d = InstallData(self.environment.get_source_dir(), self.environment.get_build_dir(), self.environment.get_prefix(), diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 372d7c241..44bdaab55 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -536,8 +536,7 @@ int dummy; # a serialized executable wrapper for that and check if the # CustomTarget command needs extra paths first. is_cross = self.environment.is_cross_build() and \ - self.environment.cross_info.need_cross_compiler() and \ - self.environment.cross_info.need_exe_wrapper() + self.environment.need_exe_wrapper() if mesonlib.for_windows(is_cross, self.environment) or \ mesonlib.for_cygwin(is_cross, self.environment): extra_bdeps = target.get_transitive_build_target_deps() @@ -1338,7 +1337,7 @@ int dummy; if not is_cross: self.generate_java_link(outfile) if is_cross: - if self.environment.cross_info.need_cross_compiler(): + if self.environment.is_cross_build(): static_linker = self.build.static_cross_linker else: static_linker = self.build.static_linker @@ -1381,11 +1380,7 @@ int dummy; num_pools = self.environment.coredata.backend_options['backend_max_links'].value ctypes = [(self.build.compilers, False)] if self.environment.is_cross_build(): - if self.environment.cross_info.need_cross_compiler(): - ctypes.append((self.build.cross_compilers, True)) - else: - # Native compiler masquerades as the cross compiler. - ctypes.append((self.build.compilers, True)) + ctypes.append((self.build.cross_compilers, True)) else: ctypes.append((self.build.cross_compilers, True)) for (complist, is_cross) in ctypes: @@ -1396,13 +1391,9 @@ int dummy; or langname == 'cs': continue crstr = '' - cross_args = [] + cross_args = self.environment.properties.host.get_external_link_args(langname) if is_cross: crstr = '_CROSS' - try: - cross_args = self.environment.cross_info.config['properties'][langname + '_link_args'] - except KeyError: - pass rule = 'rule %s%s_LINKER\n' % (langname, crstr) if compiler.can_linker_accept_rsp(): command_template = ''' command = {executable} @$out.rsp @@ -1667,12 +1658,7 @@ rule FORTRAN_DEP_HACK%s self.generate_compile_rule_for(langname, compiler, False, outfile) self.generate_pch_rule_for(langname, compiler, False, outfile) if self.environment.is_cross_build(): - # In case we are going a target-only build, make the native compilers - # masquerade as cross compilers. - if self.environment.cross_info.need_cross_compiler(): - cclist = self.build.cross_compilers - else: - cclist = self.build.compilers + cclist = self.build.cross_compilers for langname, compiler in cclist.items(): if compiler.get_id() == 'clang': self.generate_llvm_ir_compile_rule(compiler, True, outfile) @@ -1835,7 +1821,7 @@ rule FORTRAN_DEP_HACK%s def get_cross_stdlib_args(self, target, compiler): if not target.is_cross: return [] - if not self.environment.cross_info.has_stdlib(compiler.language): + if not self.environment.properties.host.has_stdlib(compiler.language): return [] return compiler.get_no_stdinc_args() @@ -2235,14 +2221,14 @@ rule FORTRAN_DEP_HACK%s targetdir = self.get_target_private_dir(target) symname = os.path.join(targetdir, target_name + '.symbols') elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_file) - if self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler(): - elem.add_item('CROSS', '--cross-host=' + self.environment.cross_info.config['host_machine']['system']) + if self.environment.is_cross_build(): + elem.add_item('CROSS', '--cross-host=' + self.environment.machines.host.system) elem.write(outfile) def get_cross_stdlib_link_args(self, target, linker): if isinstance(target, build.StaticLibrary) or not target.is_cross: return [] - if not self.environment.cross_info.has_stdlib(linker.language): + if not self.environment.properties.host.has_stdlib(linker.language): return [] return linker.get_no_stdlib_link_args() diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 46f41819d..92a9fa6d7 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -757,7 +757,7 @@ class CCompiler(Compiler): varname = 'has function ' + funcname varname = varname.replace(' ', '_') if self.is_cross: - val = env.cross_info.config['properties'].get(varname, None) + val = env.properties.host.get(varname, None) if val is not None: if isinstance(val, bool): return val diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 31047b14e..2be6ef124 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1045,13 +1045,10 @@ class Compiler: def get_cross_extra_flags(self, environment, link): extra_flags = [] if self.is_cross and environment: - if 'properties' in environment.cross_info.config: - props = environment.cross_info.config['properties'] - lang_args_key = self.language + '_args' - extra_flags += mesonlib.stringlistify(props.get(lang_args_key, [])) - lang_link_args_key = self.language + '_link_args' - if link: - extra_flags += mesonlib.stringlistify(props.get(lang_link_args_key, [])) + props = environment.properties.host + extra_flags += props.get_external_args(self.language) + if link: + extra_flags += props.get_external_link_args(self.language) return extra_flags def _get_compile_output(self, dirname, mode): diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 8ada86a98..b650f78b7 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -231,42 +231,6 @@ def load_configs(filenames): return config -def _get_section(config, section): - if config.has_section(section): - final = {} - for k, v in config.items(section): - # Windows paths... - v = v.replace('\\', '\\\\') - try: - final[k] = ast.literal_eval(v) - except SyntaxError: - raise MesonException( - 'Malformed value in native file variable: {}'.format(v)) - return final - return {} - - -class ConfigData: - - """Contains configuration information provided by the user for the build.""" - - def __init__(self, config=None): - if config: - self.binaries = _get_section(config, 'binaries') - # global is a keyword and globals is a builtin, rather than mangle it, - # use a similar word - self.universal = _get_section(config, 'globals') - self.subprojects = {s: _get_section(config, s) for s in config.sections() - if s not in {'binaries', 'globals'}} - else: - self.binaries = {} - self.universal = {} - self.subprojects = {} - - def get_binaries(self, name): - return self.binaries.get(name, None) - - # This class contains all data that must persist over multiple # invocations of Meson. It is roughly the same thing as # cmakecache. diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index cd0293920..35237d09d 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -33,7 +33,8 @@ from pathlib import PurePath from .. import mlog from .. import mesonlib from ..compilers import clib_langs -from ..mesonlib import MesonException, OrderedSet +from ..environment import BinaryTable +from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify # These must be defined in this file to avoid cyclical references. @@ -393,24 +394,21 @@ class ConfigToolDependency(ExternalDependency): if not isinstance(versions, list) and versions is not None: versions = listify(versions) - if self.env.is_cross_build() and not self.native: - cross_file = self.env.cross_info.config['binaries'] - try: - tools = [cross_file[self.tool_name]] - except KeyError: + for_machine = MachineChoice.BUILD if self.native else MachineChoice.HOST + tool = self.env.binaries[for_machine].lookup_entry(self.tool_name) + if tool is not None: + tools = [tool] + else: + if self.env.is_cross_build() and not self.native: mlog.warning('No entry for {0} specified in your cross file. ' 'Falling back to searching PATH. This may find a ' 'native version of {0}!'.format(self.tool_name)) - tools = self.tools - elif self.tool_name in self.env.config_info.binaries: - tools = [self.env.config_info.binaries[self.tool_name]] - else: - tools = self.tools + tools = [[t] for t in self.tools] best_match = (None, None) for tool in tools: try: - p, out = Popen_safe([tool, '--version'])[:2] + p, out = Popen_safe(tool + ['--version'])[:2] except (FileNotFoundError, PermissionError): continue if p.returncode != 0: @@ -447,12 +445,12 @@ class ConfigToolDependency(ExternalDependency): mlog.log('Found', mlog.bold(self.tool_name), repr(req_version), mlog.red('NO')) return False - mlog.log('Found {}:'.format(self.tool_name), mlog.bold(shutil.which(self.config)), + mlog.log('Found {}:'.format(self.tool_name), mlog.bold(shutil.which(self.config[0])), '({})'.format(version)) return True def get_config_value(self, args, stage): - p, out, err = Popen_safe([self.config] + args) + p, out, err = Popen_safe(self.config + args) # This is required to keep shlex from stripping path separators on # Windows. Also, don't put escape sequences in config values, okay? out = out.replace('\\', '\\\\') @@ -469,7 +467,7 @@ class ConfigToolDependency(ExternalDependency): return [DependencyMethods.AUTO, DependencyMethods.CONFIG_TOOL] def get_configtool_variable(self, variable_name): - p, out, _ = Popen_safe([self.config, '--{}'.format(variable_name)]) + p, out, _ = Popen_safe(self.config + ['--{}'.format(variable_name)]) if p.returncode != 0: if self.required: raise DependencyException( @@ -486,7 +484,7 @@ class ConfigToolDependency(ExternalDependency): class PkgConfigDependency(ExternalDependency): # The class's copy of the pkg-config path. Avoids having to search for it # multiple times in the same Meson invocation. - class_pkgbin = None + class_pkgbin = PerMachine(None, None, None) # We cache all pkg-config subprocess invocations to avoid redundant calls pkgbin_cache = {} @@ -498,31 +496,56 @@ class PkgConfigDependency(ExternalDependency): # stored in the pickled coredata and recovered. self.pkgbin = None - # When finding dependencies for cross-compiling, we don't care about - # the 'native' pkg-config - if self.want_cross: - if 'pkgconfig' not in environment.cross_info.config['binaries']: - if self.required: - raise DependencyException('Pkg-config binary missing from cross file') + if not self.want_cross and environment.is_cross_build(): + for_machine = MachineChoice.BUILD + else: + for_machine = MachineChoice.HOST + + # Create a nested function for sake of early return + def search(): + # Only search for the pkg-config for each machine the first time and + # store the result in the class definition + if PkgConfigDependency.class_pkgbin[for_machine] is None: + mlog.debug('Pkg-config binary for %s is not cached.' % for_machine) + else: + mlog.debug('Pkg-config binary for %s is cached.' % for_machine) + choice = PkgConfigDependency.class_pkgbin[for_machine] + assert choice is not None + return choice + # Lookup in cross or machine file. + bt = environment.binaries[for_machine] + potential_pkgpath = bt.lookup_entry('pkgconfig') + if potential_pkgpath is None: + mlog.debug('Pkg-config binary missing from cross or native file, or PKG_CONFIG undefined.') else: - potential_pkgbin = ExternalProgram.from_bin_list( - environment.cross_info.config['binaries'], 'pkgconfig') - if potential_pkgbin.found(): - self.pkgbin = potential_pkgbin + mlog.debug('Pkg-config binary for %s specified from config file as %s.', for_machine, potential_pkgpath) + potential_pkgbin = ExternalProgram.from_entry('pkgconfig', potential_pkgpath) + if not potential_pkgbin.found(): + mlog.debug( + 'Pkg-config %s for machine %s specified at %s but not found.', + potential_pkgbin.name, for_machine, potential_pkgbin.command) else: - mlog.debug('Cross pkg-config %s not found.' % potential_pkgbin.name) - # Only search for the native pkg-config the first time and - # store the result in the class definition - elif PkgConfigDependency.class_pkgbin is None: - self.pkgbin = self.check_pkgconfig() - PkgConfigDependency.class_pkgbin = self.pkgbin - else: - self.pkgbin = PkgConfigDependency.class_pkgbin + return potential_pkgbin + # Fallback on hard-coded defaults. + if environment.machines.matches_build_machine(for_machine): + for potential_pkgpath in environment.default_pkgconfig: + potential_pkgbin = self.check_pkgconfig(potential_pkgpath) + if potential_pkgbin is None: + mlog.debug( + 'default Pkg-config fallback %s for machine %s specified at %s but not found.', + potential_pkgbin.name, for_machine, potential_pkgbin.command) + else: + return potential_pkgbin - if not self.pkgbin: + self.pkgbin = search() + if self.pkgbin is None: + msg = 'Pkg-config binary for machine %s not found.' % for_machine if self.required: - raise DependencyException('Pkg-config not found.') - return + raise DependencyException(msg) + else: + mlog.debug(msg) + else: + PkgConfigDependency.class_pkgbin[for_machine] = self.pkgbin mlog.debug('Determining dependency {!r} with pkg-config executable ' '{!r}'.format(name, self.pkgbin.get_path())) @@ -785,12 +808,7 @@ class PkgConfigDependency(ExternalDependency): def get_methods(): return [DependencyMethods.PKGCONFIG] - def check_pkgconfig(self): - evar = 'PKG_CONFIG' - if evar in os.environ: - pkgbin = os.environ[evar].strip() - else: - pkgbin = 'pkg-config' + def check_pkgconfig(self, pkgbin): pkgbin = ExternalProgram(pkgbin, silent=True) if pkgbin.found(): try: @@ -1688,14 +1706,15 @@ class ExternalProgram: '''Human friendly description of the command''' return ' '.join(self.command) - @staticmethod - def from_bin_list(bins, name): - if name not in bins: + @classmethod + def from_bin_list(cls, bt: BinaryTable, name): + command = bt.lookup_entry(name) + if command is None: return NonExistingExternalProgram() - command = bins[name] - if not isinstance(command, (list, str)): - raise MesonException('Invalid type {!r} for binary {!r} in cross file' - ''.format(command, name)) + return cls.from_entry(name, command) + + @staticmethod + def from_entry(name, command): if isinstance(command, list): if len(command) == 1: command = command[0] @@ -1703,6 +1722,7 @@ class ExternalProgram: # need to search if the path is an absolute path. if isinstance(command, list) or os.path.isabs(command): return ExternalProgram(name, command=command, silent=True) + assert isinstance(command, str) # Search for the command using the specified string! return ExternalProgram(command, silent=True) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 7b3d051e9..03c96f223 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -333,22 +333,18 @@ class QtBaseDependency(ExternalDependency): if prefix: self.bindir = os.path.join(prefix, 'bin') - def _find_qmake(self, qmake): - # Even when cross-compiling, if a cross-info qmake is not specified, we - # fallback to using the qmake in PATH because that's what we used to do - if self.env.is_cross_build(): - if 'qmake' in self.env.cross_info.config['binaries']: - return ExternalProgram.from_bin_list(self.env.cross_info.config['binaries'], 'qmake') - elif self.env.config_info: - # Prefer suffixed to unsuffixed version - p = ExternalProgram.from_bin_list(self.env.config_info.binaries, 'qmake') - if p.found(): - return p - return ExternalProgram(qmake, silent=True) - def _qmake_detect(self, mods, kwargs): for qmake in ('qmake-' + self.name, 'qmake'): - self.qmake = self._find_qmake(qmake) + self.qmake = ExternalProgram.from_bin_list( + self.env.binaries.host, qmake) + if not self.qmake.found(): + # Even when cross-compiling, if a cross-info qmake is not + # specified, we fallback to using the qmake in PATH because + # that's what we used to do + self.qmake = ExternalProgram.from_bin_list( + self.env.binaries.build, qmake) + if not self.qmake.found(): + self.qmake = ExternalProgram(qmake, silent=True) if not self.qmake.found(): continue # Check that the qmake is for qt5 diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 71f75f906..b10f826ee 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -17,7 +17,9 @@ import configparser, os, platform, re, sys, shlex, shutil, subprocess from . import coredata from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker, CcrxLinker from . import mesonlib -from .mesonlib import MesonException, EnvironmentException, PerMachine, Popen_safe +from .mesonlib import ( + MesonException, EnvironmentException, MachineChoice, PerMachine, Popen_safe +) from . import mlog from . import compilers @@ -336,32 +338,46 @@ class Environment: else: # Just create a fresh coredata in this case self.create_new_coredata(options) - self.exe_wrapper = None self.machines = MachineInfos() # Will be fully initialized later using compilers later. self.machines.detect_build() - if self.coredata.cross_file: - self.cross_info = CrossBuildInfo(self.coredata.cross_file) - if 'exe_wrapper' in self.cross_info.config['binaries']: - from .dependencies import ExternalProgram - self.exe_wrapper = ExternalProgram.from_bin_list( - self.cross_info.config['binaries'], 'exe_wrapper') - if 'host_machine' in self.cross_info.config: - self.machines.host = MachineInfo.from_literal( - self.cross_info.config['host_machine']) - if 'target_machine' in self.cross_info.config: - self.machines.target = MachineInfo.from_literal( - self.cross_info.config['target_machine']) - else: - self.cross_info = None - self.machines.default_missing() - if self.coredata.config_files: - self.config_info = coredata.ConfigData( + # Similar to coredata.compilers and build.compilers, but lower level in + # that there is no meta data, only names/paths. + self.binaries = PerMachineDefaultable() + # Just uses hard-coded defaults and environment variables. Might be + # overwritten by a native file. + self.binaries.build = BinaryTable({}) + + # Misc other properties about each machine. + self.properties = PerMachine(Properties(), Properties(), Properties()) + + if self.coredata.config_files is not None: + config = MesonConfigFile.from_config_parser( coredata.load_configs(self.coredata.config_files)) + self.binaries.build = BinaryTable(config.get('binaries', {})) + + if self.coredata.cross_file is not None: + config = MesonConfigFile.parse_datafile(self.coredata.cross_file) + self.properties.host.properties = config.get('properties', {}) + self.binaries.host = BinaryTable(config.get('binaries', {}), False) + if 'host_machine' in config: + self.machines.host = MachineInfo.from_literal(config['host_machine']) + if 'target_machine' in config: + self.machines.target = MachineInfo.from_literal(config['target_machine']) + + self.machines.default_missing() + self.binaries.default_missing() + + exe_wrapper = self.binaries.host.lookup_entry('exe_wrapper') + if exe_wrapper is not None: + from .dependencies import ExternalProgram + self.exe_wrapper = ExternalProgram.from_bin_list( + self.binaries.host, + 'exe_wrapper') else: - self.config_info = coredata.ConfigData() + self.exe_wrapper = None self.cmd_line_options = options.cmd_line_options.copy() @@ -385,10 +401,12 @@ class Environment: self.default_swift = ['swiftc'] self.default_vala = ['valac'] self.default_static_linker = ['ar'] + self.default_strip = ['strip'] self.vs_static_linker = ['lib'] self.clang_cl_static_linker = ['llvm-lib'] self.gcc_static_linker = ['gcc-ar'] self.clang_static_linker = ['llvm-ar'] + self.default_pkgconfig = ['pkg-config'] # Various prefixes and suffixes for import libraries, shared libraries, # static libraries, and executables. @@ -406,11 +424,6 @@ class Environment: self.exe_suffix = '' self.object_suffix = 'o' self.win_libdir_layout = False - if 'STRIP' in os.environ: - self.native_strip_bin = shlex.split( - os.environ[BinaryTable.evarMap['strip']]) - else: - self.native_strip_bin = ['strip'] def create_new_coredata(self, options): # WARNING: Don't use any values from coredata in __init__. It gets @@ -422,7 +435,7 @@ class Environment: self.first_invocation = True def is_cross_build(self): - return self.cross_info is not None + return not self.machines.matches_build_machine(MachineChoice.HOST) def dump_coredata(self): return coredata.save(self.coredata, self.get_build_dir()) @@ -521,32 +534,30 @@ class Environment: The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. ''' - is_cross = False - exe_wrap = None - evar = BinaryTable.evarMap[lang] - - if self.is_cross_build() and want_cross: - if lang not in self.cross_info.config['binaries']: - raise EnvironmentException('{!r} compiler binary not defined in cross file'.format(lang)) - compilers, ccache = BinaryTable.parse_entry( - mesonlib.stringlistify(self.cross_info.config['binaries'][lang])) - BinaryTable.warn_about_lang_pointing_to_cross(compilers[0], evar) - # Return value has to be a list of compiler 'choices' - compilers = [compilers] - is_cross = True - exe_wrap = self.get_exe_wrapper() - elif evar in os.environ: - compilers, ccache = BinaryTable.parse_entry( - shlex.split(os.environ[evar])) + + # This morally assumes `want_cross = !native`. It may not yet be + # consistently set that way in the non cross build case, but it doesn't + # really matter since both options are the same in that case. + for_machine = MachineChoice.HOST if want_cross else MachineChoice.BUILD + + value = self.binaries[for_machine].lookup_entry(lang) + if value is not None: + compilers, ccache = BinaryTable.parse_entry(value) # Return value has to be a list of compiler 'choices' compilers = [compilers] - elif lang in self.config_info.binaries: - compilers, ccache = BinaryTable.parse_entry( - mesonlib.stringlistify(self.config_info.binaries[lang])) - compilers = [compilers] else: + if not self.machines.matches_build_machine(for_machine): + raise EnvironmentException('{!r} compiler binary not defined in cross or native file'.format(lang)) compilers = getattr(self, 'default_' + lang) ccache = BinaryTable.detect_ccache() + + if self.machines.matches_build_machine(for_machine): + is_cross = False + exe_wrap = None + else: + is_cross = True + exe_wrap = self.get_exe_wrapper() + return compilers, ccache, is_cross, exe_wrap def _handle_exceptions(self, exceptions, binaries, bintype='compiler'): @@ -826,9 +837,8 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) def detect_java_compiler(self): - if 'java' in self.config_info.binaries: - exelist = mesonlib.stringlistify(self.config_info.binaries['java']) - else: + exelist = self.binaries.host.lookup_entry('java') + if exelist is None: # TODO support fallback exelist = [self.default_java[0]] @@ -862,13 +872,11 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) def detect_vala_compiler(self): - if 'VALAC' in os.environ: - exelist = shlex.split(os.environ['VALAC']) - elif 'vala' in self.config_info.binaries: - exelist = mesonlib.stringlistify(self.config_info.binaries['vala']) - else: + exelist = self.binaries.host.lookup_entry('vala') + if exelist is None: # TODO support fallback exelist = [self.default_vala[0]] + try: p, out = Popen_safe(exelist + ['--version'])[0:2] except OSError: @@ -899,20 +907,15 @@ class Environment: self._handle_exceptions(popen_exceptions, compilers) def detect_d_compiler(self, want_cross): - is_cross = False + is_cross = want_cross and self.is_cross_build() + exelist = self.binaries.host.lookup_entry('d') # Search for a D compiler. # We prefer LDC over GDC unless overridden with the DC # environment variable because LDC has a much more # up to date language version at time (2016). - if 'DC' in os.environ: - exelist = shlex.split(os.environ['DC']) + if exelist is not None: if os.path.basename(exelist[-1]).startswith(('ldmd', 'gdmd')): raise EnvironmentException('Meson doesn\'t support %s as it\'s only a DMD frontend for another compiler. Please provide a valid value for DC or unset it so that Meson can resolve the compiler by itself.' % exelist[-1]) - elif self.is_cross_build() and want_cross: - exelist = mesonlib.stringlistify(self.cross_info.config['binaries']['d']) - is_cross = True - elif 'd' in self.config_info.binaries: - exelist = mesonlib.stringlistify(self.config_info.binaries['d']) else: for d in self.default_d: if shutil.which(d): @@ -947,11 +950,11 @@ class Environment: raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') def detect_swift_compiler(self): - if 'swift' in self.config_info.binaries: - exelist = mesonlib.stringlistify(self.config_info.binaries['swift']) - else: + exelist = self.binaries.host.lookup_entry('swift') + if exelist is None: # TODO support fallback exelist = [self.default_swift[0]] + try: p, _, err = Popen_safe(exelist + ['-v']) except OSError: @@ -1013,16 +1016,11 @@ class Environment: return comp, cross_comp def detect_static_linker(self, compiler): - if compiler.is_cross: - linker = self.cross_info.config['binaries']['ar'] - if isinstance(linker, str): - linker = [linker] + linker = self.binaries.host.lookup_entry('ar') + if linker is not None: linkers = [linker] else: - evar = BinaryTable.evarMap['ar'] - if evar in os.environ: - linkers = [shlex.split(os.environ[evar])] - elif isinstance(compiler, compilers.VisualStudioCCompiler): + if isinstance(compiler, compilers.VisualStudioCCompiler): linkers = [self.vs_static_linker, self.clang_cl_static_linker] elif isinstance(compiler, compilers.GnuCompiler): # Use gcc-ar if available; needed for LTO @@ -1140,37 +1138,39 @@ class Environment: out = out.split('\n')[index].lstrip('libraries: =').split(':') return [os.path.normpath(p) for p in out] + def need_exe_wrapper(self, for_machine: MachineChoice = MachineChoice.HOST): + 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() + def get_exe_wrapper(self): - if not self.cross_info.need_exe_wrapper(): + if not self.need_exe_wrapper(): from .dependencies import EmptyExternalProgram return EmptyExternalProgram() return self.exe_wrapper - -class CrossBuildInfo: - def __init__(self, filename): - self.config = {'properties': {}} - self.parse_datafile(filename) - if 'host_machine' not in self.config and 'target_machine' not in self.config: - raise mesonlib.MesonException('Cross info file must have either host or a target machine.') - if 'host_machine' in self.config and 'binaries' not in self.config: - raise mesonlib.MesonException('Cross file with "host_machine" is missing "binaries".') - - def ok_type(self, i): - return isinstance(i, (str, int, bool)) - - def parse_datafile(self, filename): +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 config.sections(): - self.config[s] = {} - for entry in config[s]: - value = config[s][entry] + 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: @@ -1178,61 +1178,48 @@ class CrossBuildInfo: except Exception: raise EnvironmentException('Malformed value in cross file variable %s.' % entry) - if self.ok_type(res): - self.config[s][entry] = res - elif isinstance(res, list): - for i in res: - if not self.ok_type(i): - raise EnvironmentException('Malformed value in cross file variable %s.' % entry) - self.config[s][entry] = res - else: - 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) - def has_host(self): - return 'host_machine' in self.config + section[entry] = res - def has_target(self): - return 'target_machine' in self.config + out[s] = section + return out - def has_stdlib(self, language): - return language + '_stdlib' in self.config['properties'] +class Properties: + def __init__(self): + self.properties = {} - def get_stdlib(self, language): - return self.config['properties'][language + '_stdlib'] + def get_external_args(self, language): + return mesonlib.stringlistify(self.properties.get(language + '_args', [])) + + def get_external_link_args(self, language): + return mesonlib.stringlistify(self.properties.get(language + '_link_args', [])) - def get_host_system(self): - "Name of host system like 'linux', or None" - if self.has_host(): - return self.config['host_machine']['system'] - return None + def has_stdlib(self, language): + return language + '_stdlib' in self.properties - def get_properties(self): - return self.config['properties'] + def get_stdlib(self, language): + return self.properties[language + '_stdlib'] def get_root(self): - return self.get_properties().get('root', None) + return self.properties.get('root', None) def get_sys_root(self): - return self.get_properties().get('sys_root', None) + return self.properties.get('sys_root', None) - # When compiling a cross compiler we use the native compiler for everything. - # But not when cross compiling a cross compiler. - def need_cross_compiler(self): - return 'host_machine' in self.config + # TODO consider removing so Properties is less freeform + def __getitem__(self, key): + return self.properties[key] - def need_exe_wrapper(self): - value = self.config['properties'].get('needs_exe_wrapper', None) - if value is not None: - return value - # 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.has_host() and detect_cpu_family({}) == 'x86_64' and \ - self.config['host_machine']['cpu_family'] == 'x86' and \ - self.config['host_machine']['system'] == detect_system(): - return False - return True + # 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): @@ -1354,7 +1341,26 @@ class MachineInfo: return self.is_windows() \ or self.is_cygwin() -class MachineInfos(PerMachine): + # 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) @@ -1382,10 +1388,24 @@ class MachineInfos(PerMachine): 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 @@ -1402,6 +1422,9 @@ class BinaryTable: # Binutils 'strip': 'STRIP', 'ar': 'AR', + 'windres': 'WINDRES', + + 'pkgconfig': 'PKG_CONFIG', } @classmethod @@ -1417,7 +1440,7 @@ class BinaryTable: return cmdlist @classmethod - def warn_about_lang_pointing_to_cross(cls, compiler_exe, evar): + 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. @@ -1434,3 +1457,26 @@ This is probably wrong, it should always point to the native compiler.''' % evar 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 diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 61481a491..f849f3ce0 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,7 +21,7 @@ from . import optinterpreter from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, Popen_safe, listify, extract_as_list, has_path_sep +from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException from .interpreterbase import InterpreterBase @@ -1760,7 +1760,7 @@ class MesonMain(InterpreterObject): @permittedKwargs({}) def has_exe_wrapper_method(self, args, kwargs): if self.is_cross_build_method(None, None) and \ - self.build.environment.cross_info.need_exe_wrapper(): + self.build.environment.need_exe_wrapper(): if self.build.environment.exe_wrapper is None: return False # We return True when exe_wrap is defined, when it's not needed, and @@ -1866,7 +1866,7 @@ class MesonMain(InterpreterObject): if not isinstance(propname, str): raise InterpreterException('Property name must be string.') try: - props = self.interpreter.environment.cross_info.get_properties() + props = self.interpreter.environment.properties.host return props[propname] except Exception: if len(args) == 2: @@ -2164,10 +2164,10 @@ class Interpreter(InterpreterBase): def check_cross_stdlibs(self): if self.build.environment.is_cross_build(): - cross_info = self.build.environment.cross_info + props = self.build.environment.properties.host for l, c in self.build.cross_compilers.items(): try: - di = mesonlib.stringlistify(cross_info.get_stdlib(l)) + di = mesonlib.stringlistify(props.get_stdlib(l)) if len(di) != 2: raise InterpreterException('Stdlib definition for %s should have exactly two elements.' % l) @@ -2685,7 +2685,7 @@ external dependencies (including libraries) must go to "dependencies".''') def add_languages(self, args, required): success = True - need_cross_compiler = self.environment.is_cross_build() and self.environment.cross_info.need_cross_compiler() + need_cross_compiler = self.environment.is_cross_build() for lang in sorted(args, key=compilers.sort_clink): lang = lang.lower() if lang in self.coredata.compilers: @@ -2735,7 +2735,8 @@ external dependencies (including libraries) must go to "dependencies".''') self.coredata. base_options[optname] = oobj self.emit_base_options_warnings(enabled_opts) - def _program_from_file(self, prognames, bins, silent): + def program_from_file_for(self, for_machine, prognames, silent): + bins = self.environment.binaries[for_machine] for p in prognames: if hasattr(p, 'held_object'): p = p.held_object @@ -2748,14 +2749,6 @@ external dependencies (including libraries) must go to "dependencies".''') return ExternalProgramHolder(prog) return None - def program_from_cross_file(self, prognames, silent=False): - bins = self.environment.cross_info.config['binaries'] - return self._program_from_file(prognames, bins, silent) - - def program_from_config_file(self, prognames, silent=False): - bins = self.environment.config_info.binaries - return self._program_from_file(prognames, bins, silent) - def program_from_system(self, args, silent=False): # Search for scripts relative to current subdir. # Do not cache found programs because find_program('foobar') @@ -2813,11 +2806,8 @@ external dependencies (including libraries) must go to "dependencies".''') progobj = self.program_from_overrides(args, silent=silent) if progobj is None: - if self.build.environment.is_cross_build() and not native: - progobj = self.program_from_cross_file(args, silent=silent) - else: - progobj = self.program_from_config_file(args, silent=silent) - + for_machine = MachineChoice.BUILD if native else MachineChoice.HOST + progobj = self.program_from_file_for(for_machine, args, silent=silent) if progobj is None: progobj = self.program_from_system(args, silent=silent) if required and (progobj is None or not progobj.found()): @@ -4121,8 +4111,9 @@ This will become a hard error in the future.''') def add_cross_stdlib_info(self, target): for l in self.get_used_languages(target): - if self.environment.cross_info.has_stdlib(l) \ - and self.subproject != self.environment.cross_info.get_stdlib(l)[0]: + props = self.environment.properties.host + if props.has_stdlib(l) \ + and self.subproject != props.get_stdlib(l)[0]: target.add_deps(self.build.cross_stdlibs[l]) def check_sources_exist(self, subdir, sources): diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index be9905994..2ab575c4c 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -532,7 +532,7 @@ class GnomeModule(ExtensionModule): for lang in langs: if state.environment.is_cross_build(): - link_args = state.environment.cross_info.config["properties"].get(lang + '_link_args', "") + link_args = state.environment.properties.host.get_external_link_args(lang) else: link_args = state.environment.coredata.get_external_link_args(lang) @@ -715,7 +715,7 @@ class GnomeModule(ExtensionModule): ret = [] for lang in langs: if state.environment.is_cross_build(): - ret += state.environment.cross_info.config["properties"].get(lang + '_args', "") + ret += state.environment.properties.host.get_external_args(lang) else: ret += state.environment.coredata.get_external_args(lang) return ret @@ -1043,8 +1043,8 @@ This will become a hard error in the future.''') ldflags.update(external_ldflags) if state.environment.is_cross_build(): - cflags.update(state.environment.cross_info.config["properties"].get('c_args', "")) - ldflags.update(state.environment.cross_info.config["properties"].get('c_link_args', "")) + cflags.update(state.environment.properties.host.get_external_args('c')) + ldflags.update(state.environment.properties.host.get_external_link_args('c')) compiler = state.environment.coredata.cross_compilers.get('c') else: cflags.update(state.environment.coredata.get_external_args('c')) diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 9643ebc44..c14ac8588 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -501,20 +501,17 @@ class PythonModule(ExtensionModule): if len(args) > 1: raise InvalidArguments('find_installation takes zero or one positional argument.') - if 'python' in state.environment.config_info.binaries: - name_or_path = state.environment.config_info.binaries['python'] - elif args: + name_or_path = state.environment.binaries.host.lookup_entry('python') + if name_or_path is None and args: name_or_path = args[0] if not isinstance(name_or_path, str): raise InvalidArguments('find_installation argument must be a string.') - else: - name_or_path = None if not name_or_path: mlog.log("Using meson's python {}".format(mesonlib.python_command)) python = ExternalProgram('python3', mesonlib.python_command, silent=True) else: - python = ExternalProgram(name_or_path, silent = True) + python = ExternalProgram.from_entry('python3', name_or_path) if not python.found() and mesonlib.is_windows(): pythonpath = self._get_win_pythonpath(name_or_path) diff --git a/mesonbuild/modules/python3.py b/mesonbuild/modules/python3.py index f6646327a..46f15f04e 100644 --- a/mesonbuild/modules/python3.py +++ b/mesonbuild/modules/python3.py @@ -48,10 +48,11 @@ class Python3Module(ExtensionModule): @noKwargs def find_python(self, state, args, kwargs): - options = [state.environment.config_info.binaries.get('python3')] - if not options[0]: # because this would be [None] - options = ['python3', mesonlib.python_command] - py3 = dependencies.ExternalProgram(*options, silent=True) + command = state.environment.binaries.host.lookup_entry('python3') + if command is not None: + py3 = dependencies.ExternalProgram.from_entry('python3', command) + else: + py3 = dependencies.ExternalProgram('python3', mesonlib.python_command, silent=True) return ModuleReturnValue(py3, [py3]) @noKwargs diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py index d185d8913..e8d266e54 100644 --- a/mesonbuild/modules/windows.py +++ b/mesonbuild/modules/windows.py @@ -41,29 +41,13 @@ class WindowsModule(ExtensionModule): def _find_resource_compiler(self, state): # FIXME: Does not handle `native: true` executables, see # See https://github.com/mesonbuild/meson/issues/1531 + # But given a machine, we can un-hardcode `binaries.host` below. if hasattr(self, '_rescomp'): return self._rescomp - rescomp = None - if state.environment.is_cross_build(): - # If cross compiling see if windres has been specified in the - # cross file before trying to find it another way. - bins = state.environment.cross_info.config['binaries'] - rescomp = ExternalProgram.from_bin_list(bins, 'windres') - - if not rescomp or not rescomp.found(): - if 'WINDRES' in os.environ: - # Pick-up env var WINDRES if set. This is often used for - # specifying an arch-specific windres. - rescomp = ExternalProgram('windres', command=os.environ.get('WINDRES'), silent=True) - - if not rescomp or not rescomp.found(): - # Take windres from the config file after the environment, which is - # in keeping with the expectations on unix-like OSes that - # environment variables trump config files. - bins = state.environment.config_info.binaries - rescomp = ExternalProgram.from_bin_list(bins, 'windres') + # Will try cross / native file and then env var + rescomp = ExternalProgram.from_bin_list(state.environment.binaries.host, 'windres') if not rescomp or not rescomp.found(): comp = self.detect_compiler(state.compilers) diff --git a/run_tests.py b/run_tests.py index aa8a5898e..25a2d7f97 100755 --- a/run_tests.py +++ b/run_tests.py @@ -76,8 +76,10 @@ def get_fake_options(prefix): opts.native_file = [] return opts -def get_fake_env(sdir, bdir, prefix): - env = Environment(sdir, bdir, get_fake_options(prefix)) +def get_fake_env(sdir, bdir, prefix, opts = None): + if opts is None: + opts = get_fake_options(prefix) + env = Environment(sdir, bdir, opts) env.coredata.compiler_options['c_args'] = FakeCompilerOptions() return env diff --git a/run_unittests.py b/run_unittests.py index 07e6bf9be..46f93e72f 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -54,7 +54,7 @@ import mesonbuild.modules.pkgconfig from run_tests import ( Backend, FakeBuild, FakeCompilerOptions, ensure_backend_detects_changes, exe_suffix, get_backend_commands, - get_builddir_target_args, get_fake_env, get_meson_script, + get_builddir_target_args, get_fake_env, get_fake_options, get_meson_script, run_configure_inprocess, run_mtest_inprocess ) @@ -542,7 +542,10 @@ class InternalTests(unittest.TestCase): config.write(configfile) configfile.flush() configfile.close() - detected_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper() + opts = get_fake_options('') + opts.cross_file = configfilename + env = get_fake_env('', '', '', opts) + detected_value = env.need_exe_wrapper() os.unlink(configfilename) desired_value = not detected_value @@ -554,7 +557,10 @@ class InternalTests(unittest.TestCase): configfilename = configfile.name config.write(configfile) configfile.close() - forced_value = mesonbuild.environment.CrossBuildInfo(configfile.name).need_exe_wrapper() + opts = get_fake_options('') + opts.cross_file = configfilename + env = get_fake_env('', '', '', opts) + forced_value = env.need_exe_wrapper() os.unlink(configfilename) self.assertEqual(forced_value, desired_value) @@ -744,8 +750,9 @@ class InternalTests(unittest.TestCase): old_call = PkgConfigDependency._call_pkgbin old_check = PkgConfigDependency.check_pkgconfig + old_pkgbin = PkgConfigDependency.class_pkgbin PkgConfigDependency._call_pkgbin = fake_call_pkgbin - PkgConfigDependency.check_pkgconfig = lambda x: pkgbin + PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin # Test begins kwargs = {'required': True, 'silent': True} foo_dep = PkgConfigDependency('foo', env, kwargs) @@ -766,7 +773,7 @@ class InternalTests(unittest.TestCase): PkgConfigDependency.check_pkgconfig = old_check # Reset dependency class to ensure that in-process configure doesn't mess up PkgConfigDependency.pkgbin_cache = {} - PkgConfigDependency.class_pkgbin = None + PkgConfigDependency.class_pkgbin = old_pkgbin def test_version_compare(self): comparefunc = mesonbuild.mesonlib.version_compare_many @@ -4843,7 +4850,7 @@ class NativeFileTests(BasePlatformTests): getter = functools.partial(getter, False) cc = getter() binary, newid = cb(cc) - env.config_info.binaries = {lang: binary} + env.binaries.host.binaries[lang] = binary compiler = getter() self.assertEqual(compiler.id, newid) @@ -4998,7 +5005,7 @@ class NativeFileTests(BasePlatformTests): getter = getattr(env, 'detect_{}_compiler'.format(lang)) if lang in ['rust']: getter = functools.partial(getter, False) - env.config_info.binaries = {lang: wrapper} + env.binaries.host.binaries[lang] = wrapper compiler = getter() self.assertEqual(compiler.version, version) @@ -5024,7 +5031,7 @@ class NativeFileTests(BasePlatformTests): wrapper = self.helper_create_binary_wrapper( 'swiftc', version='Swift 1.2345', outfile='stderr') env = get_fake_env('', '', '') - env.config_info.binaries = {'swift': wrapper} + env.binaries.host.binaries['swift'] = wrapper compiler = env.detect_swift_compiler() self.assertEqual(compiler.version, '1.2345')