diff options
Diffstat (limited to 'meson/dependencies.py')
-rw-r--r-- | meson/dependencies.py | 1120 |
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, + } |