From 82bdf07a9d1ca6c62d4645a8c996975a5cef2989 Mon Sep 17 00:00:00 2001 From: Jon Turney Date: Thu, 31 May 2018 21:23:17 +0100 Subject: [PATCH] Hoist trying several dependency detection methods up to find_external_dependency() find_external_dependency() now makes and iterates over a list of callables which are constructors with bound arguments for the dependency objects we are going to attempt to make, so we can consolidate reporting on these attempts and handling failures in that function. --- mesonbuild/dependencies/base.py | 101 ++++++++++------ mesonbuild/dependencies/misc.py | 100 +++++++--------- mesonbuild/dependencies/ui.py | 201 ++++++++++++++++---------------- 3 files changed, 209 insertions(+), 193 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 15dbd62ba..c34146f4e 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 @@ -1279,47 +1280,79 @@ def find_external_dependency(name, env, kwargs): 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', '') + if name.lower() not in _packages_accept_language and 'language' in kwargs: + raise DependencyException('%s dependency does not accept "language" keyword argument' % (name, )) + + # build a list of dependency methods to try + candidates = _build_external_dependency_list(name, env, kwargs) + + pkg_exc = None + pkgdep = [] + 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: + # if the dependency was found + if d.found(): + return d + + # otherwise, the dependency could not be found + 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 + tried_methods = ','.join([d.type_name for d in pkgdep]) + raise DependencyException('Dependency "%s" not found, tried %s' % (name, tried_methods)) + + # 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): diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 19874fc80..607643352 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -14,6 +14,7 @@ # This file contains the detection logic for miscellaneous external dependencies. +import functools import os import re import shlex @@ -436,26 +437,21 @@ class PcapDependency(ExternalDependency): @classmethod def _factory(cls, environment, kwargs): methods = cls._process_method_kw(kwargs) + candidates = [] + if DependencyMethods.PKGCONFIG in methods: - try: - pcdep = PkgConfigDependency('pcap', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception as e: - mlog.debug('Pcap not found via pkgconfig. Trying next, error was:', str(e)) + candidates.append(functools.partial(PkgConfigDependency, 'pcap', environment, kwargs)) + if DependencyMethods.CONFIG_TOOL in methods: - try: - ctdep = ConfigToolDependency.factory( - 'pcap', environment, None, kwargs, ['pcap-config'], 'pcap-config') - if ctdep.found(): - ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') - ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') - ctdep.version = cls.get_pcap_lib_version(ctdep) - return ctdep - except Exception as e: - mlog.debug('Pcap not found via pcap-config. Trying next, error was:', str(e)) - - return PcapDependency(environment, kwargs) + candidates.append(functools.partial(ConfigToolDependency.factory, + 'pcap', environment, None, kwargs, ['pcap-config'], 'pcap-config')) +# if ctdep.found(): +# ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') +# ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') +# ctdep.version = cls.get_pcap_lib_version(ctdep) +# return ctdep + + return candidates @staticmethod def get_methods(): @@ -474,32 +470,27 @@ class CupsDependency(ExternalDependency): @classmethod def _factory(cls, environment, kwargs): methods = cls._process_method_kw(kwargs) + candidates = [] + if DependencyMethods.PKGCONFIG in methods: - try: - pcdep = PkgConfigDependency('cups', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception as e: - mlog.debug('cups not found via pkgconfig. Trying next, error was:', str(e)) + candidates.append(functools.partial(PkgConfigDependency, 'cups', environment, kwargs)) + if DependencyMethods.CONFIG_TOOL in methods: - try: - ctdep = ConfigToolDependency.factory( - 'cups', environment, None, kwargs, ['cups-config'], 'cups-config') - if ctdep.found(): - ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') - ctdep.link_args = ctdep.get_config_value(['--ldflags', '--libs'], 'link_args') - return ctdep - except Exception as e: - mlog.debug('cups not found via cups-config. Trying next, error was:', str(e)) + candidates.append(functools.partial(ConfigToolDependency.factory, + 'cups', environment, None, + kwargs, ['cups-config'], + 'cups-config')) +# if ctdep.found(): +# ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') +# ctdep.link_args = ctdep.get_config_value(['--ldflags', '--libs'], 'link_args') + if DependencyMethods.EXTRAFRAMEWORK in methods: if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency('cups', False, None, environment, - kwargs.get('language', None), kwargs) - if fwdep.found(): - return fwdep - mlog.log('Dependency', mlog.bold('cups'), 'found:', mlog.red('NO')) + candidates.append(functools.partial( + ExtraFrameworkDependency, 'cups', False, None, environment, + kwargs.get('language', None), kwargs)) - return CupsDependency(environment, kwargs) + return candidates @staticmethod def get_methods(): @@ -516,26 +507,21 @@ class LibWmfDependency(ExternalDependency): @classmethod def _factory(cls, environment, kwargs): methods = cls._process_method_kw(kwargs) + candidates = [] + if DependencyMethods.PKGCONFIG in methods: - try: - kwargs['required'] = False - pcdep = PkgConfigDependency('libwmf', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception as e: - mlog.debug('LibWmf not found via pkgconfig. Trying next, error was:', str(e)) + candidates.append(functools.partial(PkgConfigDependency, 'libwmf', environment, kwargs)) + if DependencyMethods.CONFIG_TOOL in methods: - try: - ctdep = ConfigToolDependency.factory( - 'libwmf', environment, None, kwargs, ['libwmf-config'], 'libwmf-config') - if ctdep.found(): - ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') - ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') - return ctdep - except Exception as e: - mlog.debug('cups not found via libwmf-config. Trying next, error was:', str(e)) - - return LibWmfDependency(environment, kwargs) + candidates.append(functools.partial(ConfigToolDependency.factory, + 'libwmf', environment, None, kwargs, ['libwmf-config'], 'libwmf-config')) + +# if ctdep.found(): +# ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') +# ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') +# return ctdep + + return candidates @staticmethod def get_methods(): diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 197d22cb1..e25d135b7 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -15,6 +15,7 @@ # This file contains the detection logic for external dependencies that # are UI-related. +import functools import os import re import subprocess @@ -37,32 +38,34 @@ from .base import ConfigToolDependency class GLDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('gl', environment, None, kwargs) - if DependencyMethods.SYSTEM in self.methods: - if mesonlib.is_osx(): - self.is_found = True - # FIXME: Use AppleFrameworks dependency - self.link_args = ['-framework', 'OpenGL'] - # FIXME: Detect version using self.clib_compiler - self.version = '1' - return - if mesonlib.is_windows(): - self.is_found = True - # FIXME: Use self.clib_compiler.find_library() - self.link_args = ['-lopengl32'] - # FIXME: Detect version using self.clib_compiler - self.version = '1' - return + + if mesonlib.is_osx(): + self.is_found = True + # FIXME: Use AppleFrameworks dependency + self.link_args = ['-framework', 'OpenGL'] + # FIXME: Detect version using self.clib_compiler + self.version = '1' + return + if mesonlib.is_windows(): + self.is_found = True + # FIXME: Use self.clib_compiler.find_library() + self.link_args = ['-lopengl32'] + # FIXME: Detect version using self.clib_compiler + self.version = '1' + return @classmethod def _factory(cls, environment, kwargs): - if DependencyMethods.PKGCONFIG in cls._process_method_kw(kwargs): - try: - pcdep = PkgConfigDependency('gl', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception: - pass - return GLDependency(environment, kwargs) + methods = cls._process_method_kw(kwargs) + candidates = [] + + if DependencyMethods.PKGCONFIG in methods: + candidates.append(functools.partial(PkgConfigDependency, 'gl', environment, kwargs)) + + if DependencyMethods.SYSTEM in methods: + candidates.append(functools.partial(GLDependency), environment, kwargs) + + return candidates @staticmethod def get_methods(): @@ -452,33 +455,27 @@ class SDL2Dependency(ExternalDependency): @classmethod def _factory(cls, environment, kwargs): methods = cls._process_method_kw(kwargs) + candidates = [] + if DependencyMethods.PKGCONFIG in methods: - try: - pcdep = PkgConfigDependency('sdl2', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception as e: - mlog.debug('SDL 2 not found via pkgconfig. Trying next, error was:', str(e)) + candidates.append(functools.partial(PkgConfigDependency, 'sdl2', environment, kwargs)) + if DependencyMethods.CONFIG_TOOL in methods: - try: - ctdep = ConfigToolDependency.factory( - 'sdl2', environment, None, kwargs, ['sdl2-config'], 'sdl2-config') - if ctdep.found(): - ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') - ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') - return ctdep - except Exception as e: - mlog.debug('SDL 2 not found via sdl2-config. Trying next, error was:', str(e)) + candidates.append(functools.partial(ConfigToolDependency.factory, + 'sdl2', environment, None, + kwargs, ['sdl2-config'], + 'sdl2-config')) +# if ctdep.found(): +# ctdep.compile_args = ctdep.get_config_value(['--cflags'], 'compile_args') +# ctdep.link_args = ctdep.get_config_value(['--libs'], 'link_args') + if DependencyMethods.EXTRAFRAMEWORK in methods: if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency('sdl2', False, None, environment, - kwargs.get('language', None), kwargs) - if fwdep.found(): - fwdep.version = '2' # FIXME - return fwdep - mlog.log('Dependency', mlog.bold('sdl2'), 'found:', mlog.red('NO')) - - return SDL2Dependency(environment, kwargs) + candidates.append(functools.partial(ExtraFrameworkDependency, + 'sdl2', False, None, environment, + kwargs.get('language', None), kwargs)) + # fwdep.version = '2' # FIXME + return candidates @staticmethod def get_methods(): @@ -518,73 +515,73 @@ class VulkanDependency(ExternalDependency): def __init__(self, environment, kwargs): super().__init__('vulkan', environment, None, kwargs) - if DependencyMethods.SYSTEM in self.methods: - try: - self.vulkan_sdk = os.environ['VULKAN_SDK'] - if not os.path.isabs(self.vulkan_sdk): - raise DependencyException('VULKAN_SDK must be an absolute path.') - except KeyError: - self.vulkan_sdk = None - - if self.vulkan_sdk: - # TODO: this config might not work on some platforms, fix bugs as reported - # we should at least detect other 64-bit platforms (e.g. armv8) + try: + self.vulkan_sdk = os.environ['VULKAN_SDK'] + if not os.path.isabs(self.vulkan_sdk): + raise DependencyException('VULKAN_SDK must be an absolute path.') + except KeyError: + self.vulkan_sdk = None + + if self.vulkan_sdk: + # TODO: this config might not work on some platforms, fix bugs as reported + # we should at least detect other 64-bit platforms (e.g. armv8) + lib_name = 'vulkan' + if mesonlib.is_windows(): + lib_name = 'vulkan-1' + lib_dir = 'Lib32' + inc_dir = 'Include' + if detect_cpu({}) == 'x86_64': + lib_dir = 'Lib' + else: lib_name = 'vulkan' - if mesonlib.is_windows(): - lib_name = 'vulkan-1' - lib_dir = 'Lib32' - inc_dir = 'Include' - if detect_cpu({}) == 'x86_64': - lib_dir = 'Lib' - else: - lib_name = 'vulkan' - lib_dir = 'lib' - inc_dir = 'include' + lib_dir = 'lib' + inc_dir = 'include' - # make sure header and lib are valid - inc_path = os.path.join(self.vulkan_sdk, inc_dir) - header = os.path.join(inc_path, 'vulkan', 'vulkan.h') - lib_path = os.path.join(self.vulkan_sdk, lib_dir) - find_lib = self.clib_compiler.find_library(lib_name, environment, lib_path) + # make sure header and lib are valid + inc_path = os.path.join(self.vulkan_sdk, inc_dir) + header = os.path.join(inc_path, 'vulkan', 'vulkan.h') + lib_path = os.path.join(self.vulkan_sdk, lib_dir) + find_lib = self.clib_compiler.find_library(lib_name, environment, lib_path) - if not find_lib: - raise DependencyException('VULKAN_SDK point to invalid directory (no lib)') + if not find_lib: + raise DependencyException('VULKAN_SDK point to invalid directory (no lib)') - if not os.path.isfile(header): - raise DependencyException('VULKAN_SDK point to invalid directory (no include)') + if not os.path.isfile(header): + raise DependencyException('VULKAN_SDK point to invalid directory (no include)') - self.type_name = 'vulkan_sdk' - self.is_found = True - self.compile_args.append('-I' + inc_path) - self.link_args.append('-L' + lib_path) - self.link_args.append('-l' + lib_name) + self.type_name = 'vulkan_sdk' + self.is_found = True + self.compile_args.append('-I' + inc_path) + self.link_args.append('-L' + lib_path) + self.link_args.append('-l' + lib_name) - # TODO: find a way to retrieve the version from the sdk? - # Usually it is a part of the path to it (but does not have to be) - self.version = '1' + # TODO: find a way to retrieve the version from the sdk? + # Usually it is a part of the path to it (but does not have to be) + self.version = '1' + return + else: + # simply try to guess it, usually works on linux + libs = self.clib_compiler.find_library('vulkan', environment, []) + if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', environment): + self.type_name = 'system' + self.is_found = True + self.version = 1 # TODO + for lib in libs: + self.link_args.append(lib) return - else: - # simply try to guess it, usually works on linux - libs = self.clib_compiler.find_library('vulkan', environment, []) - if libs is not None and self.clib_compiler.has_header('vulkan/vulkan.h', '', environment): - self.type_name = 'system' - self.is_found = True - self.version = 1 # TODO - for lib in libs: - self.link_args.append(lib) - return @classmethod def _factory(cls, environment, kwargs): - if DependencyMethods.PKGCONFIG in cls._process_method_kw(kwargs): - try: - pcdep = PkgConfigDependency('vulkan', environment, kwargs) - if pcdep.found(): - return pcdep - except Exception: - pass + methods = cls._process_method_kw(kwargs) + candidates = [] - return VulkanDependency(environment, kwargs) + if DependencyMethods.PKGCONFIG in methods: + candidates.append(functools.partial(PkgConfigDependency, 'vulkan', environment, kwargs)) + + if DependencyMethods.PKGCONFIG in methods: + candidates.append(functools.partial(VulkanDependency, environment, kwargs)) + + return candidates @staticmethod def get_methods():