aboutsummaryrefslogtreecommitdiff
path: root/meson/dependencies.py
diff options
context:
space:
mode:
Diffstat (limited to 'meson/dependencies.py')
-rw-r--r--meson/dependencies.py1120
1 files changed, 1120 insertions, 0 deletions
diff --git a/meson/dependencies.py b/meson/dependencies.py
new file mode 100644
index 0000000..974559f
--- /dev/null
+++ b/meson/dependencies.py
@@ -0,0 +1,1120 @@
+# Copyright 2013-2015 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 os, stat, glob, subprocess, shutil
+from . coredata import MesonException
+from . import mlog
+from . import mesonlib
+
+class DependencyException(MesonException):
+ def __init__(self, *args, **kwargs):
+ MesonException.__init__(self, *args, **kwargs)
+
+class Dependency():
+ def __init__(self):
+ self.name = "null"
+ self.is_found = False
+
+ 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_name(self):
+ return self.name
+
+ def get_exe_args(self):
+ return []
+
+ def need_threads(self):
+ return False
+
+class InternalDependency():
+ def __init__(self, incdirs, libraries, sources, ext_deps):
+ super().__init__()
+ self.include_directories = incdirs
+ self.libraries = libraries
+ self.sources = sources
+ self.ext_deps = ext_deps
+
+class PkgConfigDependency(Dependency):
+ pkgconfig_found = None
+
+ def __init__(self, name, environment, kwargs):
+ Dependency.__init__(self)
+ self.is_libtool = False
+ self.required = kwargs.get('required', True)
+ if 'native' in kwargs and environment.is_cross_build():
+ want_cross = not kwargs['native']
+ else:
+ want_cross = environment.is_cross_build()
+ self.name = name
+ if PkgConfigDependency.pkgconfig_found is None:
+ self.check_pkgconfig()
+
+ self.is_found = False
+ if not PkgConfigDependency.pkgconfig_found:
+ if self.required:
+ raise DependencyException('Pkg-config not found.')
+ self.cargs = []
+ self.libs = []
+ return
+ if environment.is_cross_build() and want_cross:
+ if "pkgconfig" not in environment.cross_info.config["binaries"]:
+ raise DependencyException('Pkg-config binary missing from cross file.')
+ pkgbin = environment.cross_info.config["binaries"]['pkgconfig']
+ self.type_string = 'Cross'
+ else:
+ pkgbin = 'pkg-config'
+ self.type_string = 'Native'
+
+ mlog.debug('Determining dependency %s with pkg-config executable %s.' % (name, pkgbin))
+ self.pkgbin = pkgbin
+ p = subprocess.Popen([pkgbin, '--modversion', name],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ if self.required:
+ raise DependencyException('%s dependency %s not found.' % (self.type_string, name))
+ self.modversion = 'none'
+ self.cargs = []
+ self.libs = []
+ else:
+ self.modversion = out.decode().strip()
+ mlog.log('%s dependency' % self.type_string, mlog.bold(name), 'found:',
+ mlog.green('YES'), self.modversion)
+ self.version_requirement = kwargs.get('version', None)
+ if self.version_requirement is None:
+ self.is_found = True
+ else:
+ if not isinstance(self.version_requirement, str):
+ raise DependencyException('Version argument must be string.')
+ self.is_found = mesonlib.version_compare(self.modversion, self.version_requirement)
+ if not self.is_found and self.required:
+ raise DependencyException(
+ 'Invalid version of a dependency, needed %s %s found %s.' %
+ (name, self.version_requirement, self.modversion))
+ if not self.is_found:
+ return
+ p = subprocess.Popen([pkgbin, '--cflags', name], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ raise DependencyException('Could not generate cargs for %s:\n\n%s' % \
+ (name, out.decode(errors='ignore')))
+ self.cargs = out.decode().split()
+
+ p = subprocess.Popen([pkgbin, '--libs', name], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ raise DependencyException('Could not generate libs for %s:\n\n%s' % \
+ (name, out.decode(errors='ignore')))
+ self.libs = []
+ for lib in out.decode().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_variable(self, variable_name):
+ p = subprocess.Popen([self.pkgbin, '--variable=%s' % variable_name, self.name],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ if self.required:
+ raise DependencyException('%s dependency %s not found.' %
+ (self.type_string, self.name))
+ else:
+ variable = out.decode().strip()
+ mlog.debug('return of subprocess : %s' % variable)
+
+ return variable
+
+ def get_modversion(self):
+ return self.modversion
+
+ def get_compile_args(self):
+ return self.cargs
+
+ def get_link_args(self):
+ return self.libs
+
+ def check_pkgconfig(self):
+ try:
+ p = subprocess.Popen(['pkg-config', '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode == 0:
+ mlog.log('Found pkg-config:', mlog.bold(shutil.which('pkg-config')),
+ '(%s)' % out.decode().strip())
+ PkgConfigDependency.pkgconfig_found = True
+ return
+ except Exception:
+ pass
+ PkgConfigDependency.pkgconfig_found = False
+ mlog.log('Found Pkg-config:', mlog.red('NO'))
+
+ def found(self):
+ return self.is_found
+
+ def extract_field(self, la_file, fieldname):
+ for line in open(la_file):
+ 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)
+ if WxDependency.wx_found is None:
+ self.check_wxconfig()
+
+ if not WxDependency.wx_found:
+ raise DependencyException('Wx-config not found.')
+ self.is_found = False
+ p = subprocess.Popen([self.wxc, '--version'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ mlog.log('Dependency wxwidgets found:', mlog.red('NO'))
+ self.cargs = []
+ self.libs = []
+ else:
+ self.modversion = out.decode().strip()
+ version_req = kwargs.get('version', None)
+ if version_req is not None:
+ if not mesonlib.version_compare(self.modversion, version_req):
+ 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 = subprocess.Popen([self.wxc, '--cxxflags'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ raise DependencyException('Could not generate cargs for wxwidgets.')
+ self.cargs = out.decode().split()
+
+ p = subprocess.Popen([self.wxc, '--libs'] + self.requested_modules,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode != 0:
+ raise DependencyException('Could not generate libs for wxwidgets.')
+ self.libs = out.decode().split()
+
+ def get_requested(self, kwargs):
+ modules = 'modules'
+ if not modules 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_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 = subprocess.Popen([wxc, '--version'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out = p.communicate()[0]
+ if p.returncode == 0:
+ mlog.log('Found wx-config:', mlog.bold(shutil.which(wxc)),
+ '(%s)' % out.decode().strip())
+ self.wxc = wxc
+ WxDependency.wx_found = True
+ return
+ except Exception:
+ pass
+ WxDependency.wxconfig_found = False
+ mlog.log('Found wx-config:', mlog.red('NO'))
+
+ def found(self):
+ return self.is_found
+
+class ExternalProgram():
+ def __init__(self, name, fullpath=None, silent=False, search_dir=None):
+ self.name = name
+ self.fullpath = None
+ if fullpath is not None:
+ if not isinstance(fullpath, list):
+ self.fullpath = [fullpath]
+ else:
+ self.fullpath = fullpath
+ else:
+ self.fullpath = [shutil.which(name)]
+ if self.fullpath[0] is None and search_dir is not None:
+ trial = os.path.join(search_dir, name)
+ suffix = os.path.splitext(trial)[-1].lower()[1:]
+ if mesonlib.is_windows() and (suffix == 'exe' or suffix == 'com'\
+ or suffix == 'bat'):
+ self.fullpath = [trial]
+ elif not mesonlib.is_windows() and os.access(trial, os.X_OK):
+ self.fullpath = [trial]
+ else:
+ # 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.
+ try:
+ first_line = open(trial).readline().strip()
+ if first_line.startswith('#!'):
+ commands = first_line[2:].split('#')[0].strip().split()
+ if mesonlib.is_windows():
+ # Windows does not have /usr/bin.
+ commands[0] = commands[0].split('/')[-1]
+ if commands[0] == 'env':
+ commands = commands[1:]
+ self.fullpath = commands + [trial]
+ except Exception:
+ pass
+ if not silent:
+ if self.found():
+ mlog.log('Program', mlog.bold(name), 'found:', mlog.green('YES'),
+ '(%s)' % ' '.join(self.fullpath))
+ else:
+ mlog.log('Program', mlog.bold(name), 'found:', mlog.red('NO'))
+
+ def found(self):
+ return self.fullpath[0] is not None
+
+ def get_command(self):
+ return self.fullpath
+
+ def get_name(self):
+ return self.name
+
+class ExternalLibrary(Dependency):
+ def __init__(self, name, fullpath=None, silent=False):
+ super().__init__()
+ self.name = name
+ self.fullpath = fullpath
+ if not silent:
+ if self.found():
+ mlog.log('Library', mlog.bold(name), 'found:', mlog.green('YES'),
+ '(%s)' % self.fullpath)
+ else:
+ mlog.log('Library', mlog.bold(name), 'found:', mlog.red('NO'))
+
+ def found(self):
+ return self.fullpath is not None
+
+ def get_link_args(self):
+ if self.found():
+ return [self.fullpath]
+ 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)
+ self.name = 'boost'
+ self.libdir = ''
+ 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 mesonlib.is_windows():
+ self.boost_root = self.detect_win_root()
+ self.incdir = self.boost_root
+ 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'))
+
+ 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 = []
+ if self.boost_root is not None:
+ if mesonlib.is_windows():
+ args.append('-I' + self.boost_root)
+ else:
+ args.append('-I' + os.path.join(self.boost_root, 'include'))
+ else:
+ args.append('-I' + self.incdir)
+ 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
+ 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):
+ if mesonlib.is_32bit():
+ gl = 'lib32*'
+ else:
+ gl = 'lib64*'
+ libdir = glob.glob(os.path.join(self.boost_root, gl))
+ if len(libdir) == 0:
+ 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):
+ libsuffix = None
+ if mesonlib.is_osx():
+ libsuffix = 'dylib'
+ else:
+ libsuffix = 'so'
+
+ globber = 'libboost_*.{}'.format(libsuffix)
+ if 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'))
+ for module in self.requested_modules:
+ module = BoostDependency.name2lib.get(module, module)
+ if module in self.lib_modules or module in self.lib_modules_mt:
+ linkcmd = '-lboost_' + module
+ 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)
+ 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_include_dir = '/usr/src/gtest'
+ self.src_dir = '/usr/src/gtest/src'
+ 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.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 os.path.exists(self.src_dir):
+ 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 get_compile_args(self):
+ arr = []
+ if self.include_dir != '/usr/include':
+ arr.append('-I' + self.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 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/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.
+ self.compile_args = ['-I/usr/src/gmock', '-I/usr/src/gmock/src']
+ 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 Qt5Dependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self)
+ self.name = 'qt5'
+ self.root = '/usr'
+ mods = kwargs.get('modules', [])
+ self.cargs = []
+ self.largs = []
+ self.is_found = False
+ if isinstance(mods, str):
+ mods = [mods]
+ if len(mods) == 0:
+ raise DependencyException('No Qt5 modules specified.')
+ type_text = 'native'
+ if environment.is_cross_build() and kwargs.get('native', False):
+ type_text = 'cross'
+ self.pkgconfig_detect(mods, environment, kwargs)
+ elif not environment.is_cross_build() and shutil.which('pkg-config') is not None:
+ self.pkgconfig_detect(mods, environment, kwargs)
+ elif shutil.which('qmake') is not None:
+ self.qmake_detect(mods, kwargs)
+ else:
+ self.version = 'none'
+ if not self.is_found:
+ mlog.log('Qt5 %s dependency found: ' % type_text, mlog.red('NO'))
+ else:
+ mlog.log('Qt5 %s dependency found: ' % type_text, mlog.green('YES'))
+
+ def pkgconfig_detect(self, mods, environment, kwargs):
+ modules = []
+ for module in mods:
+ modules.append(PkgConfigDependency('Qt5' + module, environment, kwargs))
+ for m in modules:
+ self.cargs += m.get_compile_args()
+ self.largs += m.get_link_args()
+ self.is_found = True
+ self.version = modules[0].modversion
+
+ def qmake_detect(self, mods, kwargs):
+ pc = subprocess.Popen(['qmake', '-v'], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (stdo, _) = pc.communicate()
+ if pc.returncode != 0:
+ return
+ stdo = stdo.decode()
+ if not 'version 5' in stdo:
+ mlog.log('QMake is not for Qt5.')
+ return
+ self.version = re.search('5(\.\d+)+', stdo).group(0)
+ (stdo, _) = subprocess.Popen(['qmake', '-query'], stdout=subprocess.PIPE).communicate()
+ qvars = {}
+ for line in stdo.decode().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']
+ bindir = qvars['QT_INSTALL_BINS']
+ #self.largs.append('-L' + libdir)
+ for module in mods:
+ mincdir = os.path.join(incdir, 'Qt' + module)
+ self.cargs.append('-I' + mincdir)
+ libfile = os.path.join(libdir, 'Qt5' + module + '.lib')
+ if not os.path.isfile(libfile):
+ # MinGW links directly to .dll, not to .lib.
+ libfile = os.path.join(bindir, 'Qt5' + module + '.dll')
+ self.largs.append(libfile)
+ self.is_found = True
+
+ 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)
+ self.cargs.append('-F' + libdir)
+ if fwdep.found():
+ self.is_found = True
+ self.cargs += fwdep.get_compile_args()
+ self.largs += fwdep.get_link_args()
+
+
+ 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 found(self):
+ return self.is_found
+
+ def get_exe_args(self):
+ # 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.
+ # Fix this to be more portable, especially to MSVC.
+ return ['-fPIC']
+
+class Qt4Dependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self)
+ self.name = 'qt4'
+ self.root = '/usr'
+ self.modules = []
+ mods = kwargs.get('modules', [])
+ if isinstance(mods, str):
+ mods = [mods]
+ for module in mods:
+ self.modules.append(PkgConfigDependency('Qt' + module, environment, kwargs))
+ if len(self.modules) == 0:
+ raise DependencyException('No Qt4 modules specified.')
+
+ def get_version(self):
+ return self.modules[0].get_version()
+
+ def get_compile_args(self):
+ args = []
+ for m in self.modules:
+ args += m.get_compile_args()
+ return args
+
+ def get_sources(self):
+ return []
+
+ def get_link_args(self):
+ args = []
+ for module in self.modules:
+ args += module.get_link_args()
+ return args
+
+ def found(self):
+ for i in self.modules:
+ if not i.found():
+ return False
+ return True
+
+class GnuStepDependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self)
+ self.modules = kwargs.get('modules', [])
+ self.detect()
+
+ def detect(self):
+ confprog = 'gnustep-config'
+ try:
+ gp = subprocess.Popen([confprog, '--help'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ gp.communicate()
+ except FileNotFoundError:
+ 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 = subprocess.Popen([confprog, '--objc-flags'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (flagtxt, flagerr) = fp.communicate()
+ flagtxt = flagtxt.decode()
+ flagerr = flagerr.decode()
+ if fp.returncode != 0:
+ raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr))
+ args = flagtxt.split()
+ self.args = self.filter_arsg(args)
+ fp = subprocess.Popen([confprog, arg],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (libtxt, liberr) = fp.communicate()
+ libtxt = libtxt.decode()
+ liberr = liberr.decode()
+ if fp.returncode != 0:
+ raise DependencyException('Error getting objc-lib args: %s %s' % (libtxt, liberr))
+ self.libs = self.weird_filter(libtxt.split())
+ mlog.log('Dependency GnuStep found:', mlog.green('YES'))
+
+ 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 found(self):
+ return self.args is not None
+
+ 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)
+ modules = kwargs.get('modules', [])
+ if isinstance(modules, str):
+ modules = [modules]
+ if len(modules) == 0:
+ 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()
+
+class GLDependency(Dependency):
+ def __init__(self, environment, kwargs):
+ Dependency.__init__(self)
+ self.is_found = False
+ self.cargs = []
+ self.linkargs = []
+ try:
+ pcdep = PkgConfigDependency('gl', environment, kwargs)
+ if pcdep.found():
+ self.is_found = True
+ self.cargs = pcdep.get_compile_args()
+ self.linkargs = pcdep.get_link_args()
+ return
+ except Exception:
+ pass
+ if mesonlib.is_osx():
+ self.is_found = True
+ self.linkargs = ['-framework', 'OpenGL']
+ return
+ if mesonlib.is_windows():
+ self.is_found = True
+ self.linkargs = ['-lopengl32']
+ return
+
+ def get_link_args(self):
+ return self.linkargs
+
+# 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)
+ self.is_found = False
+ self.cargs = []
+ self.linkargs = []
+ sdlconf = shutil.which('sdl2-config')
+ if sdlconf:
+ pc = subprocess.Popen(['sdl2-config', '--cflags'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL)
+ (stdo, _) = pc.communicate()
+ self.cargs = stdo.decode().strip().split()
+ pc = subprocess.Popen(['sdl2-config', '--libs'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.DEVNULL)
+ (stdo, _) = pc.communicate()
+ self.linkargs = stdo.decode().strip().split()
+ self.is_found = True
+ mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.green('YES'), '(%s)' % sdlconf)
+ return
+ try:
+ pcdep = PkgConfigDependency('sdl2', kwargs)
+ if pcdep.found():
+ self.is_found = True
+ self.cargs = pcdep.get_compile_args()
+ self.linkargs = pcdep.get_link_args()
+ return
+ except Exception:
+ pass
+ if mesonlib.is_osx():
+ fwdep = ExtraFrameworkDependency('sdl2', kwargs.get('required', True))
+ if fwdep.found():
+ self.is_found = True
+ self.cargs = fwdep.get_compile_args()
+ self.linkargs = fwdep.get_link_args()
+ 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
+
+class ExtraFrameworkDependency(Dependency):
+ def __init__(self, name, required, path=None):
+ Dependency.__init__(self)
+ 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
+
+class ThreadDependency(Dependency):
+ def __init__(self, environment, kwargs):
+ super().__init__()
+ 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_dep_identifier(name, kwargs):
+ elements = [name]
+ modlist = kwargs.get('modules', [])
+ if isinstance(modlist, str):
+ modlist = [modlist]
+ for module in modlist:
+ elements.append(module)
+ return '/'.join(elements) + '/main' + str(kwargs.get('main', False))
+
+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.')
+ 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)
+ if required and not fwdep.found():
+ raise DependencyException('Dependency "%s" not found' % 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,
+ }