diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2017-05-11 22:43:10 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-11 22:43:10 +0300 |
commit | 86ec0d9477c3c7fe313dc250c10929dabc5c6188 (patch) | |
tree | 8da675f8a5ea931aa7e25c57e818a6a1cd925874 | |
parent | 73494c992761a528bf98c8dd48fbdd2f71d4b521 (diff) | |
parent | e922a6f6f030b9781b994f1088bac70ec02772a4 (diff) | |
download | meson-86ec0d9477c3c7fe313dc250c10929dabc5c6188.zip meson-86ec0d9477c3c7fe313dc250c10929dabc5c6188.tar.gz meson-86ec0d9477c3c7fe313dc250c10929dabc5c6188.tar.bz2 |
Merge pull request #1631 from QuLogic/split-dependencies
Split dependencies.py into separate files
-rw-r--r-- | mesonbuild/dependencies.py | 1815 | ||||
-rw-r--r-- | mesonbuild/dependencies/__init__.py | 46 | ||||
-rw-r--r-- | mesonbuild/dependencies/base.py | 641 | ||||
-rw-r--r-- | mesonbuild/dependencies/dev.py | 293 | ||||
-rw-r--r-- | mesonbuild/dependencies/misc.py | 382 | ||||
-rw-r--r-- | mesonbuild/dependencies/platform.py | 44 | ||||
-rw-r--r-- | mesonbuild/dependencies/ui.py | 560 |
7 files changed, 1966 insertions, 1815 deletions
diff --git a/mesonbuild/dependencies.py b/mesonbuild/dependencies.py deleted file mode 100644 index d9a7c9c..0000000 --- a/mesonbuild/dependencies.py +++ /dev/null @@ -1,1815 +0,0 @@ -# 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 - __cpp_blacklist = {'-DNDEBUG'} - - 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.__best_found), - 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 = list(mesonlib.OrderedSet(shlex.split(out)).difference(self.__cpp_blacklist)) - - 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, - } diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py new file mode 100644 index 0000000..ec11152 --- /dev/null +++ b/mesonbuild/dependencies/__init__.py @@ -0,0 +1,46 @@ +# Copyright 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. + +from .base import ( # noqa: F401 + Dependency, DependencyException, DependencyMethods, ExternalProgram, ExternalLibrary, ExtraFrameworkDependency, + InternalDependency, PkgConfigDependency, find_external_dependency, get_dep_identifier, packages) +from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency +from .misc import BoostDependency, Python3Dependency, ThreadDependency +from .platform import AppleFrameworks +from .ui import GLDependency, GnuStepDependency, Qt4Dependency, Qt5Dependency, SDL2Dependency, WxDependency + + +packages.update({ + # From dev: + 'gtest': GTestDependency, + 'gmock': GMockDependency, + 'llvm': LLVMDependency, + 'valgrind': ValgrindDependency, + + # From misc: + 'boost': BoostDependency, + 'python3': Python3Dependency, + 'threads': ThreadDependency, + + # From platform: + 'appleframeworks': AppleFrameworks, + + # From ui: + 'gl': GLDependency, + 'gnustep': GnuStepDependency, + 'qt4': Qt4Dependency, + 'qt5': Qt5Dependency, + 'sdl2': SDL2Dependency, + 'wxwidgets': WxDependency, +}) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py new file mode 100644 index 0000000..b934ddf --- /dev/null +++ b/mesonbuild/dependencies/base.py @@ -0,0 +1,641 @@ +# 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. +# Custom logic for several other packages are in separate files. + +import os +import shutil +import stat +import sys +from enum import Enum + +from .. import mlog +from .. import mesonlib +from ..mesonlib import MesonException, Popen_safe, flatten, version_compare_many + + +# This must be defined in this file to avoid cyclical references. +packages = {} + + +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 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 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' + + +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 diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py new file mode 100644 index 0000000..569108e --- /dev/null +++ b/mesonbuild/dependencies/dev.py @@ -0,0 +1,293 @@ +# 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 useful for +# development purposes, such as testing, debugging, etc.. + +import os +import shlex +import shutil + +from .. import mlog +from .. import mesonlib +from ..mesonlib import version_compare, Popen_safe +from .base import Dependency, DependencyException, PkgConfigDependency + + +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 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 + __cpp_blacklist = {'-DNDEBUG'} + + 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.__best_found), + 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 = list(mesonlib.OrderedSet(shlex.split(out)).difference(self.__cpp_blacklist)) + + 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 + + +class ValgrindDependency(PkgConfigDependency): + def __init__(self, environment, kwargs): + PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs) + + def get_link_args(self): + return [] diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py new file mode 100644 index 0000000..3374c6e --- /dev/null +++ b/mesonbuild/dependencies/misc.py @@ -0,0 +1,382 @@ +# 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 miscellaneous external dependencies. + +import glob +import os +import stat +import sysconfig + +from .. import mlog +from .. import mesonlib +from ..environment import detect_cpu_family + +from .base import Dependency, DependencyException, DependencyMethods, ExtraFrameworkDependency, PkgConfigDependency + + +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 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 diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py new file mode 100644 index 0000000..cd46412 --- /dev/null +++ b/mesonbuild/dependencies/platform.py @@ -0,0 +1,44 @@ +# 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 that are +# platform-specific (generally speaking). + +from .. import mesonlib + +from .base import Dependency, DependencyException + + +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' diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py new file mode 100644 index 0000000..3174176 --- /dev/null +++ b/mesonbuild/dependencies/ui.py @@ -0,0 +1,560 @@ +# 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 that +# are UI-related. + +import os +import re +import shutil +import subprocess +from collections import OrderedDict + +from .. import mlog +from .. import mesonlib +from ..mesonlib import MesonException, Popen_safe, version_compare +from ..environment import for_windows + +from .base import (Dependency, DependencyException, DependencyMethods, + ExternalProgram, ExtraFrameworkDependency, PkgConfigDependency) + + +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] + + +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 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 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 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') + + +# 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 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 |