Merge pull request #4445 from Ericson2314/no-cross_info

Parsing of cross file upfront, and store in cross-agnostic data structures
pull/4726/head
Jussi Pakkanen 6 years ago committed by GitHub
commit 1aca899a63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      mesonbuild/backend/backends.py
  2. 32
      mesonbuild/backend/ninjabackend.py
  3. 2
      mesonbuild/compilers/c.py
  4. 11
      mesonbuild/compilers/compilers.py
  5. 36
      mesonbuild/coredata.py
  6. 120
      mesonbuild/dependencies/base.py
  7. 24
      mesonbuild/dependencies/ui.py
  8. 322
      mesonbuild/environment.py
  9. 35
      mesonbuild/interpreter.py
  10. 8
      mesonbuild/modules/gnome.py
  11. 9
      mesonbuild/modules/python.py
  12. 9
      mesonbuild/modules/python3.py
  13. 22
      mesonbuild/modules/windows.py
  14. 6
      run_tests.py
  15. 23
      run_unittests.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(),

@ -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()

@ -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

@ -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):

@ -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.

@ -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)

@ -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

@ -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

@ -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):

@ -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'))

@ -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)

@ -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

@ -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)

@ -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

@ -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')

Loading…
Cancel
Save