The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1814 lines
70 KiB

# Copyright 2013-2017 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This file contains the detection logic for external
# dependencies. Mostly just uses pkg-config but also contains
# custom logic for packages that don't provide them.
# Currently one file, should probably be split into a
# package before this gets too big.
import re
import sys
import os, stat, glob, shutil
import shlex
import subprocess
import sysconfig
from enum import Enum
from collections import OrderedDict
from . import mlog
from . import mesonlib
from .mesonlib import Popen_safe, flatten
from .mesonlib import MesonException, version_compare, version_compare_many
from .environment import detect_cpu_family, for_windows
class DependencyException(MesonException):
'''Exceptions raised while trying to find dependencies'''
class DependencyMethods(Enum):
# Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best.
AUTO = 'auto'
PKGCONFIG = 'pkg-config'
QMAKE = 'qmake'
# Just specify the standard link arguments, assuming the operating system provides the library.
SYSTEM = 'system'
# Detect using sdl2-config
SDLCONFIG = 'sdlconfig'
# This is only supported on OSX - search the frameworks directory by name.
EXTRAFRAMEWORK = 'extraframework'
# Detect using the sysconfig module.
SYSCONFIG = 'sysconfig'
class Dependency:
def __init__(self, type_name, kwargs):
self.name = "null"
self.language = None
self.is_found = False
self.type_name = type_name
method = DependencyMethods(kwargs.get('method', 'auto'))
# Set the detection method. If the method is set to auto, use any available method.
# If method is set to a specific string, allow only that detection method.
if method == DependencyMethods.AUTO:
self.methods = self.get_methods()
elif method in self.get_methods():
self.methods = [method]
else:
raise MesonException('Unsupported detection method: {}, allowed methods are {}'.format(method.value, mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods()))))
def __repr__(self):
s = '<{0} {1}: {2}>'
return s.format(self.__class__.__name__, self.name, self.is_found)
def get_compile_args(self):
return []
def get_link_args(self):
return []
def found(self):
return self.is_found
def get_sources(self):
"""Source files that need to be added to the target.
As an example, gtest-all.cc when using GTest."""
return []
def get_methods(self):
return [DependencyMethods.AUTO]
def get_name(self):
return self.name
def get_exe_args(self, compiler):
return []
def need_threads(self):
return False
def get_pkgconfig_variable(self, variable_name):
raise MesonException('Tried to get a pkg-config variable from a non-pkgconfig dependency.')
class InternalDependency(Dependency):
def __init__(self, version, incdirs, compile_args, link_args, libraries, sources, ext_deps):
super().__init__('internal', {})
self.version = version
self.is_found = True
self.include_directories = incdirs
self.compile_args = compile_args
self.link_args = link_args
self.libraries = libraries
self.sources = sources
self.ext_deps = ext_deps
def get_compile_args(self):
return self.compile_args
def get_link_args(self):
return self.link_args
def get_version(self):
return self.version
class PkgConfigDependency(Dependency):
# 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
def __init__(self, name, environment, kwargs):
Dependency.__init__(self, 'pkgconfig', kwargs)
self.is_libtool = False
self.version_reqs = kwargs.get('version', None)
self.required = kwargs.get('required', True)
self.static = kwargs.get('static', False)
self.silent = kwargs.get('silent', False)
if not isinstance(self.static, bool):
raise DependencyException('Static keyword must be boolean')
# Store a copy of the pkg-config path on the object itself so it is
# stored in the pickled coredata and recovered.
self.pkgbin = None
self.cargs = []
self.libs = []
if 'native' in kwargs and environment.is_cross_build():
self.want_cross = not kwargs['native']
else:
self.want_cross = environment.is_cross_build()
self.name = name
self.modversion = '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')
else:
pkgname = environment.cross_info.config['binaries']['pkgconfig']
potential_pkgbin = ExternalProgram(pkgname, silent=True)
if potential_pkgbin.found():
# FIXME, we should store all pkg-configs in ExternalPrograms.
# However that is too destabilizing a change to do just before release.
self.pkgbin = potential_pkgbin.get_command()[0]
PkgConfigDependency.class_pkgbin = self.pkgbin
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
self.is_found = False
if not self.pkgbin:
if self.required:
raise DependencyException('Pkg-config not found.')
return
if self.want_cross:
self.type_string = 'Cross'
else:
self.type_string = 'Native'
mlog.debug('Determining dependency {!r} with pkg-config executable '
'{!r}'.format(name, self.pkgbin))
ret, self.modversion = self._call_pkgbin(['--modversion', name])
if ret != 0:
if self.required:
raise DependencyException('{} dependency {!r} not found'
''.format(self.type_string, name))
return
found_msg = [self.type_string + ' dependency', mlog.bold(name), 'found:']
if self.version_reqs is None:
self.is_found = True
else:
if not isinstance(self.version_reqs, (str, list)):
raise DependencyException('Version argument must be string or list.')
if isinstance(self.version_reqs, str):
self.version_reqs = [self.version_reqs]
(self.is_found, not_found, found) = \
version_compare_many(self.modversion, self.version_reqs)
if not self.is_found:
found_msg += [mlog.red('NO'),
'found {!r} but need:'.format(self.modversion),
', '.join(["'{}'".format(e) for e in not_found])]
if found:
found_msg += ['; matched:',
', '.join(["'{}'".format(e) for e in found])]
if not self.silent:
mlog.log(*found_msg)
if self.required:
m = 'Invalid version of dependency, need {!r} {!r} found {!r}.'
raise DependencyException(m.format(name, not_found, self.modversion))
return
found_msg += [mlog.green('YES'), self.modversion]
# Fetch cargs to be used while using this dependency
self._set_cargs()
# Fetch the libraries and library paths needed for using this
self._set_libs()
# Print the found message only at the very end because fetching cflags
# and libs can also fail if other needed pkg-config files aren't found.
if not self.silent:
mlog.log(*found_msg)
def __repr__(self):
s = '<{0} {1}: {2} {3}>'
return s.format(self.__class__.__name__, self.name, self.is_found,
self.version_reqs)
def _call_pkgbin(self, args):
p, out = Popen_safe([self.pkgbin] + args, env=os.environ)[0:2]
return p.returncode, out.strip()
def _set_cargs(self):
ret, out = self._call_pkgbin(['--cflags', self.name])
if ret != 0:
raise DependencyException('Could not generate cargs for %s:\n\n%s' %
(self.name, out))
self.cargs = out.split()
def _set_libs(self):
libcmd = [self.name, '--libs']
if self.static:
libcmd.append('--static')
ret, out = self._call_pkgbin(libcmd)
if ret != 0:
raise DependencyException('Could not generate libs for %s:\n\n%s' %
(self.name, out))
self.libs = []
for lib in out.split():
if lib.endswith(".la"):
shared_libname = self.extract_libtool_shlib(lib)
shared_lib = os.path.join(os.path.dirname(lib), shared_libname)
if not os.path.exists(shared_lib):
shared_lib = os.path.join(os.path.dirname(lib), ".libs", shared_libname)
if not os.path.exists(shared_lib):
raise DependencyException('Got a libtools specific "%s" dependencies'
'but we could not compute the actual shared'
'library path' % lib)
lib = shared_lib
self.is_libtool = True
self.libs.append(lib)
def get_pkgconfig_variable(self, variable_name):
ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name])
variable = ''
if ret != 0:
if self.required:
raise DependencyException('%s dependency %s not found.' %
(self.type_string, self.name))
else:
variable = out.strip()
mlog.debug('Got pkgconfig variable %s : %s' % (variable_name, variable))
return variable
def get_modversion(self):
return self.modversion
def get_version(self):
return self.modversion
def get_compile_args(self):
return self.cargs
def get_link_args(self):
return self.libs
def get_methods(self):
return [DependencyMethods.PKGCONFIG]
def check_pkgconfig(self):
evar = 'PKG_CONFIG'
if evar in os.environ:
pkgbin = os.environ[evar].strip()
else:
pkgbin = 'pkg-config'
try:
p, out = Popen_safe([pkgbin, '--version'])[0:2]
if p.returncode != 0:
# Set to False instead of None to signify that we've already
# searched for it and not found it
pkgbin = False
except (FileNotFoundError, PermissionError):
pkgbin = False
if pkgbin and not os.path.isabs(pkgbin) and shutil.which(pkgbin):
# Sometimes shutil.which fails where Popen succeeds, so
# only find the abs path if it can be found by shutil.which
pkgbin = shutil.which(pkgbin)
if not self.silent:
if pkgbin:
mlog.log('Found pkg-config:', mlog.bold(pkgbin),
'(%s)' % out.strip())
else:
mlog.log('Found Pkg-config:', mlog.red('NO'))
return pkgbin
def found(self):
return self.is_found
def extract_field(self, la_file, fieldname):
with open(la_file) as f:
for line in f:
arr = line.strip().split('=')
if arr[0] == fieldname:
return arr[1][1:-1]
return None
def extract_dlname_field(self, la_file):
return self.extract_field(la_file, 'dlname')
def extract_libdir_field(self, la_file):
return self.extract_field(la_file, 'libdir')
def extract_libtool_shlib(self, la_file):
'''
Returns the path to the shared library
corresponding to this .la file
'''
dlname = self.extract_dlname_field(la_file)
if dlname is None:
return None
# Darwin uses absolute paths where possible; since the libtool files never
# contain absolute paths, use the libdir field
if mesonlib.is_osx():
dlbasename = os.path.basename(dlname)
libdir = self.extract_libdir_field(la_file)
if libdir is None:
return dlbasename
return os.path.join(libdir, dlbasename)
# From the comments in extract_libtool(), older libtools had
# a path rather than the raw dlname
return os.path.basename(dlname)
class WxDependency(Dependency):
wx_found = None
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'wx', kwargs)
self.is_found = False
# FIXME: use version instead of modversion
self.modversion = 'none'
if WxDependency.wx_found is None:
self.check_wxconfig()
if not WxDependency.wx_found:
# FIXME: this message could be printed after Dependncy found
mlog.log("Neither wx-config-3.0 nor wx-config found; can't detect dependency")
return
# FIXME: This should print stdout and stderr using mlog.debug
p, out = Popen_safe([self.wxc, '--version'])[0:2]
if p.returncode != 0:
mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
self.cargs = []
self.libs = []
else:
self.modversion = out.strip()
version_req = kwargs.get('version', None)
if version_req is not None:
if not version_compare(self.modversion, version_req, strict=True):
mlog.log('Wxwidgets version %s does not fullfill requirement %s' %
(self.modversion, version_req))
return
mlog.log('Dependency wxwidgets found:', mlog.green('YES'))
self.is_found = True
self.requested_modules = self.get_requested(kwargs)
# wx-config seems to have a cflags as well but since it requires C++,
# this should be good, at least for now.
p, out = Popen_safe([self.wxc, '--cxxflags'])[0:2]
# FIXME: this error should only be raised if required is true
if p.returncode != 0:
raise DependencyException('Could not generate cargs for wxwidgets.')
self.cargs = out.split()
# FIXME: this error should only be raised if required is true
p, out = Popen_safe([self.wxc, '--libs'] + self.requested_modules)[0:2]
if p.returncode != 0:
raise DependencyException('Could not generate libs for wxwidgets.')
self.libs = out.split()
def get_requested(self, kwargs):
modules = 'modules'
if modules not in kwargs:
return []
candidates = kwargs[modules]
if isinstance(candidates, str):
return [candidates]
for c in candidates:
if not isinstance(c, str):
raise DependencyException('wxwidgets module argument is not a string.')
return candidates
def get_modversion(self):
return self.modversion
def get_version(self):
return self.modversion
def get_compile_args(self):
return self.cargs
def get_link_args(self):
return self.libs
def check_wxconfig(self):
for wxc in ['wx-config-3.0', 'wx-config']:
try:
p, out = Popen_safe([wxc, '--version'])[0:2]
if p.returncode == 0:
mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
'(%s)' % out.strip())
self.wxc = wxc
WxDependency.wx_found = True
return
except (FileNotFoundError, PermissionError):
pass
WxDependency.wxconfig_found = False
mlog.log('Found wx-config:', mlog.red('NO'))
def found(self):
return self.is_found
class ExternalProgram:
windows_exts = ('exe', 'msc', 'com', 'bat')
def __init__(self, name, command=None, silent=False, search_dir=None):
self.name = name
if command is not None:
if not isinstance(command, list):
self.command = [command]
else:
self.command = command
else:
self.command = self._search(name, search_dir)
if not silent:
if self.found():
mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
'(%s)' % ' '.join(self.command))
else:
mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
def __repr__(self):
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
@staticmethod
def _shebang_to_cmd(script):
"""
Check if the file has a shebang and manually parse it to figure out
the interpreter to use. This is useful if the script is not executable
or if we're on Windows (which does not understand shebangs).
"""
try:
with open(script) as f:
first_line = f.readline().strip()
if first_line.startswith('#!'):
commands = first_line[2:].split('#')[0].strip().split()
if mesonlib.is_windows():
# Windows does not have UNIX paths so remove them,
# but don't remove Windows paths
if commands[0].startswith('/'):
commands[0] = commands[0].split('/')[-1]
if len(commands) > 0 and commands[0] == 'env':
commands = commands[1:]
# Windows does not ship python3.exe, but we know the path to it
if len(commands) > 0 and commands[0] == 'python3':
commands[0] = sys.executable
return commands + [script]
except Exception:
pass
return False
def _is_executable(self, path):
suffix = os.path.splitext(path)[-1].lower()[1:]
if mesonlib.is_windows():
if suffix in self.windows_exts:
return True
elif os.access(path, os.X_OK):
return not os.path.isdir(path)
return False
def _search_dir(self, name, search_dir):
if search_dir is None:
return False
trial = os.path.join(search_dir, name)
if os.path.exists(trial):
if self._is_executable(trial):
return [trial]
# Now getting desperate. Maybe it is a script file that is
# a) not chmodded executable, or
# b) we are on windows so they can't be directly executed.
return self._shebang_to_cmd(trial)
else:
if mesonlib.is_windows():
for ext in self.windows_exts:
trial_ext = '{}.{}'.format(trial, ext)
if os.path.exists(trial_ext):
return [trial_ext]
return False
def _search(self, name, search_dir):
'''
Search in the specified dir for the specified executable by name
and if not found search in PATH
'''
commands = self._search_dir(name, search_dir)
if commands:
return commands
# Do a standard search in PATH
command = shutil.which(name)
if not mesonlib.is_windows():
# On UNIX-like platforms, shutil.which() is enough to find
# all executables whether in PATH or with an absolute path
return [command]
# HERE BEGINS THE TERROR OF WINDOWS
if command:
# On Windows, even if the PATH search returned a full path, we can't be
# sure that it can be run directly if it's not a native executable.
# For instance, interpreted scripts sometimes need to be run explicitly
# with an interpreter if the file association is not done properly.
name_ext = os.path.splitext(command)[1]
if name_ext[1:].lower() in self.windows_exts:
# Good, it can be directly executed
return [command]
# Try to extract the interpreter from the shebang
commands = self._shebang_to_cmd(command)
if commands:
return commands
else:
# Maybe the name is an absolute path to a native Windows
# executable, but without the extension. This is technically wrong,
# but many people do it because it works in the MinGW shell.
if os.path.isabs(name):
for ext in self.windows_exts:
command = '{}.{}'.format(name, ext)
if os.path.exists(command):
return [command]
# On Windows, interpreted scripts must have an extension otherwise they
# cannot be found by a standard PATH search. So we do a custom search
# where we manually search for a script with a shebang in PATH.
search_dirs = os.environ.get('PATH', '').split(';')
for search_dir in search_dirs:
commands = self._search_dir(name, search_dir)
if commands:
return commands
return [None]
def found(self):
return self.command[0] is not None
def get_command(self):
return self.command[:]
def get_path(self):
if self.found():
# Assume that the last element is the full path to the script or
# binary being run
return self.command[-1]
return None
def get_name(self):
return self.name
class ExternalLibrary(Dependency):
# TODO: Add `language` support to all Dependency objects so that languages
# can be exposed for dependencies that support that (i.e., not pkg-config)
def __init__(self, name, link_args, language, silent=False):
super().__init__('external', {})
self.name = name
self.language = language
self.is_found = False
self.link_args = []
self.lang_args = []
if link_args:
self.is_found = True
if not isinstance(link_args, list):
link_args = [link_args]
self.lang_args = {language: link_args}
# We special-case Vala for now till the Dependency object gets
# proper support for exposing the language it was written in.
# Without this, vala-specific link args will end up in the C link
# args list if you link to a Vala library.
# This hack use to be in CompilerHolder.find_library().
if language != 'vala':
self.link_args = link_args
if not silent:
if self.is_found:
mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'))
else:
mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
def found(self):
return self.is_found
def get_name(self):
return self.name
def get_link_args(self):
return self.link_args
def get_lang_args(self, lang):
if lang in self.lang_args:
return self.lang_args[lang]
return []
class BoostDependency(Dependency):
# Some boost libraries have different names for
# their sources and libraries. This dict maps
# between the two.
name2lib = {'test': 'unit_test_framework'}
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'boost', kwargs)
self.name = 'boost'
self.environment = environment
self.libdir = ''
if 'native' in kwargs and environment.is_cross_build():
self.want_cross = not kwargs['native']
else:
self.want_cross = environment.is_cross_build()
try:
self.boost_root = os.environ['BOOST_ROOT']
if not os.path.isabs(self.boost_root):
raise DependencyException('BOOST_ROOT must be an absolute path.')
except KeyError:
self.boost_root = None
if self.boost_root is None:
if self.want_cross:
if 'BOOST_INCLUDEDIR' in os.environ:
self.incdir = os.environ['BOOST_INCLUDEDIR']
else:
raise DependencyException('BOOST_ROOT or BOOST_INCLUDEDIR is needed while cross-compiling')
if mesonlib.is_windows():
self.boost_root = self.detect_win_root()
self.incdir = self.boost_root
else:
if 'BOOST_INCLUDEDIR' in os.environ:
self.incdir = os.environ['BOOST_INCLUDEDIR']
else:
self.incdir = '/usr/include'
else:
self.incdir = os.path.join(self.boost_root, 'include')
self.boost_inc_subdir = os.path.join(self.incdir, 'boost')
mlog.debug('Boost library root dir is', self.boost_root)
self.src_modules = {}
self.lib_modules = {}
self.lib_modules_mt = {}
self.detect_version()
self.requested_modules = self.get_requested(kwargs)
module_str = ', '.join(self.requested_modules)
if self.version is not None:
self.detect_src_modules()
self.detect_lib_modules()
self.validate_requested()
if self.boost_root is not None:
info = self.version + ', ' + self.boost_root
else:
info = self.version
mlog.log('Dependency Boost (%s) found:' % module_str, mlog.green('YES'), info)
else:
mlog.log("Dependency Boost (%s) found:" % module_str, mlog.red('NO'))
if 'cpp' not in self.environment.coredata.compilers:
raise DependencyException('Tried to use Boost but a C++ compiler is not defined.')
self.cpp_compiler = self.environment.coredata.compilers['cpp']
def detect_win_root(self):
globtext = 'c:\\local\\boost_*'
files = glob.glob(globtext)
if len(files) > 0:
return files[0]
return 'C:\\'
def get_compile_args(self):
args = []
include_dir = ''
if self.boost_root is not None:
if mesonlib.is_windows():
include_dir = self.boost_root
else:
include_dir = os.path.join(self.boost_root, 'include')
else:
include_dir = self.incdir
# Use "-isystem" when including boost headers instead of "-I"
# to avoid compiler warnings/failures when "-Werror" is used
# Careful not to use "-isystem" on default include dirs as it
# breaks some of the headers for certain gcc versions
# For example, doing g++ -isystem /usr/include on a simple
# "int main()" source results in the error:
# "/usr/include/c++/6.3.1/cstdlib:75:25: fatal error: stdlib.h: No such file or directory"
# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129
# and http://stackoverflow.com/questions/37218953/isystem-on-a-system-include-directory-causes-errors
# for more details
# TODO: The correct solution would probably be to ask the
# compiler for it's default include paths (ie: "gcc -xc++ -E
# -v -") and avoid including those with -isystem
# For now, use -isystem for all includes except for some
# typical defaults (which don't need to be included at all
# since they are in the default include paths)
if include_dir != '/usr/include' and include_dir != '/usr/local/include':
args.append("".join(self.cpp_compiler.get_include_args(include_dir, True)))
return args
def get_requested(self, kwargs):
candidates = kwargs.get('modules', [])
if isinstance(candidates, str):
return [candidates]
for c in candidates:
if not isinstance(c, str):
raise DependencyException('Boost module argument is not a string.')
return candidates
def validate_requested(self):
for m in self.requested_modules:
if m not in self.src_modules:
raise DependencyException('Requested Boost module "%s" not found.' % m)
def found(self):
return self.version is not None
def get_version(self):
return self.version
def detect_version(self):
try:
ifile = open(os.path.join(self.boost_inc_subdir, 'version.hpp'))
except FileNotFoundError:
self.version = None
return
with ifile:
for line in ifile:
if line.startswith("#define") and 'BOOST_LIB_VERSION' in line:
ver = line.split()[-1]
ver = ver[1:-1]
self.version = ver.replace('_', '.')
return
self.version = None
def detect_src_modules(self):
for entry in os.listdir(self.boost_inc_subdir):
entry = os.path.join(self.boost_inc_subdir, entry)
if stat.S_ISDIR(os.stat(entry).st_mode):
self.src_modules[os.path.split(entry)[-1]] = True
def detect_lib_modules(self):
if mesonlib.is_windows():
return self.detect_lib_modules_win()
return self.detect_lib_modules_nix()
def detect_lib_modules_win(self):
arch = detect_cpu_family(self.environment.coredata.compilers)
# Guess the libdir
if arch == 'x86':
gl = 'lib32*'
elif arch == 'x86_64':
gl = 'lib64*'
else:
# Does anyone do Boost cross-compiling to other archs on Windows?
gl = None
# See if the libdir is valid
if gl:
libdir = glob.glob(os.path.join(self.boost_root, gl))
else:
libdir = []
# Can't find libdir, bail
if not libdir:
return
libdir = libdir[0]
self.libdir = libdir
globber = 'boost_*-gd-*.lib' # FIXME
for entry in glob.glob(os.path.join(libdir, globber)):
(_, fname) = os.path.split(entry)
base = fname.split('_', 1)[1]
modname = base.split('-', 1)[0]
self.lib_modules_mt[modname] = fname
def detect_lib_modules_nix(self):
if mesonlib.is_osx():
libsuffix = 'dylib'
else:
libsuffix = 'so'
globber = 'libboost_*.{}'.format(libsuffix)
if 'BOOST_LIBRARYDIR' in os.environ:
libdirs = [os.environ['BOOST_LIBRARYDIR']]
elif self.boost_root is None:
libdirs = mesonlib.get_library_dirs()
else:
libdirs = [os.path.join(self.boost_root, 'lib')]
for libdir in libdirs:
for entry in glob.glob(os.path.join(libdir, globber)):
lib = os.path.basename(entry)
name = lib.split('.')[0].split('_', 1)[-1]
# I'm not 100% sure what to do here. Some distros
# have modules such as thread only as -mt versions.
if entry.endswith('-mt.so'):
self.lib_modules_mt[name] = True
else:
self.lib_modules[name] = True
def get_win_link_args(self):
args = []
if self.boost_root:
args.append('-L' + self.libdir)
for module in self.requested_modules:
module = BoostDependency.name2lib.get(module, module)
if module in self.lib_modules_mt:
args.append(self.lib_modules_mt[module])
return args
def get_link_args(self):
if mesonlib.is_windows():
return self.get_win_link_args()
args = []
if self.boost_root:
args.append('-L' + os.path.join(self.boost_root, 'lib'))
elif 'BOOST_LIBRARYDIR' in os.environ:
args.append('-L' + os.environ['BOOST_LIBRARYDIR'])
for module in self.requested_modules:
module = BoostDependency.name2lib.get(module, module)
libname = 'boost_' + module
# The compiler's library detector is the most reliable so use that first.
default_detect = self.cpp_compiler.find_library(libname, self.environment, [])
if default_detect is not None:
if module == 'unit_testing_framework':
emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor')
else:
emon_args = None
args += default_detect
if emon_args is not None:
args += emon_args
elif module in self.lib_modules or module in self.lib_modules_mt:
linkcmd = '-l' + libname
args.append(linkcmd)
# FIXME a hack, but Boost's testing framework has a lot of
# different options and it's hard to determine what to do
# without feedback from actual users. Update this
# as we get more bug reports.
if module == 'unit_testing_framework':
args.append('-lboost_test_exec_monitor')
elif module + '-mt' in self.lib_modules_mt:
linkcmd = '-lboost_' + module + '-mt'
args.append(linkcmd)
if module == 'unit_testing_framework':
args.append('-lboost_test_exec_monitor-mt')
return args
def get_sources(self):
return []
def need_threads(self):
return 'thread' in self.requested_modules
class GTestDependency(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'gtest', kwargs)
self.main = kwargs.get('main', False)
self.name = 'gtest'
self.libname = 'libgtest.so'
self.libmain_name = 'libgtest_main.so'
self.include_dir = '/usr/include'
self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src']
self.detect()
def found(self):
return self.is_found
def detect(self):
trial_dirs = mesonlib.get_library_dirs()
glib_found = False
gmain_found = False
for d in trial_dirs:
if os.path.isfile(os.path.join(d, self.libname)):
glib_found = True
if os.path.isfile(os.path.join(d, self.libmain_name)):
gmain_found = True
if glib_found and gmain_found:
self.is_found = True
self.compile_args = []
self.link_args = ['-lgtest']
if self.main:
self.link_args.append('-lgtest_main')
self.sources = []
mlog.log('Dependency GTest found:', mlog.green('YES'), '(prebuilt)')
elif self.detect_srcdir():
self.is_found = True
self.compile_args = ['-I' + self.src_include_dir]
self.link_args = []
if self.main:
self.sources = [self.all_src, self.main_src]
else:
self.sources = [self.all_src]
mlog.log('Dependency GTest found:', mlog.green('YES'), '(building self)')
else:
mlog.log('Dependency GTest found:', mlog.red('NO'))
self.is_found = False
return self.is_found
def detect_srcdir(self):
for s in self.src_dirs:
if os.path.exists(s):
self.src_dir = s
self.all_src = mesonlib.File.from_absolute_file(
os.path.join(self.src_dir, 'gtest-all.cc'))
self.main_src = mesonlib.File.from_absolute_file(
os.path.join(self.src_dir, 'gtest_main.cc'))
self.src_include_dir = os.path.normpath(os.path.join(self.src_dir, '..'))
return True
return False
def get_compile_args(self):
arr = []
if self.include_dir != '/usr/include':
arr.append('-I' + self.include_dir)
if hasattr(self, 'src_include_dir'):
arr.append('-I' + self.src_include_dir)
return arr
def get_link_args(self):
return self.link_args
def get_version(self):
return '1.something_maybe'
def get_sources(self):
return self.sources
def need_threads(self):
return True
class GMockDependency(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'gmock', kwargs)
# GMock may be a library or just source.
# Work with both.
self.name = 'gmock'
self.libname = 'libgmock.so'
trial_dirs = mesonlib.get_library_dirs()
gmock_found = False
for d in trial_dirs:
if os.path.isfile(os.path.join(d, self.libname)):
gmock_found = True
if gmock_found:
self.is_found = True
self.compile_args = []
self.link_args = ['-lgmock']
self.sources = []
mlog.log('Dependency GMock found:', mlog.green('YES'), '(prebuilt)')
return
for d in ['/usr/src/googletest/googlemock/src', '/usr/src/gmock/src', '/usr/src/gmock']:
if os.path.exists(d):
self.is_found = True
# Yes, we need both because there are multiple
# versions of gmock that do different things.
d2 = os.path.normpath(os.path.join(d, '..'))
self.compile_args = ['-I' + d, '-I' + d2]
self.link_args = []
all_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock-all.cc'))
main_src = mesonlib.File.from_absolute_file(os.path.join(d, 'gmock_main.cc'))
if kwargs.get('main', False):
self.sources = [all_src, main_src]
else:
self.sources = [all_src]
mlog.log('Dependency GMock found:', mlog.green('YES'), '(building self)')
return
mlog.log('Dependency GMock found:', mlog.red('NO'))
self.is_found = False
def get_version(self):
return '1.something_maybe'
def get_compile_args(self):
return self.compile_args
def get_sources(self):
return self.sources
def get_link_args(self):
return self.link_args
def found(self):
return self.is_found
class QtBaseDependency(Dependency):
def __init__(self, name, env, kwargs):
Dependency.__init__(self, name, kwargs)
self.name = name
self.qtname = name.capitalize()
self.qtver = name[-1]
if self.qtver == "4":
self.qtpkgname = 'Qt'
else:
self.qtpkgname = self.qtname
self.root = '/usr'
self.bindir = None
self.silent = kwargs.get('silent', False)
# We store the value of required here instead of passing it on to
# PkgConfigDependency etc because we want to try the qmake-based
# fallback as well.
self.required = kwargs.pop('required', True)
kwargs['required'] = False
mods = kwargs.get('modules', [])
self.cargs = []
self.largs = []
self.is_found = False
if isinstance(mods, str):
mods = [mods]
if not mods:
raise DependencyException('No ' + self.qtname + ' modules specified.')
type_text = 'cross' if env.is_cross_build() else 'native'
found_msg = '{} {} {{}} dependency (modules: {}) found:' \
''.format(self.qtname, type_text, ', '.join(mods))
from_text = 'pkg-config'
# Keep track of the detection methods used, for logging purposes.
methods = []
# Prefer pkg-config, then fallback to `qmake -query`
if DependencyMethods.PKGCONFIG in self.methods:
self._pkgconfig_detect(mods, env, kwargs)
methods.append('pkgconfig')
if not self.is_found and DependencyMethods.QMAKE in self.methods:
from_text = self._qmake_detect(mods, env, kwargs)
methods.append('qmake-' + self.name)
methods.append('qmake')
if not self.is_found:
# Reset compile args and link args
self.cargs = []
self.largs = []
from_text = '(checked {})'.format(mlog.format_list(methods))
self.version = 'none'
if self.required:
err_msg = '{} {} dependency not found {}' \
''.format(self.qtname, type_text, from_text)
raise DependencyException(err_msg)
if not self.silent:
mlog.log(found_msg.format(from_text), mlog.red('NO'))
return
from_text = '`{}`'.format(from_text)
if not self.silent:
mlog.log(found_msg.format(from_text), mlog.green('YES'))
def compilers_detect(self):
"Detect Qt (4 or 5) moc, uic, rcc in the specified bindir or in PATH"
if self.bindir:
moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True)
uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True)
rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True)
else:
# We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they
# are sometimes older, or newer versions.
moc = ExternalProgram('moc-' + self.name, silent=True)
uic = ExternalProgram('uic-' + self.name, silent=True)
rcc = ExternalProgram('rcc-' + self.name, silent=True)
return moc, uic, rcc
def _pkgconfig_detect(self, mods, env, kwargs):
modules = OrderedDict()
for module in mods:
modules[module] = PkgConfigDependency(self.qtpkgname + module, env, kwargs)
self.is_found = True
for m in modules.values():
if not m.found():
self.is_found = False
return
self.cargs += m.get_compile_args()
self.largs += m.get_link_args()
self.version = m.modversion
# Try to detect moc, uic, rcc
if 'Core' in modules:
core = modules['Core']
else:
corekwargs = {'required': 'false', 'silent': 'true'}
core = PkgConfigDependency(self.qtpkgname + 'Core', env, corekwargs)
# Used by self.compilers_detect()
self.bindir = self.get_pkgconfig_host_bins(core)
if not self.bindir:
# If exec_prefix is not defined, the pkg-config file is broken
prefix = core.get_pkgconfig_variable('exec_prefix')
if prefix:
self.bindir = os.path.join(prefix, 'bin')
def _find_qmake(self, qmake, env):
# Even when cross-compiling, if we don't get a cross-info qmake, we
# fallback to using the qmake in PATH because that's what we used to do
if env.is_cross_build():
qmake = env.cross_info.config['binaries'].get('qmake', qmake)
return ExternalProgram(qmake, silent=True)
def _qmake_detect(self, mods, env, kwargs):
for qmake in ('qmake-' + self.name, 'qmake'):
self.qmake = self._find_qmake(qmake, env)
if not self.qmake.found():
continue
# Check that the qmake is for qt5
pc, stdo = Popen_safe(self.qmake.get_command() + ['-v'])[0:2]
if pc.returncode != 0:
continue
if not 'Qt version ' + self.qtver in stdo:
mlog.log('QMake is not for ' + self.qtname)
continue
# Found qmake for Qt5!
break
else:
# Didn't find qmake :(
return
self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0)
# Query library path, header path, and binary path
mlog.log("Found qmake:", mlog.bold(self.qmake.get_name()), '(%s)' % self.version)
stdo = Popen_safe(self.qmake.get_command() + ['-query'])[1]
qvars = {}
for line in stdo.split('\n'):
line = line.strip()
if line == '':
continue
(k, v) = tuple(line.split(':', 1))
qvars[k] = v
if mesonlib.is_osx():
return self._framework_detect(qvars, mods, kwargs)
incdir = qvars['QT_INSTALL_HEADERS']
self.cargs.append('-I' + incdir)
libdir = qvars['QT_INSTALL_LIBS']
# Used by self.compilers_detect()
self.bindir = self.get_qmake_host_bins(qvars)
self.is_found = True
for module in mods:
mincdir = os.path.join(incdir, 'Qt' + module)
self.cargs.append('-I' + mincdir)
if for_windows(env.is_cross_build(), env):
libfile = os.path.join(libdir, self.qtpkgname + module + '.lib')
if not os.path.isfile(libfile):
# MinGW can link directly to .dll
libfile = os.path.join(self.bindir, self.qtpkgname + module + '.dll')
if not os.path.isfile(libfile):
self.is_found = False
break
else:
libfile = os.path.join(libdir, 'lib{}{}.so'.format(self.qtpkgname, module))
if not os.path.isfile(libfile):
self.is_found = False
break
self.largs.append(libfile)
return qmake
def _framework_detect(self, qvars, modules, kwargs):
libdir = qvars['QT_INSTALL_LIBS']
for m in modules:
fname = 'Qt' + m
fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir, kwargs)
self.cargs.append('-F' + libdir)
if fwdep.found():
self.is_found = True
self.cargs += fwdep.get_compile_args()
self.largs += fwdep.get_link_args()
# Used by self.compilers_detect()
self.bindir = self.get_qmake_host_bins(qvars)
def get_qmake_host_bins(self, qvars):
# Prefer QT_HOST_BINS (qt5, correct for cross and native compiling)
# but fall back to QT_INSTALL_BINS (qt4)
if 'QT_HOST_BINS' in qvars:
return qvars['QT_HOST_BINS']
else:
return qvars['QT_INSTALL_BINS']
def get_version(self):
return self.version
def get_compile_args(self):
return self.cargs
def get_sources(self):
return []
def get_link_args(self):
return self.largs
def get_methods(self):
return [DependencyMethods.PKGCONFIG, DependencyMethods.QMAKE]
def found(self):
return self.is_found
def get_exe_args(self, compiler):
# Originally this was -fPIE but nowadays the default
# for upstream and distros seems to be -reduce-relocations
# which requires -fPIC. This may cause a performance
# penalty when using self-built Qt or on platforms
# where -fPIC is not required. If this is an issue
# for you, patches are welcome.
return compiler.get_pic_args()
class Qt5Dependency(QtBaseDependency):
def __init__(self, env, kwargs):
QtBaseDependency.__init__(self, 'qt5', env, kwargs)
def get_pkgconfig_host_bins(self, core):
return core.get_pkgconfig_variable('host_bins')
class Qt4Dependency(QtBaseDependency):
def __init__(self, env, kwargs):
QtBaseDependency.__init__(self, 'qt4', env, kwargs)
def get_pkgconfig_host_bins(self, core):
# Only return one bins dir, because the tools are generally all in one
# directory for Qt4, in Qt5, they must all be in one directory. Return
# the first one found among the bin variables, in case one tool is not
# configured to be built.
applications = ['moc', 'uic', 'rcc', 'lupdate', 'lrelease']
for application in applications:
try:
return os.path.dirname(core.get_pkgconfig_variable('%s_location' % application))
except MesonException:
pass
class GnuStepDependency(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'gnustep', kwargs)
self.required = kwargs.get('required', True)
self.modules = kwargs.get('modules', [])
self.detect()
def detect(self):
self.confprog = 'gnustep-config'
try:
gp = Popen_safe([self.confprog, '--help'])[0]
except (FileNotFoundError, PermissionError):
self.args = None
mlog.log('Dependency GnuStep found:', mlog.red('NO'), '(no gnustep-config)')
return
if gp.returncode != 0:
self.args = None
mlog.log('Dependency GnuStep found:', mlog.red('NO'))
return
if 'gui' in self.modules:
arg = '--gui-libs'
else:
arg = '--base-libs'
fp, flagtxt, flagerr = Popen_safe([self.confprog, '--objc-flags'])
if fp.returncode != 0:
raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr))
args = flagtxt.split()
self.args = self.filter_arsg(args)
fp, libtxt, liberr = Popen_safe([self.confprog, arg])
if fp.returncode != 0:
raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr))
self.libs = self.weird_filter(libtxt.split())
self.version = self.detect_version()
mlog.log('Dependency', mlog.bold('GnuStep'), 'found:',
mlog.green('YES'), self.version)
def weird_filter(self, elems):
"""When building packages, the output of the enclosing Make
is sometimes mixed among the subprocess output. I have no idea
why. As a hack filter out everything that is not a flag."""
return [e for e in elems if e.startswith('-')]
def filter_arsg(self, args):
"""gnustep-config returns a bunch of garbage args such
as -O2 and so on. Drop everything that is not needed."""
result = []
for f in args:
if f.startswith('-D') \
or f.startswith('-f') \
or f.startswith('-I') \
or f == '-pthread' \
or (f.startswith('-W') and not f == '-Wall'):
result.append(f)
return result
def detect_version(self):
gmake = self.get_variable('GNUMAKE')
makefile_dir = self.get_variable('GNUSTEP_MAKEFILES')
# This Makefile has the GNUStep version set
base_make = os.path.join(makefile_dir, 'Additional', 'base.make')
# Print the Makefile variable passed as the argument. For instance, if
# you run the make target `print-SOME_VARIABLE`, this will print the
# value of the variable `SOME_VARIABLE`.
printver = "print-%:\n\t@echo '$($*)'"
env = os.environ.copy()
# See base.make to understand why this is set
env['FOUNDATION_LIB'] = 'gnu'
p, o, e = Popen_safe([gmake, '-f', '-', '-f', base_make,
'print-GNUSTEP_BASE_VERSION'],
env=env, write=printver, stdin=subprocess.PIPE)
version = o.strip()
if not version:
mlog.debug("Couldn't detect GNUStep version, falling back to '1'")
# Fallback to setting some 1.x version
version = '1'
return version
def get_variable(self, var):
p, o, e = Popen_safe([self.confprog, '--variable=' + var])
if p.returncode != 0 and self.required:
raise DependencyException('{!r} for variable {!r} failed to run'
''.format(self.confprog, var))
return o.strip()
def found(self):
return self.args is not None
def get_version(self):
return self.version
def get_compile_args(self):
if self.args is None:
return []
return self.args
def get_link_args(self):
return self.libs
class AppleFrameworks(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'appleframeworks', kwargs)
modules = kwargs.get('modules', [])
if isinstance(modules, str):
modules = [modules]
if not modules:
raise DependencyException("AppleFrameworks dependency requires at least one module.")
self.frameworks = modules
def get_link_args(self):
args = []
for f in self.frameworks:
args.append('-framework')
args.append(f)
return args
def found(self):
return mesonlib.is_osx()
def get_version(self):
return 'unknown'
class GLDependency(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'gl', kwargs)
self.is_found = False
self.cargs = []
self.linkargs = []
if DependencyMethods.PKGCONFIG in self.methods:
try:
pcdep = PkgConfigDependency('gl', environment, kwargs)
if pcdep.found():
self.type_name = 'pkgconfig'
self.is_found = True
self.cargs = pcdep.get_compile_args()
self.linkargs = pcdep.get_link_args()
self.version = pcdep.get_version()
return
except Exception:
pass
if DependencyMethods.SYSTEM in self.methods:
if mesonlib.is_osx():
self.is_found = True
self.linkargs = ['-framework', 'OpenGL']
self.version = '1' # FIXME
return
if mesonlib.is_windows():
self.is_found = True
self.linkargs = ['-lopengl32']
self.version = '1' # FIXME: unfixable?
return
def get_link_args(self):
return self.linkargs
def get_version(self):
return self.version
def get_methods(self):
if mesonlib.is_osx() or mesonlib.is_windows():
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM]
else:
return [DependencyMethods.PKGCONFIG]
# There are three different ways of depending on SDL2:
# sdl2-config, pkg-config and OSX framework
class SDL2Dependency(Dependency):
def __init__(self, environment, kwargs):
Dependency.__init__(self, 'sdl2', kwargs)
self.is_found = False
self.cargs = []
self.linkargs = []
if DependencyMethods.PKGCONFIG in self.methods:
try:
pcdep = PkgConfigDependency('sdl2', environment, kwargs)
if pcdep.found():
self.type_name = 'pkgconfig'
self.is_found = True
self.cargs = pcdep.get_compile_args()
self.linkargs = pcdep.get_link_args()
self.version = pcdep.get_version()
return
except Exception as e:
mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e))
pass
if DependencyMethods.SDLCONFIG in self.methods:
sdlconf = shutil.which('sdl2-config')
if sdlconf:
stdo = Popen_safe(['sdl2-config', '--cflags'])[1]
self.cargs = stdo.strip().split()
stdo = Popen_safe(['sdl2-config', '--libs'])[1]
self.linkargs = stdo.strip().split()
stdo = Popen_safe(['sdl2-config', '--version'])[1]
self.version = stdo.strip()
self.is_found = True
mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'),
self.version, '(%s)' % sdlconf)
return
mlog.debug('Could not find sdl2-config binary, trying next.')
if DependencyMethods.EXTRAFRAMEWORK in self.methods:
if mesonlib.is_osx():
fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True), None, kwargs)
if fwdep.found():
self.is_found = True
self.cargs = fwdep.get_compile_args()
self.linkargs = fwdep.get_link_args()
self.version = '2' # FIXME
return
mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO'))
def get_compile_args(self):
return self.cargs
def get_link_args(self):
return self.linkargs
def found(self):
return self.is_found
def get_version(self):
return self.version
def get_methods(self):
if mesonlib.is_osx():
return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG, DependencyMethods.EXTRAFRAMEWORK]
else:
return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG]
class ExtraFrameworkDependency(Dependency):
def __init__(self, name, required, path, kwargs):
Dependency.__init__(self, 'extraframeworks', kwargs)
self.name = None
self.detect(name, path)
if self.found():
mlog.log('Dependency', mlog.bold(name), 'found:', mlog.green('YES'),
os.path.join(self.path, self.name))
else:
mlog.log('Dependency', name, 'found:', mlog.red('NO'))
def detect(self, name, path):
lname = name.lower()
if path is None:
paths = ['/Library/Frameworks']
else:
paths = [path]
for p in paths:
for d in os.listdir(p):
fullpath = os.path.join(p, d)
if lname != d.split('.')[0].lower():
continue
if not stat.S_ISDIR(os.stat(fullpath).st_mode):
continue
self.path = p
self.name = d
return
def get_compile_args(self):
if self.found():
return ['-I' + os.path.join(self.path, self.name, 'Headers')]
return []
def get_link_args(self):
if self.found():
return ['-F' + self.path, '-framework', self.name.split('.')[0]]
return []
def found(self):
return self.name is not None
def get_version(self):
return 'unknown'
class ThreadDependency(Dependency):
def __init__(self, environment, kwargs):
super().__init__('threads', {})
self.name = 'threads'
self.is_found = True
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES'))
def need_threads(self):
return True
def get_version(self):
return 'unknown'
class Python3Dependency(Dependency):
def __init__(self, environment, kwargs):
super().__init__('python3', kwargs)
self.name = 'python3'
self.is_found = False
# We can only be sure that it is Python 3 at this point
self.version = '3'
if DependencyMethods.PKGCONFIG in self.methods:
try:
pkgdep = PkgConfigDependency('python3', environment, kwargs)
if pkgdep.found():
self.cargs = pkgdep.cargs
self.libs = pkgdep.libs
self.version = pkgdep.get_version()
self.is_found = True
return
except Exception:
pass
if not self.is_found:
if mesonlib.is_windows() and DependencyMethods.SYSCONFIG in self.methods:
self._find_libpy3_windows(environment)
elif mesonlib.is_osx() and DependencyMethods.EXTRAFRAMEWORK in self.methods:
# In OSX the Python 3 framework does not have a version
# number in its name.
fw = ExtraFrameworkDependency('python', False, None, kwargs)
if fw.found():
self.cargs = fw.get_compile_args()
self.libs = fw.get_link_args()
self.is_found = True
if self.is_found:
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES'))
else:
mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO'))
def _find_libpy3_windows(self, env):
'''
Find python3 libraries on Windows and also verify that the arch matches
what we are building for.
'''
pyarch = sysconfig.get_platform()
arch = detect_cpu_family(env.coredata.compilers)
if arch == 'x86':
arch = '32'
elif arch == 'x86_64':
arch = '64'
else:
# We can't cross-compile Python 3 dependencies on Windows yet
mlog.log('Unknown architecture {!r} for'.format(arch),
mlog.bold(self.name))
self.is_found = False
return
# Pyarch ends in '32' or '64'
if arch != pyarch[-2:]:
mlog.log('Need', mlog.bold(self.name),
'for {}-bit, but found {}-bit'.format(arch, pyarch[-2:]))
self.is_found = False
return
inc = sysconfig.get_path('include')
platinc = sysconfig.get_path('platinclude')
self.cargs = ['-I' + inc]
if inc != platinc:
self.cargs.append('-I' + platinc)
# Nothing exposes this directly that I coulf find
basedir = sysconfig.get_config_var('base')
vernum = sysconfig.get_config_var('py_version_nodot')
self.libs = ['-L{}/libs'.format(basedir),
'-lpython{}'.format(vernum)]
self.version = sysconfig.get_config_var('py_version_short')
self.is_found = True
def get_compile_args(self):
return self.cargs
def get_link_args(self):
return self.libs
def get_methods(self):
if mesonlib.is_windows():
return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSCONFIG]
elif mesonlib.is_osx():
return [DependencyMethods.PKGCONFIG, DependencyMethods.EXTRAFRAMEWORK]
else:
return [DependencyMethods.PKGCONFIG]
def get_version(self):
return self.version
class ValgrindDependency(PkgConfigDependency):
def __init__(self, environment, kwargs):
PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs)
def get_link_args(self):
return []
class LLVMDependency(Dependency):
"""LLVM dependency.
LLVM uses a special tool, llvm-config, which has arguments for getting
c args, cxx args, and ldargs as well as version.
"""
# Ordered list of llvm-config binaries to try. Start with base, then try
# newest back to oldest (3.5 is abitrary), and finally the devel version.
llvm_config_bins = [
'llvm-config', 'llvm-config-4.0', 'llvm-config-3.9', 'llvm-config39',
'llvm-config-3.8', 'llvm-config38', 'llvm-config-3.7', 'llvm-config37',
'llvm-config-3.6', 'llvm-config36', 'llvm-config-3.5', 'llvm-config35',
'llvm-config-devel',
]
llvmconfig = None
_llvmconfig_found = False
__best_found = None
def __init__(self, environment, kwargs):
super().__init__('llvm-config', kwargs)
# It's necessary for LLVM <= 3.8 to use the C++ linker. For 3.9 and 4.0
# the C linker works fine if only using the C API.
self.language = 'cpp'
self.cargs = []
self.libs = []
self.modules = []
required = kwargs.get('required', True)
req_version = kwargs.get('version', None)
if self.llvmconfig is None:
self.check_llvmconfig(req_version)
if not self._llvmconfig_found:
if self.__best_found is not None:
mlog.log('found {!r} but need:'.format(self.version),
req_version)
else:
mlog.log("No llvm-config found; can't detect dependency")
mlog.log('Dependency LLVM found:', mlog.red('NO'))
if required:
raise DependencyException('Dependency LLVM not found')
return
p, out, err = Popen_safe([self.llvmconfig, '--version'])
if p.returncode != 0:
mlog.debug('stdout: {}\nstderr: {}'.format(out, err))
if required:
raise DependencyException('Dependency LLVM not found')
return
else:
self.version = out.strip()
mlog.log('Dependency LLVM found:', mlog.green('YES'))
self.is_found = True
p, out = Popen_safe(
[self.llvmconfig, '--libs', '--ldflags', '--system-libs'])[:2]
if p.returncode != 0:
raise DependencyException('Could not generate libs for LLVM.')
self.libs = shlex.split(out)
p, out = Popen_safe([self.llvmconfig, '--cppflags'])[:2]
if p.returncode != 0:
raise DependencyException('Could not generate includedir for LLVM.')
self.cargs = shlex.split(out)
p, out = Popen_safe([self.llvmconfig, '--components'])[:2]
if p.returncode != 0:
raise DependencyException('Could not generate modules for LLVM.')
self.modules = shlex.split(out)
modules = mesonlib.stringlistify(kwargs.get('modules', []))
for mod in modules:
if mod not in self.modules:
mlog.log('LLVM module', mod, 'found:', mlog.red('NO'))
self.is_found = False
if required:
raise DependencyException(
'Could not find required LLVM Component: {}'.format(mod))
else:
mlog.log('LLVM module', mod, 'found:', mlog.green('YES'))
def get_version(self):
return self.version
def get_compile_args(self):
return self.cargs
def get_link_args(self):
return self.libs
@classmethod
def check_llvmconfig(cls, version_req):
"""Try to find the highest version of llvm-config."""
for llvmconfig in cls.llvm_config_bins:
try:
p, out = Popen_safe([llvmconfig, '--version'])[0:2]
out = out.strip()
if p.returncode != 0:
continue
if version_req:
if version_compare(out, version_req, strict=True):
if cls.__best_found and version_compare(out, '<={}'.format(cls.__best_found), strict=True):
continue
cls.__best_found = out
cls.llvmconfig = llvmconfig
else:
# If no specific version is requested use the first version
# found, since that should be the best.
cls.__best_found = out
cls.llvmconfig = llvmconfig
break
except (FileNotFoundError, PermissionError):
pass
if cls.__best_found:
mlog.log('Found llvm-config:',
mlog.bold(shutil.which(cls.llvmconfig)),
'({})'.format(out.strip()))
cls._llvmconfig_found = True
else:
cls.llvmconfig = False
def need_threads(self):
return True
def get_dep_identifier(name, kwargs, want_cross):
# Need immutable objects since the identifier will be used as a dict key
version_reqs = flatten(kwargs.get('version', []))
if isinstance(version_reqs, list):
version_reqs = frozenset(version_reqs)
identifier = (name, version_reqs, want_cross)
for key, value in kwargs.items():
# 'version' is embedded above as the second element for easy access
# 'native' is handled above with `want_cross`
# 'required' is irrelevant for caching; the caller handles it separately
# 'fallback' subprojects cannot be cached -- they must be initialized
if key in ('version', 'native', 'required', 'fallback',):
continue
# All keyword arguments are strings, ints, or lists (or lists of lists)
if isinstance(value, list):
value = frozenset(flatten(value))
identifier += (key, value)
return identifier
def find_external_dependency(name, environment, kwargs):
required = kwargs.get('required', True)
if not isinstance(required, bool):
raise DependencyException('Keyword "required" must be a boolean.')
if not isinstance(kwargs.get('method', ''), str):
raise DependencyException('Keyword "method" must be a string.')
lname = name.lower()
if lname in packages:
dep = packages[lname](environment, kwargs)
if required and not dep.found():
raise DependencyException('Dependency "%s" not found' % name)
return dep
pkg_exc = None
pkgdep = None
try:
pkgdep = PkgConfigDependency(name, environment, kwargs)
if pkgdep.found():
return pkgdep
except Exception as e:
pkg_exc = e
if mesonlib.is_osx():
fwdep = ExtraFrameworkDependency(name, required, None, kwargs)
if required and not fwdep.found():
m = 'Dependency {!r} not found, tried Extra Frameworks ' \
'and Pkg-Config:\n\n' + str(pkg_exc)
raise DependencyException(m.format(name))
return fwdep
if pkg_exc is not None:
raise pkg_exc
mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO'))
return pkgdep
# This has to be at the end so the classes it references
# are defined.
packages = {'boost': BoostDependency,
'gtest': GTestDependency,
'gmock': GMockDependency,
'qt5': Qt5Dependency,
'qt4': Qt4Dependency,
'gnustep': GnuStepDependency,
'appleframeworks': AppleFrameworks,
'wxwidgets': WxDependency,
'sdl2': SDL2Dependency,
'gl': GLDependency,
'threads': ThreadDependency,
'python3': Python3Dependency,
'valgrind': ValgrindDependency,
'llvm': LLVMDependency,
}