diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2017-06-11 14:49:52 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-11 14:49:52 +0300 |
commit | b8f02047bec9fd2d1a36db82df5fed14ef386cd6 (patch) | |
tree | b4f2e99205f3b3846a086610802ad023227a35ae | |
parent | 22cfd44221ada3219d9096e15dc8b00d32e0f9f6 (diff) | |
parent | 868d85d2e567b0f5cfb49858eb0f2ac96f7d5749 (diff) | |
download | meson-b8f02047bec9fd2d1a36db82df5fed14ef386cd6.zip meson-b8f02047bec9fd2d1a36db82df5fed14ef386cd6.tar.gz meson-b8f02047bec9fd2d1a36db82df5fed14ef386cd6.tar.bz2 |
Merge pull request #1900 from centricular/abstract-extdeps
dependencies: Add a new class ExternalDependency
24 files changed, 496 insertions, 454 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/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/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): """ 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('\\') 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/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/__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..14ec41e 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -52,10 +52,17 @@ 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 - method = DependencyMethods(kwargs.get('method', 'auto')) + self.compile_args = [] + self.link_args = [] + self.sources = [] + 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. @@ -64,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())))) @@ -74,10 +81,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 +92,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 +100,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 +110,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 +125,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-compilation? + 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 +196,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 +207,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 +222,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 +234,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 +260,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 +270,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 +284,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 +298,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 +327,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 +505,37 @@ 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 +560,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 +575,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 +599,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 +607,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, 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) @@ -642,16 +630,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..d7a7181 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.""" @@ -261,6 +202,8 @@ class LLVMDependency(Dependency): 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): @@ -288,8 +231,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..c24acf0 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,13 +121,13 @@ 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): 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.') @@ -145,19 +136,13 @@ class BoostDependency(Dependency): def validate_requested(self): for m in self.requested_modules: if m not in self.src_modules: - raise DependencyException('Requested Boost module "%s" not found.' % m) - - def found(self): - return self.version is not None - - def get_version(self): - return self.version + msg = 'Requested Boost module {!r} not found' + raise DependencyException(msg.format(m)) 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 +150,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 +165,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 +239,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 +271,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 +285,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 +308,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 +344,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 +362,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..8537a7e 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,19 +303,20 @@ 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): 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 - 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 +328,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 +368,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 +389,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 +401,16 @@ 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, self.env, + self.language, 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,18 +418,15 @@ 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: - # 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 @@ -490,15 +434,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,38 +452,26 @@ 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' 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 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 +486,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/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 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_project_tests.py b/run_project_tests.py index 822286b..66d7eb0 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() @@ -383,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] @@ -437,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')), @@ -446,9 +433,17 @@ 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] + 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_tests.py b/run_tests.py index 00c2595..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,9 +120,23 @@ 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 + self.coredata = lambda: None + self.coredata.compilers = {} def is_cross_build(self): return False @@ -162,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..dbfd638 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,27 @@ 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: + out = run_configure_inprocess(self.meson_args + args + extra_args)[1] + 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: + 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: @@ -1196,6 +1212,124 @@ 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, langs=None): + ''' + 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' + 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 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') + 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) + + 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)) + + 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): ''' Tests that should run on Cygwin, MinGW, and MSVC 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) diff --git a/test cases/frameworks/16 sdl2/meson.build b/test cases/frameworks/16 sdl2/meson.build new file mode 100644 index 0000000..c79bd46 --- /dev/null +++ b/test cases/frameworks/16 sdl2/meson.build @@ -0,0 +1,10 @@ +project('sdl2 test', 'c') + +sdl2_dep = dependency('sdl2', version : '>=2.0.0') + +e = executable('sdl2prog', 'sdl2prog.c', dependencies : sdl2_dep) + +test('sdl2test', e) + +# Ensure that we can find it with sdl2-config too +configdep = dependency('sdl2', method : 'sdlconfig') 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 <stdio.h> +#include <SDL_version.h> + +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; +} 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() 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) |