diff options
Diffstat (limited to 'mesonbuild/dependencies/base.py')
-rw-r--r-- | mesonbuild/dependencies/base.py | 241 |
1 files changed, 143 insertions, 98 deletions
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 45ff96b..c4f9630 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -16,6 +16,7 @@ # Custom logic for several other packages are in separate files. import copy +import functools import os import re import stat @@ -265,6 +266,15 @@ class ExternalDependency(Dependency): return new + def log_details(self): + return '' + + def log_info(self): + return '' + + def log_tried(self): + return '' + class NotFoundDependency(Dependency): def __init__(self, environment): @@ -296,6 +306,8 @@ class ConfigToolDependency(ExternalDependency): self.config = None return self.version = version + if getattr(self, 'finish_init', None): + self.finish_init(self) def _sanitize_version(self, version): """Remove any non-numeric, non-point version suffixes.""" @@ -307,7 +319,7 @@ class ConfigToolDependency(ExternalDependency): return version @classmethod - def factory(cls, name, environment, language, kwargs, tools, tool_name): + def factory(cls, name, environment, language, kwargs, tools, tool_name, finish_init=None): """Constructor for use in dependencies that can be found multiple ways. In addition to the standard constructor values, this constructor sets @@ -322,7 +334,7 @@ class ConfigToolDependency(ExternalDependency): def reduce(self): return (cls._unpickle, (), self.__dict__) sub = type('{}Dependency'.format(name.capitalize()), (cls, ), - {'tools': tools, 'tool_name': tool_name, '__reduce__': reduce}) + {'tools': tools, 'tool_name': tool_name, '__reduce__': reduce, 'finish_init': staticmethod(finish_init)}) return sub(name, environment, language, kwargs) @@ -388,13 +400,9 @@ class ConfigToolDependency(ExternalDependency): else: mlog.log('Found', mlog.bold(self.tool_name), repr(req_version), mlog.red('NO')) - mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.red('NO')) - if self.required: - raise DependencyException('Dependency {} not found'.format(self.name)) return False mlog.log('Found {}:'.format(self.tool_name), mlog.bold(shutil.which(self.config)), '({})'.format(version)) - mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) return True def get_config_value(self, args, stage): @@ -422,6 +430,9 @@ class ConfigToolDependency(ExternalDependency): mlog.debug('Got config-tool variable {} : {}'.format(variable_name, variable)) return variable + def log_tried(self): + return self.type_name + class PkgConfigDependency(ExternalDependency): # The class's copy of the pkg-config path. Avoids having to search for it @@ -463,20 +474,12 @@ class PkgConfigDependency(ExternalDependency): 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.get_path())) ret, self.version = 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: @@ -487,14 +490,6 @@ class PkgConfigDependency(ExternalDependency): (self.is_found, not_found, found) = \ version_compare_many(self.version, self.version_reqs) if not self.is_found: - found_msg += [mlog.red('NO'), - 'found {!r} but need:'.format(self.version), - ', '.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.version)) @@ -505,7 +500,6 @@ class PkgConfigDependency(ExternalDependency): self._set_cargs() # Fetch the libraries and library paths needed for using this self._set_libs() - found_msg += [mlog.green('YES'), self.version] except DependencyException as e: if self.required: raise @@ -513,12 +507,7 @@ class PkgConfigDependency(ExternalDependency): self.compile_args = [] self.link_args = [] self.is_found = False - found_msg += [mlog.red('NO'), '; reason: {}'.format(str(e))] - - # 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) + self.reason = e def __repr__(self): s = '<{0} {1}: {2} {3}>' @@ -712,8 +701,8 @@ class PkgConfigDependency(ExternalDependency): variable = '' if ret != 0: if self.required: - raise DependencyException('%s dependency %s not found.' % - (self.type_string, self.name)) + raise DependencyException('dependency %s not found.' % + (self.name)) else: variable = out.strip() @@ -797,6 +786,9 @@ class PkgConfigDependency(ExternalDependency): # a path rather than the raw dlname return os.path.basename(dlname) + def log_tried(self): + return self.type_name + class DubDependency(ExternalDependency): class_dubbin = None @@ -818,7 +810,6 @@ class DubDependency(ExternalDependency): if self.required: raise DependencyException('DUB not found.') self.is_found = False - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) return mlog.debug('Determining dependency {!r} with DUB executable ' @@ -828,10 +819,7 @@ class DubDependency(ExternalDependency): ret, res = self._call_dubbin(['describe', name]) if ret != 0: - if self.required: - raise DependencyException('Dependency {!r} not found'.format(name)) self.is_found = False - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) return j = json.loads(res) @@ -842,10 +830,7 @@ class DubDependency(ExternalDependency): msg = ['Dependency', mlog.bold(name), 'found but it was compiled with'] msg += [mlog.bold(j['compiler']), 'and we are using', mlog.bold(comp)] mlog.error(*msg) - if self.required: - raise DependencyException('Dependency {!r} not found'.format(name)) self.is_found = False - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) return self.version = package['version'] @@ -853,7 +838,6 @@ class DubDependency(ExternalDependency): break # Check if package version meets the requirements - found_msg = ['Dependency', mlog.bold(name), 'found:'] if self.version_reqs is None: self.is_found = True else: @@ -864,21 +848,11 @@ class DubDependency(ExternalDependency): (self.is_found, not_found, found) = \ version_compare_many(self.version, self.version_reqs) if not self.is_found: - found_msg += [mlog.red('NO'), - 'found {!r} but need:'.format(self.version), - ', '.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.version)) return - found_msg += [mlog.green('YES'), self.version] - if self.pkg['targetFileName'].endswith('.a'): self.static = True @@ -899,14 +873,8 @@ class DubDependency(ExternalDependency): self.link_args.append(file) if not found: - if self.required: - raise DependencyException('Dependency {!r} not found'.format(name)) - self.is_found = False - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) - return - - if not self.silent: - mlog.log(*found_msg) + self.is_found = False + return def get_compiler(self): return self.compiler @@ -952,7 +920,7 @@ class DubDependency(ExternalDependency): @staticmethod def get_methods(): - return [DependencyMethods.PKGCONFIG, DependencyMethods.DUB] + return [DependencyMethods.DUB] class ExternalProgram: windows_exts = ('exe', 'msc', 'com', 'bat', 'cmd') @@ -1229,10 +1197,6 @@ class ExtraFrameworkDependency(ExternalDependency): if self.found(): self.compile_args = ['-I' + os.path.join(self.path, self.name, 'Headers')] self.link_args = ['-F' + self.path, '-framework', self.name.split('.')[0]] - 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() @@ -1251,12 +1215,16 @@ class ExtraFrameworkDependency(ExternalDependency): self.name = d self.is_found = True return - if not self.found() and self.required: - raise DependencyException('Framework dependency %s not found.' % (name, )) def get_version(self): return 'unknown' + def log_info(self): + return os.path.join(self.path, self.name) + + def log_tried(self): + return 'framework' + def get_dep_identifier(name, kwargs, want_cross): # Need immutable objects since the identifier will be used as a dict key @@ -1277,54 +1245,131 @@ def get_dep_identifier(name, kwargs, want_cross): identifier += (key, value) return identifier +display_name_map = { + 'boost': 'Boost', + 'dub': 'DUB', + 'gmock': 'GMock', + 'gtest': 'GTest', + 'llvm': 'LLVM', + 'mpi': 'MPI', + 'openmp': 'OpenMP', + 'wxwidgets': 'WxWidgets', +} def find_external_dependency(name, env, kwargs): + assert(name) 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.') - method = kwargs.get('method', '') + lname = name.lower() + if lname not in _packages_accept_language and 'language' in kwargs: + raise DependencyException('%s dependency does not accept "language" keyword argument' % (name, )) + + # display the dependency name with correct casing + display_name = display_name_map.get(lname, lname) + + # if this isn't a cross-build, it's uninteresting if native: is used or not + if not env.is_cross_build(): + type_text = 'Dependency' + else: + type_text = 'Native' if kwargs.get('native', False) else 'Cross' + type_text += ' dependency' + + # build a list of dependency methods to try + candidates = _build_external_dependency_list(name, env, kwargs) + + pkg_exc = None + pkgdep = [] + details = '' + + for c in candidates: + # try this dependency method + try: + d = c() + pkgdep.append(d) + except Exception as e: + mlog.debug(str(e)) + # store the first exception we see + if not pkg_exc: + pkg_exc = e + else: + details = d.log_details() + if details: + details = '(' + details + ') ' + if 'language' in kwargs: + details += 'for ' + d.language + ' ' + + # if the dependency was found + if d.found(): + + info = d.log_info() + if info: + info = ', ' + info + + mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.green('YES'), d.version + info) + + return d + + # otherwise, the dependency could not be found + tried_methods = [d.log_tried() for d in pkgdep if d.log_tried()] + if tried_methods: + tried = '{}'.format(mlog.format_list(tried_methods)) + else: + tried = '' + + mlog.log(type_text, mlog.bold(display_name), details + 'found:', mlog.red('NO'), + '(tried {})'.format(tried) if tried else '') + + if required: + # if exception(s) occurred, re-raise the first one (on the grounds that + # it came from a preferred dependency detection method) + if pkg_exc: + raise pkg_exc + + # we have a list of failed ExternalDependency objects, so we can report + # the methods we tried to find the dependency + raise DependencyException('Dependency "%s" not found, tried %s' % (name, tried)) + + # return the last failed dependency object + if pkgdep: + return pkgdep[-1] + + # this should never happen + raise DependencyException('Dependency "%s" not found, but no dependency object to return' % (name)) + + +def _build_external_dependency_list(name, env, kwargs): + # Is there a specific dependency detector for this dependency? lname = name.lower() if lname in packages: - if lname not in _packages_accept_language and 'language' in kwargs: - raise DependencyException('%s dependency does not accept "language" keyword argument' % (lname, )) - # Create the dependency object using a factory class method, if one - # exists, otherwise it is just constructed directly. + # Create the list of dependency object constructors using a factory + # class method, if one exists, otherwise the list just consists of the + # constructor if getattr(packages[lname], '_factory', None): dep = packages[lname]._factory(env, kwargs) else: - dep = packages[lname](env, kwargs) - if required and not dep.found(): - raise DependencyException('Dependency "%s" not found' % name) + dep = [functools.partial(packages[lname], env, kwargs)] return dep - if 'language' in kwargs: - # Remove check when PkgConfigDependency supports language. - raise DependencyException('%s dependency does not accept "language" keyword argument' % (lname, )) - if 'dub' == method: - dubdep = DubDependency(name, env, kwargs) - if required and not dubdep.found(): - mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) - return dubdep - pkg_exc = None - pkgdep = None - try: - pkgdep = PkgConfigDependency(name, env, kwargs) - if pkgdep.found(): - return pkgdep - except Exception as e: - pkg_exc = e + + candidates = [] + + # If it's explicitly requested, use the dub detection method (only) + if 'dub' == kwargs.get('method', ''): + candidates.append(functools.partial(DubDependency, name, env, kwargs)) + return candidates + # TBD: other values of method should control what method(s) are used + + # Otherwise, just use the pkgconfig dependency detector + candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) + + # On OSX, also try framework dependency detector if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency(name, False, None, env, 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 + candidates.append(functools.partial(ExtraFrameworkDependency, name, + False, None, env, None, kwargs)) + + return candidates def strip_system_libdirs(environment, link_args): |