From 0c83f8352d288134ede8f4b8854e455007ce02b7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 03:29:40 +0530 Subject: dependencies: Add a new class ExternalDependency This class now consolidates a lot of the logic that each external dependency was duplicating in its class definition. All external dependencies now set: * self.version * self.compile_args and self.link_args * self.is_found (if found) * self.sources * etc And the abstract ExternalDependency class defines the methods that will fetch those properties. Some classes still override that for various reasons, but those should also be migrated to properties as far as possible. Next step is to consolidate and standardize the way in which we call 'configuration binaries' such as sdl2-config, llvm-config, pkg-config, etc. Currently each class has to duplicate code involved with that even though the format is very similar. Currently only pkg-config supports multiple version requirements, and some classes don't even properly check the version requirement. That will also become easier now. --- mesonbuild/backend/backends.py | 2 +- mesonbuild/build.py | 2 +- mesonbuild/dependencies/__init__.py | 5 +- mesonbuild/dependencies/base.py | 176 ++++++++--------- mesonbuild/dependencies/dev.py | 109 +++-------- mesonbuild/dependencies/misc.py | 73 +++---- mesonbuild/dependencies/platform.py | 16 +- mesonbuild/dependencies/ui.py | 209 +++++++-------------- mesonbuild/interpreter.py | 3 +- mesonbuild/modules/gnome.py | 2 +- run_tests.py | 2 + .../linuxlike/5 dependency versions/meson.build | 12 +- test cases/osx/4 framework/meson.build | 9 +- 13 files changed, 226 insertions(+), 394 deletions(-) diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 3044ce6..05d6e03 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -429,7 +429,7 @@ class Backend: break commands += ['--pkg', dep.name] elif isinstance(dep, dependencies.ExternalLibrary): - commands += dep.get_lang_args('vala') + commands += dep.get_link_args('vala') else: commands += dep.get_compile_args() # Qt needs -fPIC for executables diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 2c55ed4..ba30fec 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -806,7 +806,7 @@ class BuildTarget(Target): self.external_deps.append(extpart) # Deps of deps. self.add_deps(dep.ext_deps) - elif isinstance(dep, dependencies.Dependency): + elif isinstance(dep, dependencies.ExternalDependency): self.external_deps.append(dep) self.process_sourcelist(dep.get_sources()) elif isinstance(dep, BuildTarget): diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index ec11152..3d41a2b 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -13,8 +13,9 @@ # limitations under the License. from .base import ( # noqa: F401 - Dependency, DependencyException, DependencyMethods, ExternalProgram, ExternalLibrary, ExtraFrameworkDependency, - InternalDependency, PkgConfigDependency, find_external_dependency, get_dep_identifier, packages) + Dependency, DependencyException, DependencyMethods, ExternalProgram, + ExternalDependency, 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 diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 139ff39..d727021 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -52,9 +52,13 @@ class DependencyMethods(Enum): class Dependency: def __init__(self, type_name, kwargs): self.name = "null" - self.language = None + self.version = 'none' + self.language = None # None means C-like self.is_found = False self.type_name = type_name + self.compile_args = [] + self.link_args = [] + self.sources = [] method = DependencyMethods(kwargs.get('method', 'auto')) # Set the detection method. If the method is set to auto, use any available method. @@ -74,10 +78,10 @@ class Dependency: return s.format(self.__class__.__name__, self.name, self.is_found) def get_compile_args(self): - return [] + return self.compile_args def get_link_args(self): - return [] + return self.link_args def found(self): return self.is_found @@ -85,7 +89,7 @@ class Dependency: def get_sources(self): """Source files that need to be added to the target. As an example, gtest-all.cc when using GTest.""" - return [] + return self.sources def get_methods(self): return [DependencyMethods.AUTO] @@ -93,6 +97,9 @@ class Dependency: def get_name(self): return self.name + def get_version(self): + return self.version + def get_exe_args(self, compiler): return [] @@ -100,7 +107,7 @@ class Dependency: return False def get_pkgconfig_variable(self, variable_name): - raise MesonException('Tried to get a pkg-config variable from a non-pkgconfig dependency.') + raise NotImplementedError('{!r} is not a pkgconfig dependency'.format(self.name)) class InternalDependency(Dependency): @@ -115,41 +122,52 @@ class InternalDependency(Dependency): 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 +class ExternalDependency(Dependency): + def __init__(self, type_name, environment, language, kwargs): + super().__init__(type_name, kwargs) + self.env = environment + self.name = type_name # default + self.is_found = False + self.language = language + if language and language not in self.env.coredata.compilers: + m = self.name.capitalize() + ' requires a {} compiler' + raise DependencyException(m.format(language.capitalize())) + self.version_reqs = kwargs.get('version', None) + self.required = kwargs.get('required', True) + self.silent = kwargs.get('silent', False) + self.static = kwargs.get('static', False) + if not isinstance(self.static, bool): + raise DependencyException('Static keyword must be boolean') + # Is this dependency for cross-com,pilation? + if 'native' in kwargs and self.env.is_cross_build(): + self.want_cross = not kwargs['native'] + else: + self.want_cross = self.env.is_cross_build() + # Set the compiler that will be used by this dependency + # This is only used for configuration checks + if self.want_cross: + compilers = self.env.coredata.cross_compilers + else: + compilers = self.env.coredata.compilers + self.compiler = compilers.get(self.language or 'c', None) - def get_version(self): - return self.version + def get_compiler(self): + return self.compiler -class PkgConfigDependency(Dependency): +class PkgConfigDependency(ExternalDependency): # 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) + super().__init__('pkgconfig', environment, None, kwargs) + self.name = name 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 @@ -175,7 +193,6 @@ class PkgConfigDependency(Dependency): else: self.pkgbin = PkgConfigDependency.class_pkgbin - self.is_found = False if not self.pkgbin: if self.required: raise DependencyException('Pkg-config not found.') @@ -187,7 +204,7 @@ class PkgConfigDependency(Dependency): mlog.debug('Determining dependency {!r} with pkg-config executable ' '{!r}'.format(name, self.pkgbin)) - ret, self.modversion = self._call_pkgbin(['--modversion', name]) + ret, self.version = self._call_pkgbin(['--modversion', name]) if ret != 0: if self.required: raise DependencyException('{} dependency {!r} not found' @@ -202,10 +219,10 @@ class PkgConfigDependency(Dependency): 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) + version_compare_many(self.version, self.version_reqs) if not self.is_found: found_msg += [mlog.red('NO'), - 'found {!r} but need:'.format(self.modversion), + 'found {!r} but need:'.format(self.version), ', '.join(["'{}'".format(e) for e in not_found])] if found: found_msg += ['; matched:', @@ -214,9 +231,9 @@ class PkgConfigDependency(Dependency): 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)) + raise DependencyException(m.format(name, not_found, self.version)) return - found_msg += [mlog.green('YES'), self.modversion] + found_msg += [mlog.green('YES'), self.version] # Fetch cargs to be used while using this dependency self._set_cargs() # Fetch the libraries and library paths needed for using this @@ -240,7 +257,7 @@ class PkgConfigDependency(Dependency): if ret != 0: raise DependencyException('Could not generate cargs for %s:\n\n%s' % (self.name, out)) - self.cargs = out.split() + self.compile_args = out.split() def _set_libs(self): libcmd = [self.name, '--libs'] @@ -250,7 +267,7 @@ class PkgConfigDependency(Dependency): if ret != 0: raise DependencyException('Could not generate libs for %s:\n\n%s' % (self.name, out)) - self.libs = [] + self.link_args = [] for lib in out.split(): if lib.endswith(".la"): shared_libname = self.extract_libtool_shlib(lib) @@ -264,7 +281,7 @@ class PkgConfigDependency(Dependency): 'library path' % lib) lib = shared_lib self.is_libtool = True - self.libs.append(lib) + self.link_args.append(lib) def get_pkgconfig_variable(self, variable_name): ret, out = self._call_pkgbin(['--variable=' + variable_name, self.name]) @@ -278,18 +295,6 @@ class PkgConfigDependency(Dependency): 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] @@ -319,9 +324,6 @@ class PkgConfigDependency(Dependency): 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: @@ -500,52 +502,39 @@ class ExternalProgram: 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', {}) +class ExternalLibrary(ExternalDependency): + def __init__(self, name, link_args, environment, language, silent=False): + super().__init__('external', environment, language, {}) 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 + 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): + def get_link_args(self, language=None): + ''' + External libraries detected using a compiler must only be used with + compatible code. For instance, Vala libraries (.vapi files) cannot be + used with C code, and not all Rust library types can be linked with + C-like code. Note that C++ libraries *can* be linked with C code with + a C++ linker (and vice-versa). + ''' + if self.language == 'vala' and language != 'vala': + return [] 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) +class ExtraFrameworkDependency(ExternalDependency): + def __init__(self, name, required, path, env, lang, kwargs): + super().__init__('extraframeworks', env, lang, kwargs) self.name = None self.required = required self.detect(name, path) @@ -570,6 +559,7 @@ class ExtraFrameworkDependency(Dependency): continue self.path = p self.name = d + self.is_found = True return if not self.found() and self.required: raise DependencyException('Framework dependency %s not found.' % (name, )) @@ -584,9 +574,6 @@ class ExtraFrameworkDependency(Dependency): 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' @@ -611,7 +598,7 @@ def get_dep_identifier(name, kwargs, want_cross): return identifier -def find_external_dependency(name, environment, kwargs): +def find_external_dependency(name, env, kwargs): required = kwargs.get('required', True) if not isinstance(required, bool): raise DependencyException('Keyword "required" must be a boolean.') @@ -619,20 +606,20 @@ def find_external_dependency(name, environment, kwargs): raise DependencyException('Keyword "method" must be a string.') lname = name.lower() if lname in packages: - dep = packages[lname](environment, kwargs) + dep = packages[lname](env, 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) + pkgdep = PkgConfigDependency(name, env, kwargs) if pkgdep.found(): return pkgdep except Exception as e: pkg_exc = e if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency(name, required, None, kwargs) + fwdep = ExtraFrameworkDependency(name, required, None, env, kwargs) if required and not fwdep.found(): m = 'Dependency {!r} not found, tried Extra Frameworks ' \ 'and Pkg-Config:\n\n' + str(pkg_exc) @@ -642,16 +629,3 @@ def find_external_dependency(name, environment, kwargs): raise pkg_exc mlog.log('Dependency', mlog.bold(name), 'found:', mlog.red('NO')) return pkgdep - -def dependency_get_compiler(language, environment, kwargs): - if 'native' in kwargs and environment.is_cross_build(): - want_cross = not kwargs['native'] - else: - want_cross = environment.is_cross_build() - - if want_cross: - compilers = environment.coredata.cross_compilers - else: - compilers = environment.coredata.compilers - - return compilers.get(language, None) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 76d6691..8d64379 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -22,29 +22,20 @@ import shutil from .. import mlog from .. import mesonlib from ..mesonlib import version_compare, Popen_safe -from .base import Dependency, DependencyException, PkgConfigDependency, dependency_get_compiler +from .base import DependencyException, ExternalDependency, PkgConfigDependency -class GTestDependency(Dependency): +class GTestDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gtest', kwargs) - self.env = environment + super().__init__('gtest', environment, 'cpp', kwargs) self.main = kwargs.get('main', False) - self.name = 'gtest' - self.include_dir = '/usr/include' self.src_dirs = ['/usr/src/gtest/src', '/usr/src/googletest/googletest/src'] - - self.cpp_compiler = dependency_get_compiler('cpp', environment, kwargs) - if self.cpp_compiler is None: - raise DependencyException('Tried to use gtest but a C++ compiler is not defined.') self.detect() - def found(self): - return self.is_found - def detect(self): - gtest_detect = self.cpp_compiler.find_library("gtest", self.env, []) - gtest_main_detect = self.cpp_compiler.find_library("gtest_main", self.env, []) - if gtest_detect and gtest_main_detect: + self.version = '1.something_maybe' + gtest_detect = self.compiler.find_library("gtest", self.env, []) + gtest_main_detect = self.compiler.find_library("gtest_main", self.env, []) + if gtest_detect and (not self.main or gtest_main_detect): self.is_found = True self.compile_args = [] self.link_args = gtest_detect @@ -64,7 +55,6 @@ class GTestDependency(Dependency): 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: @@ -78,37 +68,17 @@ class GTestDependency(Dependency): 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): +class GMockDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gmock', kwargs) + super().__init__('gmock', environment, 'cpp', kwargs) + self.version = '1.something_maybe' # GMock may be a library or just source. # Work with both. - self.name = 'gmock' - cpp_compiler = dependency_get_compiler('cpp', environment, kwargs) - if cpp_compiler is None: - raise DependencyException('Tried to use gmock but a C++ compiler is not defined.') - gmock_detect = cpp_compiler.find_library("gmock", environment, []) + gmock_detect = self.compiler.find_library("gmock", self.env, []) if gmock_detect: self.is_found = True self.compile_args = [] @@ -133,29 +103,12 @@ class GMockDependency(Dependency): 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. +class LLVMDependency(ExternalDependency): + """ LLVM uses a special tool, llvm-config, which has arguments for getting c args, cxx args, and ldargs as well as version. """ @@ -182,15 +135,11 @@ class LLVMDependency(Dependency): __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 = [] + super().__init__('llvm-config', environment, 'cpp', kwargs) self.modules = [] - - required = kwargs.get('required', True) + # FIXME: Support multiple version requirements ala PkgConfigDependency req_version = kwargs.get('version', None) if self.llvmconfig is None: self.check_llvmconfig(req_version) @@ -201,14 +150,14 @@ class LLVMDependency(Dependency): else: mlog.log("No llvm-config found; can't detect dependency") mlog.log('Dependency LLVM found:', mlog.red('NO')) - if required: + if self.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: + if self.required: raise DependencyException('Dependency LLVM not found') return else: @@ -220,12 +169,13 @@ class LLVMDependency(Dependency): [self.llvmconfig, '--libs', '--ldflags', '--system-libs'])[:2] if p.returncode != 0: raise DependencyException('Could not generate libs for LLVM.') - self.libs = shlex.split(out) + self.link_args = 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)) + cargs = mesonlib.OrderedSet(shlex.split(out)) + self.compile_args = list(cargs.difference(self.__cpp_blacklist)) p, out = Popen_safe([self.llvmconfig, '--components'])[:2] if p.returncode != 0: @@ -237,21 +187,12 @@ class LLVMDependency(Dependency): if mod not in self.modules: mlog.log('LLVM module', mod, 'found:', mlog.red('NO')) self.is_found = False - if required: + if self.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.""" @@ -288,8 +229,12 @@ class LLVMDependency(Dependency): class ValgrindDependency(PkgConfigDependency): - def __init__(self, environment, kwargs): - PkgConfigDependency.__init__(self, 'valgrind', environment, kwargs) + ''' + Consumers of Valgrind usually only need the compile args and do not want to + link to its (static) libraries. + ''' + def __init__(self, env, kwargs): + super().__init__('valgrind', env, None, kwargs) def get_link_args(self): return [] diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 6a76ba6..1356ec8 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -23,25 +23,19 @@ from .. import mlog from .. import mesonlib from ..environment import detect_cpu_family -from .base import Dependency, DependencyException, DependencyMethods, ExtraFrameworkDependency, PkgConfigDependency +from .base import DependencyException, DependencyMethods +from .base import ExternalDependency, ExtraFrameworkDependency, PkgConfigDependency -class BoostDependency(Dependency): +class BoostDependency(ExternalDependency): # 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 + super().__init__('boost', environment, 'cpp', kwargs) self.libdir = '' - self.static = kwargs.get('static', False) - 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): @@ -72,7 +66,7 @@ class BoostDependency(Dependency): self.detect_version() self.requested_modules = self.get_requested(kwargs) module_str = ', '.join(self.requested_modules) - if self.version is not None: + if self.is_found: self.detect_src_modules() self.detect_lib_modules() self.validate_requested() @@ -83,9 +77,6 @@ class BoostDependency(Dependency): 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_*' @@ -130,7 +121,7 @@ class BoostDependency(Dependency): # names in order to handle cases like cross-compiling where we # might have a different sysroot. if not include_dir.endswith(('/usr/include', '/usr/local/include')): - args.append("".join(self.cpp_compiler.get_include_args(include_dir, True))) + args.append("".join(self.compiler.get_include_args(include_dir, True))) return args def get_requested(self, kwargs): @@ -147,17 +138,10 @@ class BoostDependency(Dependency): 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: @@ -165,8 +149,8 @@ class BoostDependency(Dependency): ver = line.split()[-1] ver = ver[1:-1] self.version = ver.replace('_', '.') + self.is_found = True return - self.version = None def detect_src_modules(self): for entry in os.listdir(self.boost_inc_subdir): @@ -180,7 +164,7 @@ class BoostDependency(Dependency): return self.detect_lib_modules_nix() def detect_lib_modules_win(self): - arch = detect_cpu_family(self.environment.coredata.compilers) + arch = detect_cpu_family(self.env.coredata.compilers) # Guess the libdir if arch == 'x86': gl = 'lib32*' @@ -254,10 +238,10 @@ class BoostDependency(Dependency): 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, []) + default_detect = self.compiler.find_library(libname, self.env, []) if default_detect is not None: if module == 'unit_testing_framework': - emon_args = self.cpp_compiler.find_library('boost_test_exec_monitor') + emon_args = self.compiler.find_library('boost_test_exec_monitor') else: emon_args = None args += default_detect @@ -286,9 +270,9 @@ class BoostDependency(Dependency): return 'thread' in self.requested_modules -class ThreadDependency(Dependency): +class ThreadDependency(ExternalDependency): def __init__(self, environment, kwargs): - super().__init__('threads', {}) + super().__init__('threads', environment, None, {}) self.name = 'threads' self.is_found = True mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) @@ -300,19 +284,18 @@ class ThreadDependency(Dependency): return 'unknown' -class Python3Dependency(Dependency): +class Python3Dependency(ExternalDependency): def __init__(self, environment, kwargs): - super().__init__('python3', kwargs) + super().__init__('python3', environment, None, 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.compile_args = pkgdep.get_compile_args() + self.link_args = pkgdep.get_link_args() self.version = pkgdep.get_version() self.is_found = True return @@ -324,10 +307,11 @@ class Python3Dependency(Dependency): 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) + fw = ExtraFrameworkDependency('python', False, None, self.env, + self.language, kwargs) if fw.found(): - self.cargs = fw.get_compile_args() - self.libs = fw.get_link_args() + self.compile_args = fw.get_compile_args() + self.link_args = fw.get_link_args() self.is_found = True if self.is_found: mlog.log('Dependency', mlog.bold(self.name), 'found:', mlog.green('YES')) @@ -359,23 +343,17 @@ class Python3Dependency(Dependency): return inc = sysconfig.get_path('include') platinc = sysconfig.get_path('platinclude') - self.cargs = ['-I' + inc] + self.compile_args = ['-I' + inc] if inc != platinc: - self.cargs.append('-I' + platinc) + self.compile_args.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.link_args = ['-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] @@ -383,6 +361,3 @@ class Python3Dependency(Dependency): 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 index cd46412..95ab727 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -17,25 +17,21 @@ from .. import mesonlib -from .base import Dependency, DependencyException +from .base import ExternalDependency, DependencyException -class AppleFrameworks(Dependency): - def __init__(self, environment, kwargs): - Dependency.__init__(self, 'appleframeworks', kwargs) +class AppleFrameworks(ExternalDependency): + def __init__(self, env, kwargs): + super().__init__('appleframeworks', env, None, 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 = [] + # FIXME: Use self.compiler to check if the frameworks are available for f in self.frameworks: - args.append('-framework') - args.append(f) - return args + self.link_args += ['-framework', f] def found(self): return mesonlib.is_osx() diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 3174176..7b276cc 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -26,24 +26,22 @@ 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) +from .base import DependencyException, DependencyMethods +from .base import ExternalDependency, ExternalProgram +from .base import ExtraFrameworkDependency, PkgConfigDependency -class GLDependency(Dependency): +class GLDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gl', kwargs) - self.is_found = False - self.cargs = [] - self.linkargs = [] + super().__init__('gl', environment, None, kwargs) 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.compile_args = pcdep.get_compile_args() + self.link_args = pcdep.get_link_args() self.version = pcdep.get_version() return except Exception: @@ -51,21 +49,19 @@ class GLDependency(Dependency): if DependencyMethods.SYSTEM in self.methods: if mesonlib.is_osx(): self.is_found = True - self.linkargs = ['-framework', 'OpenGL'] - self.version = '1' # FIXME + # FIXME: Use AppleFrameworks dependency + self.link_args = ['-framework', 'OpenGL'] + # FIXME: Detect version using self.compiler + self.version = '1' return if mesonlib.is_windows(): self.is_found = True - self.linkargs = ['-lopengl32'] - self.version = '1' # FIXME: unfixable? + # FIXME: Use self.compiler.find_library() + self.link_args = ['-lopengl32'] + # FIXME: Detect version using self.compiler + self.version = '1' 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] @@ -73,10 +69,9 @@ class GLDependency(Dependency): return [DependencyMethods.PKGCONFIG] -class GnuStepDependency(Dependency): +class GnuStepDependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'gnustep', kwargs) - self.required = kwargs.get('required', True) + super().__init__('gnustep', environment, 'objc', kwargs) self.modules = kwargs.get('modules', []) self.detect() @@ -85,11 +80,9 @@ class GnuStepDependency(Dependency): 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: @@ -100,12 +93,13 @@ class GnuStepDependency(Dependency): if fp.returncode != 0: raise DependencyException('Error getting objc-args: %s %s' % (flagtxt, flagerr)) args = flagtxt.split() - self.args = self.filter_arsg(args) + self.compile_args = self.filter_args(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.link_args = self.weird_filter(libtxt.split()) self.version = self.detect_version() + self.is_found = True mlog.log('Dependency', mlog.bold('GnuStep'), 'found:', mlog.green('YES'), self.version) @@ -115,7 +109,7 @@ 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): + def filter_args(self, args): """gnustep-config returns a bunch of garbage args such as -O2 and so on. Drop everything that is not needed.""" result = [] @@ -157,25 +151,10 @@ why. As a hack filter out everything that is not a flag.""" ''.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): +class QtBaseDependency(ExternalDependency): def __init__(self, name, env, kwargs): - Dependency.__init__(self, name, kwargs) - self.name = name + super().__init__(name, env, 'cpp', kwargs) self.qtname = name.capitalize() self.qtver = name[-1] if self.qtver == "4": @@ -184,16 +163,7 @@ class QtBaseDependency(Dependency): 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: @@ -207,16 +177,16 @@ class QtBaseDependency(Dependency): methods = [] # Prefer pkg-config, then fallback to `qmake -query` if DependencyMethods.PKGCONFIG in self.methods: - self._pkgconfig_detect(mods, env, kwargs) + self._pkgconfig_detect(mods, kwargs) methods.append('pkgconfig') if not self.is_found and DependencyMethods.QMAKE in self.methods: - from_text = self._qmake_detect(mods, env, kwargs) + from_text = self._qmake_detect(mods, kwargs) methods.append('qmake-' + self.name) methods.append('qmake') if not self.is_found: # Reset compile args and link args - self.cargs = [] - self.largs = [] + self.compile_args = [] + self.link_args = [] from_text = '(checked {})'.format(mlog.format_list(methods)) self.version = 'none' if self.required: @@ -244,24 +214,27 @@ class QtBaseDependency(Dependency): rcc = ExternalProgram('rcc-' + self.name, silent=True) return moc, uic, rcc - def _pkgconfig_detect(self, mods, env, kwargs): + def _pkgconfig_detect(self, mods, kwargs): + # We set the value of required to False so that we can try the + # qmake-based fallback if pkg-config fails. + kwargs['required'] = False modules = OrderedDict() for module in mods: - modules[module] = PkgConfigDependency(self.qtpkgname + module, env, kwargs) - self.is_found = True + modules[module] = PkgConfigDependency(self.qtpkgname + module, self.env, kwargs) 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 + self.compile_args += m.get_compile_args() + self.link_args += m.get_link_args() + self.is_found = True + self.version = m.version # 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) + core = PkgConfigDependency(self.qtpkgname + 'Core', self.env, corekwargs) # Used by self.compilers_detect() self.bindir = self.get_pkgconfig_host_bins(core) if not self.bindir: @@ -270,16 +243,16 @@ class QtBaseDependency(Dependency): if prefix: self.bindir = os.path.join(prefix, 'bin') - def _find_qmake(self, qmake, env): + def _find_qmake(self, qmake): # 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) + if self.env.is_cross_build(): + qmake = self.env.cross_info.config['binaries'].get('qmake', qmake) return ExternalProgram(qmake, silent=True) - def _qmake_detect(self, mods, env, kwargs): + def _qmake_detect(self, mods, kwargs): for qmake in ('qmake-' + self.name, 'qmake'): - self.qmake = self._find_qmake(qmake, env) + self.qmake = self._find_qmake(qmake) if not self.qmake.found(): continue # Check that the qmake is for qt5 @@ -293,6 +266,7 @@ class QtBaseDependency(Dependency): break else: # Didn't find qmake :( + self.is_found = False return self.version = re.search(self.qtver + '(\.\d+)+', stdo).group(0) # Query library path, header path, and binary path @@ -308,15 +282,15 @@ class QtBaseDependency(Dependency): if mesonlib.is_osx(): return self._framework_detect(qvars, mods, kwargs) incdir = qvars['QT_INSTALL_HEADERS'] - self.cargs.append('-I' + incdir) + self.compile_args.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): + self.compile_args.append('-I' + mincdir) + if for_windows(self.env.is_cross_build(), self.env): libfile = os.path.join(libdir, self.qtpkgname + module + '.lib') if not os.path.isfile(libfile): # MinGW can link directly to .dll @@ -329,7 +303,7 @@ class QtBaseDependency(Dependency): if not os.path.isfile(libfile): self.is_found = False break - self.largs.append(libfile) + self.link_args.append(libfile) return qmake def _framework_detect(self, qvars, modules, kwargs): @@ -340,8 +314,8 @@ class QtBaseDependency(Dependency): self.cargs.append('-F' + libdir) if fwdep.found(): self.is_found = True - self.cargs += fwdep.get_compile_args() - self.largs += fwdep.get_link_args() + self.compile_args += fwdep.get_compile_args() + self.link_args += fwdep.get_link_args() # Used by self.compilers_detect() self.bindir = self.get_qmake_host_bins(qvars) @@ -353,24 +327,9 @@ class QtBaseDependency(Dependency): 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 @@ -408,20 +367,18 @@ class Qt5Dependency(QtBaseDependency): # There are three different ways of depending on SDL2: # sdl2-config, pkg-config and OSX framework -class SDL2Dependency(Dependency): +class SDL2Dependency(ExternalDependency): def __init__(self, environment, kwargs): - Dependency.__init__(self, 'sdl2', kwargs) - self.is_found = False - self.cargs = [] - self.linkargs = [] + super().__init__('sdl2', environment, None, kwargs) if DependencyMethods.PKGCONFIG in self.methods: try: + kwargs['required'] = False 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.compile_args = pcdep.get_compile_args() + self.link_args = pcdep.get_link_args() self.version = pcdep.get_version() return except Exception as e: @@ -431,9 +388,9 @@ class SDL2Dependency(Dependency): sdlconf = shutil.which('sdl2-config') if sdlconf: stdo = Popen_safe(['sdl2-config', '--cflags'])[1] - self.cargs = stdo.strip().split() + self.compile_args = stdo.strip().split() stdo = Popen_safe(['sdl2-config', '--libs'])[1] - self.linkargs = stdo.strip().split() + self.link_args = stdo.strip().split() stdo = Popen_safe(['sdl2-config', '--version'])[1] self.version = stdo.strip() self.is_found = True @@ -443,27 +400,15 @@ class SDL2Dependency(Dependency): 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) + fwdep = ExtraFrameworkDependency('sdl2', False, None, kwargs) if fwdep.found(): self.is_found = True - self.cargs = fwdep.get_compile_args() - self.linkargs = fwdep.get_link_args() + self.compile_args = fwdep.get_compile_args() + self.link_args = 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] @@ -471,14 +416,12 @@ class SDL2Dependency(Dependency): return [DependencyMethods.PKGCONFIG, DependencyMethods.SDLCONFIG] -class WxDependency(Dependency): +class WxDependency(ExternalDependency): 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' + super().__init__('wx', environment, None, kwargs) + self.version = 'none' if WxDependency.wx_found is None: self.check_wxconfig() if not WxDependency.wx_found: @@ -490,15 +433,14 @@ class WxDependency(Dependency): 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() + self.version = out.strip() + # FIXME: Support multiple version reqs like PkgConfigDependency version_req = kwargs.get('version', None) if version_req is not None: - if not version_compare(self.modversion, version_req, strict=True): + if not version_compare(self.version, version_req, strict=True): mlog.log('Wxwidgets version %s does not fullfill requirement %s' % - (self.modversion, version_req)) + (self.version, version_req)) return mlog.log('Dependency wxwidgets found:', mlog.green('YES')) self.is_found = True @@ -509,13 +451,13 @@ class WxDependency(Dependency): # 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() + self.compile_args = 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() + self.link_args = out.split() def get_requested(self, kwargs): modules = 'modules' @@ -529,18 +471,6 @@ class WxDependency(Dependency): 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: @@ -555,6 +485,3 @@ class WxDependency(Dependency): pass WxDependency.wxconfig_found = False mlog.log('Found wx-config:', mlog.red('NO')) - - def found(self): - return self.is_found diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 621047c..afe4bf3 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -973,7 +973,8 @@ class CompilerHolder(InterpreterObject): if required and not linkargs: l = self.compiler.language.capitalize() raise InterpreterException('{} library {!r} not found'.format(l, libname)) - lib = dependencies.ExternalLibrary(libname, linkargs, self.compiler.language) + lib = dependencies.ExternalLibrary(libname, linkargs, self.environment, + self.compiler.language) return ExternalLibraryHolder(lib) def has_argument_method(self, args, kwargs): diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 6644ba7..e134acf 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -68,7 +68,7 @@ class GnomeModule(ExtensionModule): if native_glib_version is None: glib_dep = PkgConfigDependency('glib-2.0', state.environment, {'native': True}) - native_glib_version = glib_dep.get_modversion() + native_glib_version = glib_dep.get_version() return native_glib_version def __print_gresources_warning(self, state): diff --git a/run_tests.py b/run_tests.py index 00c2595..1e70784 100755 --- a/run_tests.py +++ b/run_tests.py @@ -121,6 +121,8 @@ def should_run_linux_cross_tests(): class FakeEnvironment(object): def __init__(self): self.cross_info = None + self.coredata = lambda: None + self.coredata.compilers = {} def is_cross_build(self): return False diff --git a/test cases/linuxlike/5 dependency versions/meson.build b/test cases/linuxlike/5 dependency versions/meson.build index 5c2c262..ad513f2 100644 --- a/test cases/linuxlike/5 dependency versions/meson.build +++ b/test cases/linuxlike/5 dependency versions/meson.build @@ -90,9 +90,15 @@ if meson.is_cross_build() assert(native_prefix != cross_prefix, 'native prefix == cross_prefix == ' + native_prefix) endif +objc_found = add_languages('objc', required : false) + foreach d : ['sdl2', 'gnustep', 'wx', 'gl', 'python3', 'boost', 'gtest', 'gmock'] - dep = dependency(d, required : false) - if dep.found() - dep.version() + if d == 'gnustep' and not objc_found + message('Skipping gnustep because no ObjC compiler found') + else + dep = dependency(d, required : false) + if dep.found() + dep.version() + endif endif endforeach diff --git a/test cases/osx/4 framework/meson.build b/test cases/osx/4 framework/meson.build index 8d93bf9..460b480 100644 --- a/test cases/osx/4 framework/meson.build +++ b/test cases/osx/4 framework/meson.build @@ -10,8 +10,13 @@ project('xcode framework test', 'c', default_options : ['libdir=libtest']) -dep_libs = [dependency('appleframeworks', modules : ['OpenGL'], required : true)] -dep_main = [dependency('appleframeworks', modules : ['Foundation'], required : true)] +dep_libs = dependency('appleframeworks', modules : ['OpenGL'], required : false) +if not dep_libs.found() + error('OpenGL framework not found') +endif +assert(dep_libs.type_name() == 'appleframeworks', 'type_name is wrong') + +dep_main = dependency('appleframeworks', modules : ['Foundation']) stlib = static_library('stat', 'stat.c', install : true, dependencies: dep_libs) exe = executable('prog', 'prog.c', install : true, dependencies: dep_main) -- cgit v1.1 From 0b08d5aab5f904ad584a3a215997f3b74d5aecea Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 03:36:34 +0530 Subject: Add a new test for SDL2, which didn't have one Currently optional on all platforms. --- test cases/frameworks/16 sdl2/meson.build | 9 +++++++++ test cases/frameworks/16 sdl2/sdl2prog.c | 33 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test cases/frameworks/16 sdl2/meson.build create mode 100644 test cases/frameworks/16 sdl2/sdl2prog.c diff --git a/test cases/frameworks/16 sdl2/meson.build b/test cases/frameworks/16 sdl2/meson.build new file mode 100644 index 0000000..5a33f2d --- /dev/null +++ b/test cases/frameworks/16 sdl2/meson.build @@ -0,0 +1,9 @@ +project('sdl2 test', 'c') + +sdl2_dep = dependency('sdl2', version : '>=2.0.0', required : false) + +if sdl2_dep.found() + e = executable('sdl2prog', 'sdl2prog.c', dependencies : sdl2_dep) + + test('sdl2test', e) +endif diff --git a/test cases/frameworks/16 sdl2/sdl2prog.c b/test cases/frameworks/16 sdl2/sdl2prog.c new file mode 100644 index 0000000..b67aab4 --- /dev/null +++ b/test cases/frameworks/16 sdl2/sdl2prog.c @@ -0,0 +1,33 @@ +/* vim: set sts=4 sw=4 et : */ + +#include +#include + +int main(int argc, char *argv[]) { + SDL_version compiled; + SDL_version linked; + + SDL_VERSION(&compiled); + SDL_GetVersion(&linked); + + if (compiled.major != linked.major) { + fprintf(stderr, "Compiled major '%u' != linked major '%u'", + compiled.major, linked.major); + return -1; + } + + if (compiled.minor != linked.minor) { + fprintf(stderr, "Compiled minor '%u' != linked minor '%u'", + compiled.minor, linked.minor); + return -2; + } +#if 0 + /* Disabled because sometimes this is 'micro' and sometimes 'patch' */ + if (compiled.micro != linked.micro) { + fprintf(stderr, "Compiled micro '%u' != linked micro '%u'", + compiled.micro, linked.micro); + return -3; + } +#endif + return 0; +} -- cgit v1.1 From c4d7667675760d43596853f7bac9db38b82e798b Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 05:15:53 +0530 Subject: ExtraFrameworkDependency: Don't set required Otherwise we will never hit the informative DependencyException. --- mesonbuild/dependencies/base.py | 4 ++-- mesonbuild/dependencies/ui.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index d727021..00a14be 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -139,7 +139,7 @@ class ExternalDependency(Dependency): self.static = kwargs.get('static', False) if not isinstance(self.static, bool): raise DependencyException('Static keyword must be boolean') - # Is this dependency for cross-com,pilation? + # Is this dependency for cross-compilation? if 'native' in kwargs and self.env.is_cross_build(): self.want_cross = not kwargs['native'] else: @@ -619,7 +619,7 @@ def find_external_dependency(name, env, kwargs): except Exception as e: pkg_exc = e if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency(name, required, None, env, kwargs) + 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) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 7b276cc..7a66a2d 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -310,7 +310,8 @@ class QtBaseDependency(ExternalDependency): libdir = qvars['QT_INSTALL_LIBS'] for m in modules: fname = 'Qt' + m - fwdep = ExtraFrameworkDependency(fname, kwargs.get('required', True), libdir, kwargs) + fwdep = ExtraFrameworkDependency(fname, False, libdir, self.env, + self.language, kwargs) self.cargs.append('-F' + libdir) if fwdep.found(): self.is_found = True @@ -400,7 +401,8 @@ class SDL2Dependency(ExternalDependency): mlog.debug('Could not find sdl2-config binary, trying next.') if DependencyMethods.EXTRAFRAMEWORK in self.methods: if mesonlib.is_osx(): - fwdep = ExtraFrameworkDependency('sdl2', False, None, kwargs) + fwdep = ExtraFrameworkDependency('sdl2', False, None, self.env, + self.language, kwargs) if fwdep.found(): self.is_found = True self.compile_args = fwdep.get_compile_args() -- cgit v1.1 From 627190faf39781dba197fab0849adff208a5bb96 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 29 May 2017 20:35:49 +0530 Subject: Add an env var to force meson to print a backtrace This is really useful when debugging test failures. Without a stack trace, you have to grep the source code for the error message. Also set this in run_tests.py. --- mesonbuild/mesonmain.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mesonbuild/mesonmain.py b/mesonbuild/mesonmain.py index dacc478..603be18 100644 --- a/mesonbuild/mesonmain.py +++ b/mesonbuild/mesonmain.py @@ -312,7 +312,11 @@ def run(mainfile, args): else: mlog.log(mlog.red('\nMeson encountered an error:')) mlog.log(e) + if os.environ.get('MESON_FORCE_BACKTRACE'): + raise else: + if os.environ.get('MESON_FORCE_BACKTRACE'): + raise traceback.print_exc() return 1 return 0 -- cgit v1.1 From ed6a5abee853ed5e4d8c1826b17a0dc29843cd6a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 12:58:32 +0530 Subject: find_library: link_args is always a list --- mesonbuild/compilers.py | 2 +- mesonbuild/dependencies/base.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index c9cfb46..977b7c4 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -1773,7 +1773,7 @@ class ValaCompiler(Compiler): for d in extra_dirs: vapi = os.path.join(d, libname + '.vapi') if os.path.isfile(vapi): - return vapi + return [vapi] mlog.debug('Searched {!r} and {!r} wasn\'t found'.format(extra_dirs, libname)) return None diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 00a14be..6fbf8ba 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -510,8 +510,6 @@ class ExternalLibrary(ExternalDependency): self.is_found = False if link_args: self.is_found = True - if not isinstance(link_args, list): - link_args = [link_args] self.link_args = link_args if not silent: if self.is_found: -- cgit v1.1 From 3a33a8ef49227c2fce1c0c7143e5529b4208d04e Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 13:00:20 +0530 Subject: unit tests: Add class to generate failing tests It is not feasible to test all failure modes by creating projects in `test cases/failing` that would be an explosion of files, and that mechanism is too coarse anyway. We have no way to ensure that the expected error is being raised. See FailureTests.test_dependency for an example. --- mesonbuild/dependencies/base.py | 7 +++- run_project_tests.py | 14 +------ run_tests.py | 16 +++++++- run_unittests.py | 83 +++++++++++++++++++++++++++++++++++------ 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 6fbf8ba..14ec41e 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -59,7 +59,10 @@ class Dependency: self.compile_args = [] self.link_args = [] self.sources = [] - method = DependencyMethods(kwargs.get('method', 'auto')) + method = kwargs.get('method', 'auto') + if method not in [e.value for e in DependencyMethods]: + raise DependencyException('method {!r} is invalid'.format(method)) + method = DependencyMethods(method) # 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. @@ -68,7 +71,7 @@ class Dependency: elif method in self.get_methods(): self.methods = [method] else: - raise MesonException( + raise DependencyException( 'Unsupported detection method: {}, allowed methods are {}'.format( method.value, mlog.format_list(map(lambda x: x.value, [DependencyMethods.AUTO] + self.get_methods())))) diff --git a/run_project_tests.py b/run_project_tests.py index 822286b..5a88fa4 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -34,7 +34,7 @@ import time import multiprocessing import concurrent.futures as conc import re -from run_unittests import get_fake_options +from run_unittests import get_fake_options, run_configure_inprocess from run_tests import get_backend_commands, get_backend_args_for_dir, Backend from run_tests import ensure_backend_detects_changes @@ -249,18 +249,6 @@ def log_text_file(logfile, testdir, stdo, stde): executor.shutdown() raise StopException() -def run_configure_inprocess(commandlist): - old_stdout = sys.stdout - sys.stdout = mystdout = StringIO() - old_stderr = sys.stderr - sys.stderr = mystderr = StringIO() - try: - returncode = mesonmain.run(commandlist[0], commandlist[1:]) - finally: - sys.stdout = old_stdout - sys.stderr = old_stderr - return returncode, mystdout.getvalue(), mystderr.getvalue() - def run_test_inprocess(testdir): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() diff --git a/run_tests.py b/run_tests.py index 1e70784..1549979 100755 --- a/run_tests.py +++ b/run_tests.py @@ -22,7 +22,9 @@ import subprocess import tempfile import platform from mesonbuild import mesonlib +from mesonbuild import mesonmain from mesonbuild.environment import detect_ninja +from io import StringIO from enum import Enum from glob import glob @@ -118,6 +120,18 @@ def get_fake_options(prefix): def should_run_linux_cross_tests(): return shutil.which('arm-linux-gnueabihf-gcc-6') and not platform.machine().startswith('arm') +def run_configure_inprocess(commandlist): + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + old_stderr = sys.stderr + sys.stderr = mystderr = StringIO() + try: + returncode = mesonmain.run(commandlist[0], commandlist[1:]) + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + return returncode, mystdout.getvalue(), mystderr.getvalue() + class FakeEnvironment(object): def __init__(self): self.cross_info = None @@ -164,7 +178,7 @@ if __name__ == '__main__': os.environ.pop('platform') # Run tests print('Running unittests.\n') - units = ['InternalTests', 'AllPlatformTests'] + units = ['InternalTests', 'AllPlatformTests', 'FailureTests'] if mesonlib.is_linux(): units += ['LinuxlikeTests'] if should_run_linux_cross_tests(): diff --git a/run_unittests.py b/run_unittests.py index 8a9ac0a..b03534d 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -25,16 +25,19 @@ import unittest from configparser import ConfigParser from glob import glob from pathlib import PurePath + +import mesonbuild.mlog import mesonbuild.compilers import mesonbuild.environment import mesonbuild.mesonlib from mesonbuild.mesonlib import is_windows, is_osx, is_cygwin from mesonbuild.environment import Environment +from mesonbuild.dependencies import DependencyException from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram from run_tests import exe_suffix, get_fake_options, FakeEnvironment from run_tests import get_builddir_target_args, get_backend_commands, Backend -from run_tests import ensure_backend_detects_changes +from run_tests import ensure_backend_detects_changes, run_configure_inprocess def get_dynamic_section_entry(fname, entry): @@ -401,8 +404,8 @@ class BasePlatformTests(unittest.TestCase): # Get the backend # FIXME: Extract this from argv? self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) - self.meson_command = [sys.executable, os.path.join(src_root, 'meson.py'), - '--backend=' + self.backend.name] + self.meson_args = [os.path.join(src_root, 'meson.py'), '--backend=' + self.backend.name] + self.meson_command = [sys.executable] + self.meson_args self.mconf_command = [sys.executable, os.path.join(src_root, 'mesonconf.py')] self.mintro_command = [sys.executable, os.path.join(src_root, 'mesonintrospect.py')] self.mtest_command = [sys.executable, os.path.join(src_root, 'mesontest.py'), '-C', self.builddir] @@ -452,7 +455,7 @@ class BasePlatformTests(unittest.TestCase): raise subprocess.CalledProcessError(p.returncode, command) return output - def init(self, srcdir, extra_args=None, default_args=True): + def init(self, srcdir, extra_args=None, default_args=True, inprocess=False): self.assertTrue(os.path.exists(srcdir)) if extra_args is None: extra_args = [] @@ -462,14 +465,26 @@ class BasePlatformTests(unittest.TestCase): if default_args: args += ['--prefix', self.prefix, '--libdir', self.libdir] - try: - self._run(self.meson_command + args + extra_args) - except unittest.SkipTest: - raise unittest.SkipTest('Project requested skipping: ' + srcdir) - except: - self._print_meson_log() - raise self.privatedir = os.path.join(self.builddir, 'meson-private') + if inprocess: + try: + run_configure_inprocess(self.meson_args + args + extra_args) + except: + self._print_meson_log() + raise + finally: + # Close log file to satisfy Windows file locking + mesonbuild.mlog.shutdown() + mesonbuild.mlog.log_dir = None + mesonbuild.mlog.log_file = None + else: + try: + self._run(self.meson_command + args + extra_args) + except unittest.SkipTest: + raise unittest.SkipTest('Project requested skipping: ' + srcdir) + except: + self._print_meson_log() + raise def build(self, target=None, extra_args=None): if extra_args is None: @@ -1196,6 +1211,52 @@ int main(int argc, char **argv) { self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) +class FailureTests(BasePlatformTests): + ''' + Tests that test failure conditions. Build files here should be dynamically + generated and static tests should go into `test cases/failing*`. + This is useful because there can be many ways in which a particular + function can fail, and creating failing tests for all of them is tedious + and slows down testing. + ''' + dnf = "[Dd]ependency.*not found" + + def setUp(self): + super().setUp() + self.srcdir = os.path.realpath(tempfile.mkdtemp()) + self.mbuild = os.path.join(self.srcdir, 'meson.build') + + def tearDown(self): + super().tearDown() + shutil.rmtree(self.srcdir) + + def assertMesonRaises(self, contents, match, extra_args=None): + ''' + Assert that running meson configure on the specified contents raises + the specified error message. + ''' + with open(self.mbuild, 'w') as f: + f.write("project('failure test', 'c', 'cpp')\n") + f.write(contents) + # Force tracebacks so we can detect them properly + os.environ['MESON_FORCE_BACKTRACE'] = '1' + with self.assertRaisesRegex(DependencyException, match, msg=contents): + # Must run in-process or we'll get a generic CalledProcessError + self.init(self.srcdir, extra_args=extra_args, inprocess=True) + + def test_dependency(self): + if not shutil.which('pkg-config'): + raise unittest.SkipTest('pkg-config not found') + a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), + ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), + ("dependency('zlib', version : 1)", "[Vv]ersion.*string or list"), + ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), + ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), + ("dependency('zlibfail')", self.dnf),) + for contents, match in a: + self.assertMesonRaises(contents, match) + + class WindowsTests(BasePlatformTests): ''' Tests that should run on Cygwin, MinGW, and MSVC -- cgit v1.1 From 38716f0fcb03c73ae1279036913e5b0ac41528a6 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 12:59:34 +0530 Subject: tests: Improve llvm dependency test coverage --- mesonbuild/dependencies/dev.py | 2 ++ run_unittests.py | 4 ++++ test cases/frameworks/15 llvm/meson.build | 7 +++++++ 3 files changed, 13 insertions(+) diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 8d64379..d7a7181 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -202,6 +202,8 @@ class LLVMDependency(ExternalDependency): out = out.strip() if p.returncode != 0: continue + # FIXME: As soon as some llvm-config is found, version checks + # in further dependnecy() calls will be ignored 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): diff --git a/run_unittests.py b/run_unittests.py index b03534d..96aefc5 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1256,6 +1256,10 @@ class FailureTests(BasePlatformTests): for contents, match in a: self.assertMesonRaises(contents, match) + def test_llvm_dependency(self): + self.assertMesonRaises("dependency('llvm', modules : 'fail')", + "(required.*fail|{})".format(self.dnf)) + class WindowsTests(BasePlatformTests): ''' diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build index 582ff37..af7f8c6 100644 --- a/test cases/frameworks/15 llvm/meson.build +++ b/test cases/frameworks/15 llvm/meson.build @@ -7,4 +7,11 @@ llvm_dep = dependency( required : true, ) +d = dependency('llvm', modules : 'not-found', required : false) +assert(d.found() == false, 'not-found llvm module found') + +# XXX: Version checks are broken, see FIXME in LLVMDependency +#d = dependency('llvm', version : '<0.1', required : false) +#assert(d.found() == false, 'ancient llvm module found') + executable('sum', 'sum.c', dependencies : llvm_dep) -- cgit v1.1 From 130076642974e62bdda3a6bf97317323cc8331f7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 19:14:58 +0530 Subject: vs: Always check VSINSTALLDIR in case VisualStudioVersion is unset This happened on the CI, so it could happen on people's machines too: https://ci.appveyor.com/project/jpakkane/meson/build/2870/job/p2n70hg01vp3dkgl https://ci.appveyor.com/project/jpakkane/meson/build/2870/job/7ifh64mi1999guxt --- mesonbuild/backend/vs2010backend.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index fafde8f..d4a7a19 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -29,26 +29,24 @@ from ..environment import Environment def autodetect_vs_version(build): vs_version = os.getenv('VisualStudioVersion', None) - if vs_version: - if vs_version == '14.0': - from mesonbuild.backend.vs2015backend import Vs2015Backend - return Vs2015Backend(build) - if vs_version == '15.0': - from mesonbuild.backend.vs2017backend import Vs2017Backend - return Vs2017Backend(build) - raise MesonException('Could not detect Visual Studio (unknown Visual Studio version: "{}")!\n' - 'Please specify the exact backend to use.'.format(vs_version)) - vs_install_dir = os.getenv('VSINSTALLDIR', None) - if not vs_install_dir: - raise MesonException('Could not detect Visual Studio (neither VisualStudioVersion nor VSINSTALLDIR set in ' - 'environment)!\nPlease specify the exact backend to use.') - + if not vs_version and not vs_install_dir: + raise MesonException('Could not detect Visual Studio: VisualStudioVersion and VSINSTALLDIR are unset!\n' + 'Are we inside a Visual Studio build environment? ' + 'You can also try specifying the exact backend to use.') + # VisualStudioVersion is set since Visual Studio 12.0, but sometimes + # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR + if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir: + from mesonbuild.backend.vs2015backend import Vs2015Backend + return Vs2015Backend(build) + if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \ + 'Visual Studio\\2017' in vs_install_dir: + from mesonbuild.backend.vs2017backend import Vs2017Backend + return Vs2017Backend(build) if 'Visual Studio 10.0' in vs_install_dir: return Vs2010Backend(build) - - raise MesonException('Could not detect Visual Studio (unknown VSINSTALLDIR: "{}")!\n' - 'Please specify the exact backend to use.'.format(vs_install_dir)) + raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n' + 'Please specify the exact backend to use.'.format(vs_version, vs_install_dir)) def split_o_flags_args(args): """ -- cgit v1.1 From b6b3905325884c5fd2a502d09680b5a35564f0df Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 20:25:27 +0530 Subject: vs: Sometimes WindowsSDKVersion is unset https://ci.appveyor.com/project/jpakkane/meson/build/2871/job/ti4qpoptd5tk19sn --- mesonbuild/backend/vs2017backend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py index 35d56f3..fe1d7c7 100644 --- a/mesonbuild/backend/vs2017backend.py +++ b/mesonbuild/backend/vs2017backend.py @@ -24,4 +24,6 @@ class Vs2017Backend(Vs2010Backend): self.platform_toolset = 'v141' self.vs_version = '2017' # WindowsSDKVersion should be set by command prompt. - self.windows_target_platform_version = os.getenv('WindowsSDKVersion', None).rstrip('\\') + sdk_version = os.environ.get('WindowsSDKVersion', None) + if sdk_version: + self.windows_target_platform_version = sdk_version.rstrip('\\') -- cgit v1.1 From c1e9c757ebbe7a26b126ee8cb2106f6f8dffa322 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Fri, 9 Jun 2017 23:23:38 +0530 Subject: tests: Increase dependencies coverage a bit more --- mesonbuild/dependencies/ui.py | 7 ++-- run_project_tests.py | 2 +- run_unittests.py | 66 +++++++++++++++++++++++++++++++--- test cases/frameworks/4 qt/meson.build | 6 ++++ 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index 7a66a2d..8537a7e 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -427,7 +427,6 @@ class WxDependency(ExternalDependency): 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 @@ -466,11 +465,11 @@ class WxDependency(ExternalDependency): if modules not in kwargs: return [] candidates = kwargs[modules] - if isinstance(candidates, str): - return [candidates] + if not isinstance(candidates, list): + candidates = [candidates] for c in candidates: if not isinstance(c, str): - raise DependencyException('wxwidgets module argument is not a string.') + raise DependencyException('wxwidgets module argument is not a string') return candidates def check_wxconfig(self): diff --git a/run_project_tests.py b/run_project_tests.py index 5a88fa4..71d6c0c 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -434,7 +434,7 @@ def detect_tests_to_run(): ('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or mesonlib.is_windows() or not have_objc_compiler()), ('fortran', 'fortran', backend is not Backend.ninja or not shutil.which('gfortran')), ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), - ('python3', 'python3', backend is not Backend.ninja or not shutil.which('python3')), + ('python3', 'python3', backend is not Backend.ninja), ] return [(name, gather_tests('test cases/' + subdir), skip) for name, subdir, skip in all_tests] diff --git a/run_unittests.py b/run_unittests.py index 96aefc5..bcb55a2 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -468,7 +468,7 @@ class BasePlatformTests(unittest.TestCase): self.privatedir = os.path.join(self.builddir, 'meson-private') if inprocess: try: - run_configure_inprocess(self.meson_args + args + extra_args) + out = run_configure_inprocess(self.meson_args + args + extra_args)[1] except: self._print_meson_log() raise @@ -479,12 +479,13 @@ class BasePlatformTests(unittest.TestCase): mesonbuild.mlog.log_file = None else: try: - self._run(self.meson_command + args + extra_args) + out = self._run(self.meson_command + args + extra_args) except unittest.SkipTest: raise unittest.SkipTest('Project requested skipping: ' + srcdir) except: self._print_meson_log() raise + return out def build(self, target=None, extra_args=None): if extra_args is None: @@ -1230,13 +1231,17 @@ class FailureTests(BasePlatformTests): super().tearDown() shutil.rmtree(self.srcdir) - def assertMesonRaises(self, contents, match, extra_args=None): + def assertMesonRaises(self, contents, match, extra_args=None, langs=None): ''' - Assert that running meson configure on the specified contents raises - the specified error message. + Assert that running meson configure on the specified @contents raises + a error message matching regex @match. ''' + if langs is None: + langs = [] with open(self.mbuild, 'w') as f: f.write("project('failure test', 'c', 'cpp')\n") + for lang in langs: + f.write("add_languages('{}', required : false)\n".format(lang)) f.write(contents) # Force tracebacks so we can detect them properly os.environ['MESON_FORCE_BACKTRACE'] = '1' @@ -1244,6 +1249,22 @@ class FailureTests(BasePlatformTests): # Must run in-process or we'll get a generic CalledProcessError self.init(self.srcdir, extra_args=extra_args, inprocess=True) + def assertMesonOutputs(self, contents, match, extra_args=None, langs=None): + ''' + Assert that running meson configure on the specified @contents outputs + something that matches regex @match. + ''' + if langs is None: + langs = [] + with open(self.mbuild, 'w') as f: + f.write("project('output test', 'c', 'cpp')\n") + for lang in langs: + f.write("add_languages('{}', required : false)\n".format(lang)) + f.write(contents) + # Run in-process for speed and consistency with assertMesonRaises + out = self.init(self.srcdir, extra_args=extra_args, inprocess=True) + self.assertRegex(out, match) + def test_dependency(self): if not shutil.which('pkg-config'): raise unittest.SkipTest('pkg-config not found') @@ -1256,6 +1277,41 @@ class FailureTests(BasePlatformTests): for contents, match in a: self.assertMesonRaises(contents, match) + def test_apple_frameworks_dependency(self): + if not is_osx(): + raise unittest.SkipTest('only run on macOS') + self.assertMesonRaises("dependency('appleframeworks')", + "requires at least one module") + + def test_sdl2_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('sdl2-config'): + raise unittest.SkipTest('sdl2-config found') + self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf) + self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf) + + def test_gnustep_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('gnustep-config'): + raise unittest.SkipTest('gnustep-config found') + self.assertMesonRaises("dependency('gnustep')", + "(requires a Objc compiler|{})".format(self.dnf), + langs = ['objc']) + + def test_wx_notfound_dependency(self): + # Want to test failure, so skip if available + if shutil.which('wx-config-3.0') or shutil.which('wx-config'): + raise unittest.SkipTest('wx-config or wx-config-3.0 found') + self.assertMesonRaises("dependency('wxwidgets')", self.dnf) + self.assertMesonOutputs("dependency('wxwidgets', required : false)", + "nor wx-config found") + + def test_wx_dependency(self): + if not shutil.which('wx-config-3.0') and not shutil.which('wx-config'): + raise unittest.SkipTest('Neither wx-config nor wx-config-3.0 found') + self.assertMesonRaises("dependency('wxwidgets', modules : 1)", + "module argument is not a string") + def test_llvm_dependency(self): self.assertMesonRaises("dependency('llvm', modules : 'fail')", "(required.*fail|{})".format(self.dnf)) diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build index 468b9c9..d9cab6f 100644 --- a/test cases/frameworks/4 qt/meson.build +++ b/test cases/frameworks/4 qt/meson.build @@ -8,16 +8,22 @@ foreach qt : ['qt4', 'qt5'] if qt == 'qt5' qt_modules += qt5_modules endif + # Test that invalid modules are indeed not found fakeqtdep = dependency(qt, modules : ['DefinitelyNotFound'], required : false, method : get_option('method')) if fakeqtdep.found() error('Invalid qt dep incorrectly found!') endif + # Test that partially-invalid modules are indeed not found fakeqtdep = dependency(qt, modules : ['Core', 'DefinitelyNotFound'], required : false, method : get_option('method')) if fakeqtdep.found() error('Invalid qt dep incorrectly found!') endif + + # Ensure that the "no-Core-module-specified" code branch is hit + nocoredep = dependency(qt, modules : ['Gui'], required : qt == 'qt5', method : get_option('method')) + # If qt4 modules are found, test that. qt5 is required. qtdep = dependency(qt, modules : qt_modules, required : qt == 'qt5', method : get_option('method')) if qtdep.found() -- cgit v1.1 From 9308a6d923ecdfabb40d88e1590226172ea95e43 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 10 Jun 2017 13:04:00 +0530 Subject: tests: Add Boost unit tests and project tests on Windows Boost tests are disabled on Windows for now because the detection is actually completely broken. Once that's fixed (after the release) we can enable it again. --- .appveyor.yml | 2 ++ mesonbuild/dependencies/misc.py | 7 ++++--- run_project_tests.py | 13 ++++++++++--- run_unittests.py | 13 +++++++++++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 1e20a37..6551445 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -55,6 +55,8 @@ skip_commits: install: - cmd: set "ORIG_PATH=%PATH%" + # Boost 1.56.0: https://www.appveyor.com/docs/build-environment/#boost + #- cmd: set "BOOST_ROOT=C:\Libraries\boost" # Use the x86 python only when building for x86 for the cpython tests. # For all other archs (including, say, arm), use the x64 python. - ps: (new-object net.webclient).DownloadFile('https://www.dropbox.com/s/bbzvepq85hv47x1/ninja.exe?dl=1', 'C:\projects\meson\ninja.exe') diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index 1356ec8..c24acf0 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -126,8 +126,8 @@ class BoostDependency(ExternalDependency): def get_requested(self, kwargs): candidates = kwargs.get('modules', []) - if isinstance(candidates, str): - return [candidates] + if not isinstance(candidates, list): + candidates = [candidates] for c in candidates: if not isinstance(c, str): raise DependencyException('Boost module argument is not a string.') @@ -136,7 +136,8 @@ class BoostDependency(ExternalDependency): 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) + msg = 'Requested Boost module {!r} not found' + raise DependencyException(msg.format(m)) def detect_version(self): try: diff --git a/run_project_tests.py b/run_project_tests.py index 71d6c0c..66d7eb0 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -371,7 +371,7 @@ def _run_test(testdir, test_build_dir, install_dir, extra_args, compiler, backen return TestResult(validate_install(testdir, install_dir, compiler), BuildStep.validate, stdo, stde, mesonlog, gen_time, build_time, test_time) def gather_tests(testdir): - tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(os.path.join(testdir, '*'))] + tests = [t.replace('\\', '/').split('/', 2)[2] for t in glob(testdir + '/*')] testlist = [(int(t.split()[0]), t) for t in tests] testlist.sort() tests = [os.path.join(testdir, t[1]) for t in testlist] @@ -425,7 +425,6 @@ def detect_tests_to_run(): ('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()), ('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()), - ('framework', 'frameworks', mesonlib.is_osx() or mesonlib.is_windows() or mesonlib.is_cygwin()), ('java', 'java', backend is not Backend.ninja or mesonlib.is_osx() or not have_java()), ('C#', 'csharp', backend is not Backend.ninja or not shutil.which('mcs')), ('vala', 'vala', backend is not Backend.ninja or not shutil.which('valac')), @@ -436,7 +435,15 @@ def detect_tests_to_run(): ('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), ('python3', 'python3', backend is not Backend.ninja), ] - return [(name, gather_tests('test cases/' + subdir), skip) for name, subdir, skip in all_tests] + gathered_tests = [(name, gather_tests('test cases/' + subdir), skip) for name, subdir, skip in all_tests] + if mesonlib.is_windows(): + # TODO: Set BOOST_ROOT in .appveyor.yml + gathered_tests += [('framework', ['test cases/frameworks/1 boost'], 'BOOST_ROOT' not in os.environ)] + elif mesonlib.is_osx() or mesonlib.is_cygwin(): + gathered_tests += [('framework', gather_tests('test cases/frameworks'), True)] + else: + gathered_tests += [('framework', gather_tests('test cases/frameworks'), False)] + return gathered_tests def run_tests(all_tests, log_name_base, extra_args): global stop, executor, futures diff --git a/run_unittests.py b/run_unittests.py index bcb55a2..dbfd638 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1316,6 +1316,19 @@ class FailureTests(BasePlatformTests): self.assertMesonRaises("dependency('llvm', modules : 'fail')", "(required.*fail|{})".format(self.dnf)) + def test_boost_notfound_dependency(self): + # Can be run even if Boost is found or not + self.assertMesonRaises("dependency('boost', modules : 1)", + "module.*not a string") + self.assertMesonRaises("dependency('boost', modules : 'fail')", + "(fail.*not found|{})".format(self.dnf)) + + def test_boost_BOOST_ROOT_dependency(self): + # Test BOOST_ROOT; can be run even if Boost is found or not + os.environ['BOOST_ROOT'] = 'relative/path' + self.assertMesonRaises("dependency('boost')", + "(BOOST_ROOT.*absolute|{})".format(self.dnf)) + class WindowsTests(BasePlatformTests): ''' -- cgit v1.1 From 868d85d2e567b0f5cfb49858eb0f2ac96f7d5749 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sat, 10 Jun 2017 19:00:00 +0530 Subject: tests: Make SDL2 compulsory now that it's in the CI image --- test cases/frameworks/16 sdl2/meson.build | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test cases/frameworks/16 sdl2/meson.build b/test cases/frameworks/16 sdl2/meson.build index 5a33f2d..c79bd46 100644 --- a/test cases/frameworks/16 sdl2/meson.build +++ b/test cases/frameworks/16 sdl2/meson.build @@ -1,9 +1,10 @@ project('sdl2 test', 'c') -sdl2_dep = dependency('sdl2', version : '>=2.0.0', required : false) +sdl2_dep = dependency('sdl2', version : '>=2.0.0') -if sdl2_dep.found() - e = executable('sdl2prog', 'sdl2prog.c', dependencies : sdl2_dep) +e = executable('sdl2prog', 'sdl2prog.c', dependencies : sdl2_dep) - test('sdl2test', e) -endif +test('sdl2test', e) + +# Ensure that we can find it with sdl2-config too +configdep = dependency('sdl2', method : 'sdlconfig') -- cgit v1.1