diff options
39 files changed, 743 insertions, 166 deletions
diff --git a/ciimage/Dockerfile b/ciimage/Dockerfile index 520ce0f..980ed53 100644 --- a/ciimage/Dockerfile +++ b/ciimage/Dockerfile @@ -20,6 +20,7 @@ RUN sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" \ && apt-get -y install --no-install-recommends wine-stable \ && apt-get -y install llvm-dev libclang-dev \ && apt-get -y install libgcrypt11-dev \ +&& apt-get -y install libhdf5-dev \ && dub fetch urld && dub build urld --compiler=gdc \ && dub fetch dubtestproject \ && dub build dubtestproject:test1 --compiler=ldc2 \ diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index e3fedc4..259f09e 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -148,6 +148,14 @@ it automatically. cmake_dep = dependency('ZLIB', method : 'cmake', modules : ['ZLIB::ZLIB']) ``` +It is also possible to reuse existing `Find<name>.cmake` files with the +`cmake_module_path` property. Using this property is equivalent to setting the +`CMAKE_MODULE_PATH` variable in CMake. The path(s) given to `cmake_module_path` +should all be relative to the project source directory. Absolute paths +should only be used if the CMake files are not stored in the project itself. + +Additional CMake parameters can be specified with the `cmake_args` property. + ### Some notes on Dub Please understand that meson is only able to find dependencies that @@ -269,6 +277,17 @@ e = executable('testprog', 'test.cc', dependencies : gtest_dep) test('gtest test', e) ``` +## HDF5 +HDF5 is supported for C, C++ and Fortran. Because dependencies are +language-specific, you must specify the requested language using the +`language` keyword argument, i.e., + * `dependency('hdf5', language: 'c')` for the C HDF5 headers and libraries + * `dependency('hdf5', language: 'cpp')` for the C++ HDF5 headers and libraries + * `dependency('hdf5', language: 'fortran')` for the Fortran HDF5 headers and libraries + +Meson uses pkg-config to find HDF5. The standard low-level HDF5 function and the `HL` high-level HDF5 functions are linked for each language. + + ## libwmf *(added 0.44.0)* diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md index 3d8515f..8ae4fde 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -203,3 +203,20 @@ executable(..., dependencies : m_dep) ```meson executable(..., install : true, install_dir : get_option('libexecdir')) ``` + +## Use existing `Find<name>.cmake` files + +Meson can use the CMake `find_package()` ecosystem if CMake is installed. +To find a dependency with custom `Find<name>.cmake`, set the `cmake_module_path` +property to the path in your project where the CMake scripts are stored. + +Example for a `FindCmakeOnlyDep.cmake` in a `cmake` subdirectory: + +```meson +cm_dep = dependency('CmakeOnlyDep', cmake_module_path : 'cmake') +``` + +The `cmake_module_path` property is only needed for custom CMake scripts. System +wide CMake scripts are found automatically. + +More information can be found [here](Dependencies.md#cmake) diff --git a/docs/markdown/snippets/cmake_module_path.md b/docs/markdown/snippets/cmake_module_path.md new file mode 100644 index 0000000..7beb453 --- /dev/null +++ b/docs/markdown/snippets/cmake_module_path.md @@ -0,0 +1,9 @@ +## Added `cmake_module_path` and `cmake_args` to dependency + +The CMake dependency backend can now make use of existing `Find<name>.cmake` +files by setting the `CMAKE_MODULE_PATH` with the new `dependency()` property +`cmake_module_path`. The paths given to `cmake_module_path` should be relative +to the project source directory. + +Furthermore the property `cmake_args` was added to give CMake additional +parameters. diff --git a/docs/markdown/snippets/hdf5.md b/docs/markdown/snippets/hdf5.md new file mode 100644 index 0000000..8ebb4c0 --- /dev/null +++ b/docs/markdown/snippets/hdf5.md @@ -0,0 +1,3 @@ +## HDF5 + +HDF5 support is added via pkg-config. diff --git a/mesonbuild/ast/interpreter.py b/mesonbuild/ast/interpreter.py index 2071432..68c017a 100644 --- a/mesonbuild/ast/interpreter.py +++ b/mesonbuild/ast/interpreter.py @@ -89,6 +89,16 @@ class AstInterpreter(interpreterbase.InterpreterBase): 'set_variable': self.func_do_nothing, 'get_variable': self.func_do_nothing, 'is_variable': self.func_do_nothing, + 'disabler': self.func_do_nothing, + 'gettext': self.func_do_nothing, + 'jar': self.func_do_nothing, + 'warning': self.func_do_nothing, + 'shared_module': self.func_do_nothing, + 'option': self.func_do_nothing, + 'both_libraries': self.func_do_nothing, + 'add_test_setup': self.func_do_nothing, + 'find_library': self.func_do_nothing, + 'subdir_done': self.func_do_nothing, }) def func_do_nothing(self, node, args, kwargs): diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 493fc0d..9b215b2 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2347,15 +2347,14 @@ rule FORTRAN_DEP_HACK%s target_args = self.build_target_link_arguments(linker, target.link_whole_targets) return linker.get_link_whole_for(target_args) if len(target_args) else [] - @staticmethod @lru_cache(maxsize=None) - def guess_library_absolute_path(linker, libname, search_dirs, patterns): + def guess_library_absolute_path(self, linker, libname, search_dirs, patterns): for d in search_dirs: for p in patterns: trial = CCompiler._get_trials_from_pattern(p, d, libname) if not trial: continue - trial = CCompiler._get_file_from_list(trial) + trial = CCompiler._get_file_from_list(self.environment, trial) if not trial: continue # Return the first result diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 783ae64..621aa1a 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -1153,7 +1153,7 @@ class Vs2010Backend(backends.Backend): ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename)) extra_files = target.extra_files - if len(headers) + len(gen_hdrs) + len(extra_files) > 0: + if len(headers) + len(gen_hdrs) + len(extra_files) + len(pch_sources) > 0: inc_hdrs = ET.SubElement(root, 'ItemGroup') for h in headers: relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src)) @@ -1163,6 +1163,9 @@ class Vs2010Backend(backends.Backend): for h in target.extra_files: relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src)) ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath) + for lang in pch_sources: + h = pch_sources[lang][0] + ET.SubElement(inc_hdrs, 'CLInclude', Include=os.path.join(proj_to_src_dir, h)) if len(sources) + len(gen_src) + len(pch_sources) > 0: inc_src = ET.SubElement(root, 'ItemGroup') diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index c0cd0bc..a591183 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -27,6 +27,7 @@ from . import compilers from ..mesonlib import ( EnvironmentException, MesonException, version_compare, Popen_safe, listify, for_windows, for_darwin, for_cygwin, for_haiku, for_openbsd, + darwin_get_object_archs ) from .c_function_attributes import C_FUNC_ATTRIBUTES @@ -59,6 +60,7 @@ class CCompiler(Compiler): library_dirs_cache = {} program_dirs_cache = {} find_library_cache = {} + find_framework_cache = {} internal_libs = gnu_compiler_internal_libs @staticmethod @@ -979,10 +981,28 @@ class CCompiler(Compiler): return [f.as_posix()] @staticmethod - def _get_file_from_list(files: List[str]) -> str: + def _get_file_from_list(env, files: List[str]) -> str: + ''' + We just check whether the library exists. We can't do a link check + because the library might have unresolved symbols that require other + libraries. On macOS we check if the library matches our target + architecture. + ''' + # If not building on macOS for Darwin, do a simple file check + if not env.machines.host.is_darwin() or not env.machines.build.is_darwin(): + for f in files: + if os.path.isfile(f): + return f + # Run `lipo` and check if the library supports the arch we want for f in files: - if os.path.isfile(f): + if not os.path.isfile(f): + continue + archs = darwin_get_object_archs(f) + if archs and env.machines.host.cpu_family in archs: return f + else: + mlog.debug('Rejected {}, supports {} but need {}' + .format(f, archs, env.machines.host.cpu_family)) return None @functools.lru_cache() @@ -1023,10 +1043,7 @@ class CCompiler(Compiler): trial = self._get_trials_from_pattern(p, d, libname) if not trial: continue - # We just check whether the library exists. We can't do a link - # check because the library might have unresolved symbols that - # require other libraries. - trial = self._get_file_from_list(trial) + trial = self._get_file_from_list(env, trial) if not trial: continue return [trial] @@ -1052,6 +1069,66 @@ class CCompiler(Compiler): code = 'int main(int argc, char **argv) { return 0; }' return self.find_library_impl(libname, env, extra_dirs, code, libtype) + def find_framework_paths(self, env): + ''' + These are usually /Library/Frameworks and /System/Library/Frameworks, + unless you select a particular macOS SDK with the -isysroot flag. + You can also add to this by setting -F in CFLAGS. + ''' + if self.id != 'clang': + raise MesonException('Cannot find framework path with non-clang compiler') + # Construct the compiler command-line + commands = self.get_exelist() + ['-v', '-E', '-'] + commands += self.get_always_args() + # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS from the env + commands += env.coredata.get_external_args(self.language) + mlog.debug('Finding framework path by running: ', ' '.join(commands), '\n') + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + _, _, stde = Popen_safe(commands, env=os_env, stdin=subprocess.PIPE) + paths = [] + for line in stde.split('\n'): + if '(framework directory)' not in line: + continue + # line is of the form: + # ` /path/to/framework (framework directory)` + paths.append(line[:-21].strip()) + return paths + + def find_framework_real(self, name, env, extra_dirs, allow_system): + code = 'int main(int argc, char **argv) { return 0; }' + link_args = [] + for d in extra_dirs: + link_args += ['-F' + d] + # We can pass -Z to disable searching in the system frameworks, but + # then we must also pass -L/usr/lib to pick up libSystem.dylib + extra_args = [] if allow_system else ['-Z', '-L/usr/lib'] + link_args += ['-framework', name] + if self.links(code, env, extra_args=(extra_args + link_args)): + return link_args + + def find_framework_impl(self, name, env, extra_dirs, allow_system): + if isinstance(extra_dirs, str): + extra_dirs = [extra_dirs] + key = (tuple(self.exelist), name, tuple(extra_dirs), allow_system) + if key in self.find_framework_cache: + value = self.find_framework_cache[key] + else: + value = self.find_framework_real(name, env, extra_dirs, allow_system) + self.find_framework_cache[key] = value + if value is None: + return None + return value[:] + + def find_framework(self, name, env, extra_dirs, allow_system=True): + ''' + Finds the framework with the specified name, and returns link args for + the same or returns None when the framework is not found. + ''' + if self.id != 'clang': + raise MesonException('Cannot find frameworks with non-clang compiler') + return self.find_framework_impl(name, env, extra_dirs, allow_system) + def thread_flags(self, env): if for_haiku(self.is_cross, env) or for_darwin(self.is_cross, env): return [] diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index a8e8e25..eea1660 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -267,8 +267,8 @@ class FortranCompiler(Compiler): return CCompiler._get_trials_from_pattern(pattern, directory, libname) @staticmethod - def _get_file_from_list(files) -> List[str]: - return CCompiler._get_file_from_list(files) + def _get_file_from_list(env, files: List[str]) -> str: + return CCompiler._get_file_from_list(env, files) class GnuFortranCompiler(GnuCompiler, FortranCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs): diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index e64d57f..5303298 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -49,6 +49,12 @@ class ValaCompiler(Compiler): def get_pic_args(self): return [] + def get_pie_args(self): + return [] + + def get_pie_link_args(self): + return [] + def get_always_args(self): return ['-C'] diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 9850722..b2b9e91 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -234,7 +234,7 @@ def load_configs(filenames): raise MesonException('Cannot find specified native file: ' + f) - config = configparser.SafeConfigParser() + config = configparser.ConfigParser() config.read(gen()) return config diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index afe2a3b..f5034db 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -18,7 +18,7 @@ from .base import ( # noqa: F401 ExternalDependency, NotFoundDependency, ExternalLibrary, ExtraFrameworkDependency, InternalDependency, PkgConfigDependency, CMakeDependency, find_external_dependency, get_dep_identifier, packages, _packages_accept_language) from .dev import GMockDependency, GTestDependency, LLVMDependency, ValgrindDependency -from .misc import (MPIDependency, OpenMPDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency, LibGCryptDependency) +from .misc import (HDF5Dependency, MPIDependency, OpenMPDependency, Python3Dependency, ThreadDependency, PcapDependency, CupsDependency, LibWmfDependency, LibGCryptDependency) from .platform import AppleFrameworks from .ui import GLDependency, GnuStepDependency, Qt4Dependency, Qt5Dependency, SDL2Dependency, WxDependency, VulkanDependency @@ -33,6 +33,7 @@ packages.update({ # From misc: 'boost': BoostDependency, 'mpi': MPIDependency, + 'hdf5': HDF5Dependency, 'openmp': OpenMPDependency, 'python3': Python3Dependency, 'threads': ThreadDependency, @@ -54,6 +55,7 @@ packages.update({ 'vulkan': VulkanDependency, }) _packages_accept_language.update({ + 'hdf5', 'mpi', 'openmp', }) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 4b54005..9da0d7c 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -19,7 +19,6 @@ import copy import functools import os import re -import stat import json import shlex import shutil @@ -27,15 +26,17 @@ import textwrap import platform import itertools import ctypes +from typing import List from enum import Enum -from pathlib import PurePath +from pathlib import Path, PurePath from .. import mlog from .. import mesonlib from ..compilers import clib_langs -from ..environment import BinaryTable +from ..environment import BinaryTable, Environment from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify +from ..mesonlib import Version # These must be defined in this file to avoid cyclical references. packages = {} @@ -514,7 +515,8 @@ class PkgConfigDependency(ExternalDependency): # Lookup in cross or machine file. potential_pkgpath = environment.binaries[for_machine].lookup_entry('pkgconfig') if potential_pkgpath is not None: - mlog.debug('Pkg-config binary for %s specified from cross file, native file, or env var as %s.', for_machine, potential_pkgpath) + mlog.debug('Pkg-config binary for {} specified from cross file, native file, ' + 'or env var as {}'.format(for_machine, potential_pkgpath)) yield ExternalProgram.from_entry('pkgconfig', potential_pkgpath) # We never fallback if the user-specified option is no good, so # stop returning options. @@ -530,16 +532,15 @@ class PkgConfigDependency(ExternalDependency): # Only search for pkg-config for each machine the first time and store # the result in the class definition if PkgConfigDependency.class_pkgbin[for_machine] is False: - mlog.debug('Pkg-config binary for %s is cached missing.' % for_machine) + mlog.debug('Pkg-config binary for %s is cached as not found.' % for_machine) elif PkgConfigDependency.class_pkgbin[for_machine] is not None: mlog.debug('Pkg-config binary for %s is cached.' % for_machine) else: assert PkgConfigDependency.class_pkgbin[for_machine] is None mlog.debug('Pkg-config binary for %s is not cached.' % for_machine) for potential_pkgbin in search(): - mlog.debug( - 'Trying pkg-config binary %s for machine %s at %s.', - potential_pkgbin.name, for_machine, potential_pkgbin.command) + mlog.debug('Trying pkg-config binary {} for machine {} at {}' + .format(potential_pkgbin.name, for_machine, potential_pkgbin.command)) version_if_ok = self.check_pkgconfig(potential_pkgbin) if not version_if_ok: continue @@ -558,11 +559,12 @@ class PkgConfigDependency(ExternalDependency): self.pkgbin = PkgConfigDependency.class_pkgbin[for_machine] if self.pkgbin is False: self.pkgbin = None - msg = 'No pkg-config binary for machine %s not found. Giving up.' % for_machine + msg = 'Pkg-config binary for machine %s not found. Giving up.' % for_machine if self.required: raise DependencyException(msg) else: mlog.debug(msg) + return mlog.debug('Determining dependency {!r} with pkg-config executable ' '{!r}'.format(name, self.pkgbin.get_path())) @@ -792,10 +794,10 @@ class PkgConfigDependency(ExternalDependency): if 'define_variable' in kwargs: definition = kwargs.get('define_variable', []) if not isinstance(definition, list): - raise MesonException('define_variable takes a list') + raise DependencyException('define_variable takes a list') if len(definition) != 2 or not all(isinstance(i, str) for i in definition): - raise MesonException('define_variable must be made up of 2 strings for VARIABLENAME and VARIABLEVALUE') + raise DependencyException('define_variable must be made up of 2 strings for VARIABLENAME and VARIABLEVALUE') options = ['--define-variable=' + '='.join(definition)] + options @@ -827,8 +829,7 @@ class PkgConfigDependency(ExternalDependency): def check_pkgconfig(self, pkgbin): if not pkgbin.found(): - mlog.log('Did not find anything at {!r}' - ''.format(' '.join(pkgbin.get_command()))) + mlog.log('Did not find pkg-config by name {!r}'.format(pkgbin.name)) return None try: p, out = Popen_safe(pkgbin.get_command() + ['--version'])[0:2] @@ -925,7 +926,7 @@ class CMakeDependency(ExternalDependency): def _gen_exception(self, msg): return DependencyException('Dependency {} not found: {}'.format(self.name, msg)) - def __init__(self, name, environment, kwargs, language=None): + def __init__(self, name: str, environment: Environment, kwargs, language=None): super().__init__('cmake', environment, language, kwargs) self.name = name self.is_libtool = False @@ -972,16 +973,15 @@ class CMakeDependency(ExternalDependency): # Only search for CMake the first time and store the result in the class # definition if CMakeDependency.class_cmakebin[for_machine] is False: - mlog.debug('CMake binary for %s is cached missing.' % for_machine) + mlog.debug('CMake binary for %s is cached as not found' % for_machine) elif CMakeDependency.class_cmakebin[for_machine] is not None: mlog.debug('CMake binary for %s is cached.' % for_machine) else: assert CMakeDependency.class_cmakebin[for_machine] is None - mlog.debug('CMake binary for %s is not cached.', for_machine) + mlog.debug('CMake binary for %s is not cached' % for_machine) for potential_cmakebin in search(): - mlog.debug( - 'Trying CMake binary %s for machine %s at %s.', - potential_cmakebin.name, for_machine, potential_cmakebin.command) + mlog.debug('Trying CMake binary {} for machine {} at {}' + .format(potential_cmakebin.name, for_machine, potential_cmakebin.command)) version_if_ok = self.check_cmake(potential_cmakebin) if not version_if_ok: continue @@ -1007,18 +1007,28 @@ class CMakeDependency(ExternalDependency): if self.required: raise DependencyException(msg) mlog.debug(msg) + return modules = kwargs.get('modules', []) + cm_path = kwargs.get('cmake_module_path', []) + cm_args = kwargs.get('cmake_args', []) if not isinstance(modules, list): modules = [modules] - self._detect_dep(name, modules) + if not isinstance(cm_path, list): + cm_path = [cm_path] + if not isinstance(cm_args, list): + cm_args = [cm_args] + cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path] + if cm_path: + cm_args += ['-DCMAKE_MODULE_PATH={}'.format(';'.join(cm_path))] + self._detect_dep(name, modules, cm_args) def __repr__(self): s = '<{0} {1}: {2} {3}>' return s.format(self.__class__.__name__, self.name, self.is_found, self.version_reqs) - def _detect_dep(self, name, modules): + def _detect_dep(self, name: str, modules: List[str], args: List[str]): # Detect a dependency with CMake using the '--find-package' mode # and the trace output (stderr) # @@ -1034,7 +1044,7 @@ class CMakeDependency(ExternalDependency): mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto')) # Prepare options - cmake_opts = ['--trace-expand', '-DNAME={}'.format(name), '.'] + cmake_opts = ['--trace-expand', '-DNAME={}'.format(name)] + args + ['.'] if len(i) > 0: cmake_opts = ['-G', i] + cmake_opts @@ -1485,8 +1495,7 @@ set(CMAKE_SIZEOF_VOID_P "{}") def check_cmake(self, cmakebin): if not cmakebin.found(): - mlog.log('Did not find CMake {!r}' - ''.format(' '.join(cmakebin.get_command()))) + mlog.log('Did not find CMake {!r}'.format(cmakebin.name)) return None try: p, out = Popen_safe(cmakebin.get_command() + ['--version'])[0:2] @@ -1984,40 +1993,91 @@ class ExternalLibrary(ExternalDependency): class ExtraFrameworkDependency(ExternalDependency): - def __init__(self, name, required, path, env, lang, kwargs): + system_framework_paths = None + + def __init__(self, name, required, paths, env, lang, kwargs): super().__init__('extraframeworks', env, lang, kwargs) self.name = name self.required = required - self.detect(name, path) - if self.found(): - self.compile_args = ['-I' + os.path.join(self.path, self.name, 'Headers')] - self.link_args = ['-F' + self.path, '-framework', self.name.split('.')[0]] - - def detect(self, name, path): - # should use the compiler to look for frameworks, rather than peering at - # the filesystem, so we can also find them when cross-compiling - if self.want_cross: + # Full path to framework directory + self.framework_path = None + if not self.clib_compiler: + raise DependencyException('No C-like compilers are available') + if self.system_framework_paths is None: + self.system_framework_paths = self.clib_compiler.find_framework_paths(self.env) + self.detect(name, paths) + + def detect(self, name, paths): + if not paths: + paths = self.system_framework_paths + for p in paths: + mlog.debug('Looking for framework {} in {}'.format(name, p)) + # We need to know the exact framework path because it's used by the + # Qt5 dependency class, and for setting the include path. We also + # want to avoid searching in an invalid framework path which wastes + # time and can cause a false positive. + framework_path = self._get_framework_path(p, name) + if framework_path is None: + continue + # We want to prefer the specified paths (in order) over the system + # paths since these are "extra" frameworks. + # For example, Python2's framework is in /System/Library/Frameworks and + # Python3's framework is in /Library/Frameworks, but both are called + # Python.framework. We need to know for sure that the framework was + # found in the path we expect. + allow_system = p in self.system_framework_paths + args = self.clib_compiler.find_framework(name, self.env, [p], allow_system) + if args is None: + continue + self.link_args = args + self.framework_path = framework_path.as_posix() + self.compile_args = ['-F' + self.framework_path] + # We need to also add -I includes to the framework because all + # cross-platform projects such as OpenGL, Python, Qt, GStreamer, + # etc do not use "framework includes": + # https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Tasks/IncludingFrameworks.html + incdir = self._get_framework_include_path(framework_path) + if incdir: + self.compile_args += ['-I' + incdir] + self.is_found = True return + def _get_framework_path(self, path, name): + p = Path(path) lname = name.lower() - if path is None: - paths = ['/System/Library/Frameworks', '/Library/Frameworks'] - else: - paths = [path] - for p in paths: - for d in os.listdir(p): - fullpath = os.path.join(p, d) - if lname != d.rsplit('.', 1)[0].lower(): - continue - if not stat.S_ISDIR(os.stat(fullpath).st_mode): - continue - self.path = p - self.name = d - self.is_found = True - return + for d in p.glob('*.framework/'): + if lname == d.name.rsplit('.', 1)[0].lower(): + return d + return None + + def _get_framework_latest_version(self, path): + versions = [] + for each in path.glob('Versions/*'): + # macOS filesystems are usually case-insensitive + if each.name.lower() == 'current': + continue + versions.append(Version(each.name)) + return 'Versions/{}/Headers'.format(sorted(versions)[-1]._s) + + def _get_framework_include_path(self, path): + # According to the spec, 'Headers' must always be a symlink to the + # Headers directory inside the currently-selected version of the + # framework, but sometimes frameworks are broken. Look in 'Versions' + # for the currently-selected version or pick the latest one. + trials = ('Headers', 'Versions/Current/Headers', + self._get_framework_latest_version(path)) + for each in trials: + trial = path / each + if trial.is_dir(): + return trial.as_posix() + return None + + @staticmethod + def get_methods(): + return [DependencyMethods.EXTRAFRAMEWORK] def log_info(self): - return os.path.join(self.path, self.name) + return self.framework_path def log_tried(self): return 'framework' @@ -2044,6 +2104,7 @@ display_name_map = { 'dub': 'DUB', 'gmock': 'GMock', 'gtest': 'GTest', + 'hdf5': 'HDF5', 'llvm': 'LLVM', 'mpi': 'MPI', 'openmp': 'OpenMP', @@ -2086,7 +2147,7 @@ def find_external_dependency(name, env, kwargs): d = c() d._check_version() pkgdep.append(d) - except Exception as e: + except DependencyException as e: pkg_exc.append(e) mlog.debug(str(e)) else: @@ -2128,7 +2189,7 @@ def find_external_dependency(name, env, kwargs): # if an exception occurred with the first detection method, re-raise it # (on the grounds that it came from the preferred dependency detection # method) - if pkg_exc[0]: + if pkg_exc and pkg_exc[0]: raise pkg_exc[0] # we have a list of failed ExternalDependency objects, so we can report @@ -2173,6 +2234,14 @@ def _build_external_dependency_list(name, env, kwargs): candidates.append(functools.partial(CMakeDependency, name, env, kwargs)) return candidates + # If it's explicitly requested, use the Extraframework detection method (only) + if 'extraframework' == kwargs.get('method', ''): + # On OSX, also try framework dependency detector + if mesonlib.is_osx(): + candidates.append(functools.partial(ExtraFrameworkDependency, name, + False, None, env, None, kwargs)) + return candidates + # Otherwise, just use the pkgconfig and cmake dependency detector if 'auto' == kwargs.get('method', 'auto'): candidates.append(functools.partial(PkgConfigDependency, name, env, kwargs)) diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index e6f52a5..208f063 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -14,14 +14,13 @@ # This file contains the detection logic for miscellaneous external dependencies. +from pathlib import Path import functools import os import re import shlex import sysconfig -from pathlib import Path - from .. import mlog from .. import mesonlib from ..environment import detect_cpu_family @@ -33,6 +32,52 @@ from .base import ( ) +class HDF5Dependency(ExternalDependency): + + def __init__(self, environment, kwargs): + language = kwargs.get('language', 'c') + super().__init__('hdf5', environment, language, kwargs) + kwargs['required'] = False + kwargs['silent'] = True + self.is_found = False + + pkgconfig_files = ['hdf5'] + + if language not in ('c', 'cpp', 'fortran'): + raise DependencyException('Language {} is not supported with HDF5.'.format(language)) + + for pkg in pkgconfig_files: + try: + pkgdep = PkgConfigDependency(pkg, environment, kwargs, language=self.language) + if pkgdep.found(): + self.compile_args = pkgdep.get_compile_args() + # derive needed libraries by language + pd_link_args = pkgdep.get_link_args() + link_args = [] + for larg in pd_link_args: + lpath = Path(larg) + if lpath.is_file(): + if language == 'cpp': + link_args.append(str(lpath.parent / (lpath.stem + '_hl_cpp' + lpath.suffix))) + link_args.append(str(lpath.parent / (lpath.stem + '_cpp' + lpath.suffix))) + elif language == 'fortran': + link_args.append(str(lpath.parent / (lpath.stem + 'hl_fortran' + lpath.suffix))) + link_args.append(str(lpath.parent / (lpath.stem + '_fortran' + lpath.suffix))) + + # HDF5 C libs are required by other HDF5 languages + link_args.append(str(lpath.parent / (lpath.stem + '_hl' + lpath.suffix))) + link_args.append(larg) + else: + link_args.append(larg) + + self.link_args = link_args + self.version = pkgdep.get_version() + self.is_found = True + self.pcdep = pkgdep + break + except Exception: + pass + class MPIDependency(ExternalDependency): def __init__(self, environment, kwargs): @@ -307,7 +352,7 @@ class Python3Dependency(ExternalDependency): # There is a python in /System/Library/Frameworks, but that's # python 2, Python 3 will always be in /Library candidates.append(functools.partial( - ExtraFrameworkDependency, 'python', False, '/Library/Frameworks', + ExtraFrameworkDependency, 'Python', False, ['/Library/Frameworks'], environment, kwargs.get('language', None), kwargs)) return candidates diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py index c78ebed..7e9f9d8 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -15,8 +15,6 @@ # This file contains the detection logic for external dependencies that are # platform-specific (generally speaking). -from .. import mesonlib - from .base import ExternalDependency, DependencyException @@ -29,11 +27,19 @@ class AppleFrameworks(ExternalDependency): if not modules: raise DependencyException("AppleFrameworks dependency requires at least one module.") self.frameworks = modules - # FIXME: Use self.clib_compiler to check if the frameworks are available + if not self.clib_compiler: + raise DependencyException('No C-like compilers are available, cannot find the framework') + self.is_found = True for f in self.frameworks: - self.link_args += ['-framework', f] - - self.is_found = mesonlib.for_darwin(self.want_cross, self.env) + args = self.clib_compiler.find_framework(f, env, []) + if args is not None: + # No compile args are needed for system frameworks + self.link_args = args + else: + self.is_found = False + + def log_info(self): + return ', '.join(self.frameworks) def log_tried(self): return 'framework' diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index e23124c..ce1ca68 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -91,9 +91,9 @@ class GnuStepDependency(ConfigToolDependency): 'link_args')) def find_config(self, versions=None): - tool = self.tools[0] + tool = [self.tools[0]] try: - p, out = Popen_safe([tool, '--help'])[:2] + p, out = Popen_safe(tool + ['--help'])[:2] except (FileNotFoundError, PermissionError): return (None, None) if p.returncode != 0: @@ -177,13 +177,13 @@ def _qt_get_private_includes(mod_inc_dir, module, mod_version): os.path.join(private_dir, 'Qt' + module)) class QtExtraFrameworkDependency(ExtraFrameworkDependency): - def __init__(self, name, required, path, env, lang, kwargs): - super().__init__(name, required, path, env, lang, kwargs) + def __init__(self, name, required, paths, env, lang, kwargs): + super().__init__(name, required, paths, env, lang, kwargs) self.mod_name = name[2:] def get_compile_args(self, with_private_headers=False, qt_version="0"): if self.found(): - mod_inc_dir = os.path.join(self.path, self.name, 'Headers') + mod_inc_dir = os.path.join(self.framework_path, 'Headers') args = ['-I' + mod_inc_dir] if with_private_headers: args += ['-I' + dirname for dirname in _qt_get_private_includes(mod_inc_dir, self.mod_name, qt_version)] @@ -216,9 +216,11 @@ class QtBaseDependency(ExternalDependency): methods = [] # Prefer pkg-config, then fallback to `qmake -query` if DependencyMethods.PKGCONFIG in self.methods: + mlog.debug('Trying to find qt with pkg-config') self._pkgconfig_detect(mods, kwargs) methods.append('pkgconfig') if not self.is_found and DependencyMethods.QMAKE in self.methods: + mlog.debug('Trying to find qt with qmake') self.from_text = self._qmake_detect(mods, kwargs) methods.append('qmake-' + self.name) methods.append('qmake') @@ -371,7 +373,9 @@ class QtBaseDependency(ExternalDependency): continue (k, v) = tuple(line.split(':', 1)) qvars[k] = v - if mesonlib.is_osx(): + # Qt on macOS uses a framework, but Qt for iOS does not + if self.env.machines.host.is_darwin() and 'ios' not in qvars['QMAKE_XSPEC']: + mlog.debug("Building for macOS, looking for framework") self._framework_detect(qvars, mods, kwargs) return qmake incdir = qvars['QT_INSTALL_HEADERS'] @@ -442,7 +446,8 @@ class QtBaseDependency(ExternalDependency): for m in modules: fname = 'Qt' + m - fwdep = QtExtraFrameworkDependency(fname, False, libdir, self.env, + mlog.debug('Looking for qt framework ' + fname) + fwdep = QtExtraFrameworkDependency(fname, False, [libdir], self.env, self.language, fw_kwargs) self.compile_args.append('-F' + libdir) if fwdep.found(): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index cedbc7e..f9defcd 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -1337,6 +1337,9 @@ class MachineInfo: return NotImplemented return not self.__eq__(other) + def __repr__(self): + return '<MachineInfo: {} {} ({})>'.format(self.system, self.cpu_family, self.cpu) + @staticmethod def detect(compilers = None): """Detect the machine we're running on @@ -1520,6 +1523,7 @@ class BinaryTable: 'windres': 'WINDRES', 'cmake': 'CMAKE', + 'qmake': 'QMAKE', 'pkgconfig': 'PKG_CONFIG', } diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c940f40..fb4c468 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -276,7 +276,8 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): if len(args) == 1 and isinstance(args[0], list) and len(args[0]) == 2: mlog.deprecation('Passing a list as the single argument to ' 'configuration_data.set is deprecated. This will ' - 'become a hard error in the future.') + 'become a hard error in the future.', + location=self.current_node) args = args[0] if len(args) != 2: @@ -289,7 +290,7 @@ class ConfigurationDataHolder(MutableInterpreterObject, ObjectHolder): msg = 'Setting a configuration data value to {!r} is invalid, ' \ 'and will fail at configure_file(). If you are using it ' \ 'just to store some values, please use a dict instead.' - mlog.deprecation(msg.format(val)) + mlog.deprecation(msg.format(val), location=self.current_node) desc = kwargs.get('description', None) if not isinstance(name, str): raise InterpreterException("First argument to set must be a string.") @@ -1928,6 +1929,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'main', 'method', 'modules', + 'cmake_module_path', 'optional_modules', 'native', 'not_found_message', @@ -1935,6 +1937,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'static', 'version', 'private_headers', + 'cmake_args', }, 'declare_dependency': {'include_directories', 'link_with', @@ -2902,10 +2905,10 @@ external dependencies (including libraries) must go to "dependencies".''') elif name == 'openmp': FeatureNew('OpenMP Dependency', '0.46.0').use(self.subproject) + @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message', 'cmake_module_path', 'cmake_args']) @FeatureNewKwargs('dependency', '0.49.0', ['disabler']) @FeatureNewKwargs('dependency', '0.40.0', ['method']) @FeatureNewKwargs('dependency', '0.38.0', ['default_options']) - @FeatureNewKwargs('dependency', '0.50.0', ['not_found_message']) @disablerIfNotFound @permittedKwargs(permitted_kwargs['dependency']) def func_dependency(self, node, args, kwargs): @@ -3594,6 +3597,10 @@ This will become a hard error in the future.''' % kwargs['input'], location=self # for backwards compatibility. That was the behaviour before # 0.45.0 so preserve it. idir = kwargs.get('install_dir', '') + if idir is False: + idir = '' + mlog.deprecation('Please use the new `install:` kwarg instead of passing ' + '`false` to `install_dir:`', location=node) if not isinstance(idir, str): raise InterpreterException('"install_dir" must be a string') install = kwargs.get('install', idir != '') diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index 8454d79..540fcdc 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -461,6 +461,26 @@ def exe_exists(arglist): pass return False +lru_cache(maxsize=None) +def darwin_get_object_archs(objpath): + ''' + For a specific object (executable, static library, dylib, etc), run `lipo` + to fetch the list of archs supported by it. Supports both thin objects and + 'fat' objects. + ''' + _, stdo, stderr = Popen_safe(['lipo', '-info', objpath]) + if not stdo: + mlog.debug('lipo {}: {}'.format(objpath, stderr)) + return None + stdo = stdo.rsplit(': ', 1)[1] + # Convert from lipo-style archs to meson-style CPUs + stdo = stdo.replace('i386', 'x86') + stdo = stdo.replace('arm64', 'aarch64') + # Add generic name for armv7 and armv7s + if 'armv7' in stdo: + stdo += ' arm' + return stdo.split() + def detect_vcs(source_dir): vcs_systems = [ dict(name = 'git', cmd = 'git', repo_dir = '.git', get_rev = 'git describe --dirty=+', rev_regex = '(.*)', dep = '.git/logs/HEAD'), @@ -496,6 +516,9 @@ class Version: def __str__(self): return '%s (V=%s)' % (self._s, str(self._v)) + def __repr__(self): + return '<Version: {}>'.format(self._s) + def __lt__(self, other): return self.__cmp__(other) == -1 diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 2ab575c..871cd48 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -607,9 +607,15 @@ class GnomeModule(ExtensionModule): if 'b_sanitize' in compiler.base_options: sanitize = state.environment.coredata.base_options['b_sanitize'].value cflags += compilers.sanitizer_compile_args(sanitize) - if 'address' in sanitize.split(','): - internal_ldflags += ['-lasan'] # This must be first in ldflags - # FIXME: Linking directly to libasan is not recommended but g-ir-scanner + sanitize = sanitize.split(',') + # These must be first in ldflags + if 'address' in sanitize: + internal_ldflags += ['-lasan'] + if 'thread' in sanitize: + internal_ldflags += ['-ltsan'] + if 'undefined' in sanitize: + internal_ldflags += ['-lubsan'] + # FIXME: Linking directly to lib*san is not recommended but g-ir-scanner # does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892 # ldflags += compilers.sanitizer_link_args(sanitize) diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 0e232a0..8ce28ba 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -23,6 +23,8 @@ from . import ModuleReturnValue from . import ExtensionModule from ..interpreterbase import permittedKwargs, FeatureNew, FeatureNewKwargs +already_warned_objs = set() + class DependenciesHelper: def __init__(self, name): self.name = name @@ -51,16 +53,21 @@ class DependenciesHelper: self.priv_reqs += self._process_reqs(reqs) def _check_generated_pc_deprecation(self, obj): - if hasattr(obj, 'generated_pc_warn'): - mlog.deprecation('Library', mlog.bold(obj.name), 'was passed to the ' - '"libraries" keyword argument of a previous call ' - 'to generate() method instead of first positional ' - 'argument.', 'Adding', mlog.bold(obj.generated_pc), - 'to "Requires" field, but this is a deprecated ' - 'behaviour that will change in a future version ' - 'of Meson. Please report the issue if this ' - 'warning cannot be avoided in your case.', - location=obj.generated_pc_warn) + if not hasattr(obj, 'generated_pc_warn'): + return + name = obj.generated_pc_warn[0] + if (name, obj.name) in already_warned_objs: + return + mlog.deprecation('Library', mlog.bold(obj.name), 'was passed to the ' + '"libraries" keyword argument of a previous call ' + 'to generate() method instead of first positional ' + 'argument.', 'Adding', mlog.bold(obj.generated_pc), + 'to "Requires" field, but this is a deprecated ' + 'behaviour that will change in a future version ' + 'of Meson. Please report the issue if this ' + 'warning cannot be avoided in your case.', + location=obj.generated_pc_warn[1]) + already_warned_objs.add((name, obj.name)) def _process_reqs(self, reqs): '''Returns string names of requirements''' @@ -442,8 +449,9 @@ class PkgConfigModule(ExtensionModule): for lib in deps.pub_libs: if not isinstance(lib, str) and not hasattr(lib, 'generated_pc'): lib.generated_pc = filebase - lib.generated_pc_warn = types.SimpleNamespace(subdir=state.subdir, - lineno=state.current_lineno) + location = types.SimpleNamespace(subdir=state.subdir, + lineno=state.current_lineno) + lib.generated_pc_warn = [name, location] return ModuleReturnValue(res, [res]) def initialize(*args, **kwargs): diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 1d41165..049c457 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -60,6 +60,7 @@ class PythonDependency(ExternalDependency): self.pkgdep = None self.variables = python_holder.variables self.paths = python_holder.paths + self.link_libpython = python_holder.link_libpython if mesonlib.version_compare(self.version, '>= 3.0'): self.major_version = 3 else: @@ -149,11 +150,11 @@ class PythonDependency(ExternalDependency): libdirs = [] largs = self.clib_compiler.find_library(libname, environment, libdirs) - - self.is_found = largs is not None - if self.is_found: + if largs is not None: self.link_args = largs + self.is_found = largs is not None or not self.link_libpython + inc_paths = mesonlib.OrderedSet([ self.variables.get('INCLUDEPY'), self.paths.get('include'), diff --git a/run_project_tests.py b/run_project_tests.py index 4c6ca3b..d10f3a2 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -496,6 +496,10 @@ def skippable(suite, test): return 'BOOST_ROOT' not in os.environ return False + # Qt is provided on macOS by Homebrew + if test.endswith('4 qt') and mesonlib.is_osx(): + return False + # Other framework tests are allowed to be skipped on other platforms return True diff --git a/run_tests.py b/run_tests.py index 25a2d7f..20cb4e2 100755 --- a/run_tests.py +++ b/run_tests.py @@ -66,7 +66,7 @@ class FakeCompilerOptions: def __init__(self): self.value = [] -def get_fake_options(prefix): +def get_fake_options(prefix=''): import argparse opts = argparse.Namespace() opts.cross_file = None @@ -76,11 +76,12 @@ def get_fake_options(prefix): opts.native_file = [] return opts -def get_fake_env(sdir, bdir, prefix, opts = None): +def get_fake_env(sdir='', bdir=None, prefix='', opts=None): if opts is None: opts = get_fake_options(prefix) env = Environment(sdir, bdir, opts) env.coredata.compiler_options['c_args'] = FakeCompilerOptions() + env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library return env @@ -213,6 +214,14 @@ def run_mtest_inprocess(commandlist): sys.stderr = old_stderr return returncode, mystdout.getvalue(), mystderr.getvalue() +def clear_meson_configure_class_caches(): + mesonbuild.compilers.CCompiler.library_dirs_cache = {} + mesonbuild.compilers.CCompiler.program_dirs_cache = {} + mesonbuild.compilers.CCompiler.find_library_cache = {} + mesonbuild.compilers.CCompiler.find_framework_cache = {} + mesonbuild.dependencies.PkgConfigDependency.pkgbin_cache = {} + mesonbuild.dependencies.PkgConfigDependency.class_pkgbin = mesonlib.PerMachine(None, None, None) + def run_configure_inprocess(commandlist): old_stdout = sys.stdout sys.stdout = mystdout = StringIO() @@ -223,6 +232,7 @@ def run_configure_inprocess(commandlist): finally: sys.stdout = old_stdout sys.stderr = old_stderr + clear_meson_configure_class_caches() return returncode, mystdout.getvalue(), mystderr.getvalue() def run_configure_external(full_command): diff --git a/run_unittests.py b/run_unittests.py index 8c4093b..9525b01 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -42,10 +42,11 @@ import mesonbuild.mesonlib import mesonbuild.coredata import mesonbuild.modules.gnome from mesonbuild.interpreter import Interpreter, ObjectHolder +from mesonbuild.ast import AstInterpreter from mesonbuild.mesonlib import ( is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, windows_proof_rmtree, python_command, version_compare, - BuildDirLock, Version + BuildDirLock, Version, PerMachine ) from mesonbuild.environment import detect_ninja from mesonbuild.mesonlib import MesonException, EnvironmentException @@ -163,7 +164,7 @@ def skip_if_not_language(lang): @functools.wraps(func) def wrapped(*args, **kwargs): try: - env = get_fake_env('', '', '') + env = get_fake_env() f = getattr(env, 'detect_{}_compiler'.format(lang)) if lang in ['cs', 'vala', 'java', 'swift']: f() @@ -203,7 +204,7 @@ def skip_if_not_base_option(feature): def actual(f): @functools.wraps(f) def wrapped(*args, **kwargs): - env = get_fake_env('', '', '') + env = get_fake_env() cc = env.detect_c_compiler(False) if feature not in cc.base_options: raise unittest.SkipTest( @@ -230,6 +231,32 @@ def temp_filename(): except OSError: pass +@contextmanager +def no_pkgconfig(): + ''' + A context manager that overrides shutil.which and ExternalProgram to force + them to return None for pkg-config to simulate it not existing. + ''' + old_which = shutil.which + old_search = ExternalProgram._search + + def new_search(self, name, search_dir): + if name == 'pkg-config': + return [None] + return old_search(self, name, search_dir) + + def new_which(cmd, *kwargs): + if cmd == 'pkg-config': + return None + return old_which(cmd, *kwargs) + + shutil.which = new_which + ExternalProgram._search = new_search + try: + yield + finally: + shutil.which = old_which + ExternalProgram._search = old_search class PatchModule: ''' @@ -581,9 +608,9 @@ class InternalTests(unittest.TestCase): config.write(configfile) configfile.flush() configfile.close() - opts = get_fake_options('') + opts = get_fake_options() opts.cross_file = configfilename - env = get_fake_env('', '', '', opts) + env = get_fake_env(opts=opts) detected_value = env.need_exe_wrapper() os.unlink(configfilename) @@ -596,9 +623,9 @@ class InternalTests(unittest.TestCase): configfilename = configfile.name config.write(configfile) configfile.close() - opts = get_fake_options('') + opts = get_fake_options() opts.cross_file = configfilename - env = get_fake_env('', '', '', opts) + env = get_fake_env(opts=opts) forced_value = env.need_exe_wrapper() os.unlink(configfilename) @@ -718,7 +745,7 @@ class InternalTests(unittest.TestCase): 'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll', '{}.dll.a', '{}.lib', '{}.dll'), 'static': msvc_static}} - env = get_fake_env('', '', '') + env = get_fake_env() cc = env.detect_c_compiler(False) if is_osx(): self._test_all_naming(cc, env, patterns, 'darwin') @@ -755,9 +782,20 @@ class InternalTests(unittest.TestCase): https://github.com/mesonbuild/meson/issues/3951 ''' + def create_static_lib(name): + if not is_osx(): + name.open('w').close() + return + src = name.with_suffix('.c') + out = name.with_suffix('.o') + with src.open('w') as f: + f.write('int meson_foobar (void) { return 0; }') + subprocess.check_call(['clang', '-c', str(src), '-o', str(out)]) + subprocess.check_call(['ar', 'csr', str(name), str(out)]) + with tempfile.TemporaryDirectory() as tmpdir: pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) - env = get_fake_env('', '', '') + env = get_fake_env() compiler = env.detect_c_compiler(False) env.coredata.compilers = {'c': compiler} env.coredata.compiler_options['c_link_args'] = FakeCompilerOptions() @@ -766,16 +804,16 @@ class InternalTests(unittest.TestCase): p1.mkdir() p2.mkdir() # libfoo.a is in one prefix - (p1 / 'libfoo.a').open('w').close() + create_static_lib(p1 / 'libfoo.a') # libbar.a is in both prefixes - (p1 / 'libbar.a').open('w').close() - (p2 / 'libbar.a').open('w').close() + create_static_lib(p1 / 'libbar.a') + create_static_lib(p2 / 'libbar.a') # Ensure that we never statically link to these - (p1 / 'libpthread.a').open('w').close() - (p1 / 'libm.a').open('w').close() - (p1 / 'libc.a').open('w').close() - (p1 / 'libdl.a').open('w').close() - (p1 / 'librt.a').open('w').close() + create_static_lib(p1 / 'libpthread.a') + create_static_lib(p1 / 'libm.a') + create_static_lib(p1 / 'libc.a') + create_static_lib(p1 / 'libdl.a') + create_static_lib(p1 / 'librt.a') def fake_call_pkgbin(self, args, env=None): if '--libs' not in args: @@ -789,30 +827,31 @@ class InternalTests(unittest.TestCase): old_call = PkgConfigDependency._call_pkgbin old_check = PkgConfigDependency.check_pkgconfig - old_pkgbin = PkgConfigDependency.class_pkgbin PkgConfigDependency._call_pkgbin = fake_call_pkgbin PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin # Test begins - kwargs = {'required': True, 'silent': True} - foo_dep = PkgConfigDependency('foo', env, kwargs) - self.assertEqual(foo_dep.get_link_args(), - [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()]) - bar_dep = PkgConfigDependency('bar', env, kwargs) - self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()]) - internal_dep = PkgConfigDependency('internal', env, kwargs) - if compiler.get_argument_syntax() == 'msvc': - self.assertEqual(internal_dep.get_link_args(), []) - else: - link_args = internal_dep.get_link_args() - for link_arg in link_args: - for lib in ('pthread', 'm', 'c', 'dl', 'rt'): - self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args) - # Test ends - PkgConfigDependency._call_pkgbin = old_call - PkgConfigDependency.check_pkgconfig = old_check - # Reset dependency class to ensure that in-process configure doesn't mess up - PkgConfigDependency.pkgbin_cache = {} - PkgConfigDependency.class_pkgbin = old_pkgbin + try: + kwargs = {'required': True, 'silent': True} + foo_dep = PkgConfigDependency('foo', env, kwargs) + self.assertEqual(foo_dep.get_link_args(), + [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()]) + bar_dep = PkgConfigDependency('bar', env, kwargs) + self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()]) + internal_dep = PkgConfigDependency('internal', env, kwargs) + if compiler.get_argument_syntax() == 'msvc': + self.assertEqual(internal_dep.get_link_args(), []) + else: + link_args = internal_dep.get_link_args() + for link_arg in link_args: + for lib in ('pthread', 'm', 'c', 'dl', 'rt'): + self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args) + finally: + # Test ends + PkgConfigDependency._call_pkgbin = old_call + PkgConfigDependency.check_pkgconfig = old_check + # Reset dependency class to ensure that in-process configure doesn't mess up + PkgConfigDependency.pkgbin_cache = {} + PkgConfigDependency.class_pkgbin = PerMachine(None, None, None) def test_version_compare(self): comparefunc = mesonbuild.mesonlib.version_compare_many @@ -962,7 +1001,7 @@ class DataTests(unittest.TestCase): with open('docs/markdown/Builtin-options.md') as f: md = f.read() self.assertIsNotNone(md) - env = get_fake_env('', '', '') + env = get_fake_env() # FIXME: Support other compilers cc = env.detect_c_compiler(False) cpp = env.detect_cpp_compiler(False) @@ -1008,13 +1047,23 @@ class DataTests(unittest.TestCase): Ensure that syntax highlighting files were updated for new functions in the global namespace in build files. ''' - env = get_fake_env('', '', '') + env = get_fake_env() interp = Interpreter(FakeBuild(env), mock=True) with open('data/syntax-highlighting/vim/syntax/meson.vim') as f: res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE) defined = set([a.strip() for a in res.group().split('\\')][1:]) self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) + def test_all_functions_defined_in_ast_interpreter(self): + ''' + Ensure that the all functions defined in the Interpreter are also defined + in the AstInterpreter (and vice versa). + ''' + env = get_fake_env() + interp = Interpreter(FakeBuild(env), mock=True) + astint = AstInterpreter('.', '') + self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys())) + class BasePlatformTests(unittest.TestCase): def setUp(self): @@ -2201,7 +2250,7 @@ int main(int argc, char **argv) { self.assertPathExists(os.path.join(testdir, i)) def detect_prebuild_env(self): - env = get_fake_env('', self.builddir, self.prefix) + env = get_fake_env() cc = env.detect_c_compiler(False) stlinker = env.detect_static_linker(cc) if mesonbuild.mesonlib.is_windows(): @@ -3302,7 +3351,7 @@ recommended as it is not supported on some platforms''') # Check buildsystem_files bs_files = ['meson.build', 'sharedlib/meson.build', 'staticlib/meson.build'] bs_files = [os.path.join(testdir, x) for x in bs_files] - self.assertPathListEqual(res['buildsystem_files'], bs_files) + self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files))) # Check dependencies dependencies_to_find = ['threads'] @@ -3412,7 +3461,7 @@ class FailureTests(BasePlatformTests): and slows down testing. ''' dnf = "[Dd]ependency.*not found(:.*)?" - nopkg = '[Pp]kg-config not found' + nopkg = '[Pp]kg-config.*not found' def setUp(self): super().setUp() @@ -3493,16 +3542,29 @@ class FailureTests(BasePlatformTests): self.assertMesonRaises("dependency('appleframeworks')", "requires at least one module") + def test_extraframework_dependency_method(self): + code = "dependency('python', method : 'extraframework')" + if not is_osx(): + self.assertMesonRaises(code, self.dnf) + else: + # Python2 framework is always available on macOS + self.assertMesonOutputs(code, '[Dd]ependency.*python.*found.*YES') + 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) if shutil.which('pkg-config'): - errmsg = self.dnf - else: - errmsg = self.nopkg - self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", errmsg) + self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf) + with no_pkgconfig(): + # Look for pkg-config, cache it, then + # Use cached pkg-config without erroring out, then + # Use cached pkg-config to error out + code = "dependency('foobarrr', method : 'pkg-config', required : false)\n" \ + "dependency('foobarrr2', method : 'pkg-config', required : false)\n" \ + "dependency('sdl2', method : 'pkg-config')" + self.assertMesonRaises(code, self.nopkg) def test_gnustep_notfound_dependency(self): # Want to test failure, so skip if available @@ -3563,7 +3625,7 @@ class FailureTests(BasePlatformTests): ''' Test that when we can't detect objc or objcpp, we fail gracefully. ''' - env = get_fake_env('', self.builddir, self.prefix) + env = get_fake_env() try: env.detect_objc_compiler(False) env.detect_objcpp_compiler(False) @@ -3830,6 +3892,7 @@ class DarwinTests(BasePlatformTests): self.assertIsNotNone(m, msg=out) return m.groups() + @skipIfNoPkgconfig def test_library_versioning(self): ''' Ensure that compatibility_version and current_version are set correctly @@ -5180,7 +5243,7 @@ class NativeFileTests(BasePlatformTests): """Helper for generating tests for overriding compilers for langaugages with more than one implementation, such as C, C++, ObjC, ObjC++, and D. """ - env = get_fake_env('', '', '') + env = get_fake_env() getter = getattr(env, 'detect_{}_compiler'.format(lang)) if lang not in ['cs']: getter = functools.partial(getter, False) @@ -5341,7 +5404,7 @@ class NativeFileTests(BasePlatformTests): Builds a wrapper around the compiler to override the version. """ wrapper = self.helper_create_binary_wrapper(binary, version=version_str) - env = get_fake_env('', '', '') + env = get_fake_env() getter = getattr(env, 'detect_{}_compiler'.format(lang)) if lang in ['rust']: getter = functools.partial(getter, False) @@ -5370,7 +5433,7 @@ class NativeFileTests(BasePlatformTests): def test_swift_compiler(self): wrapper = self.helper_create_binary_wrapper( 'swiftc', version='Swift 1.2345', outfile='stderr') - env = get_fake_env('', '', '') + env = get_fake_env() env.binaries.host.binaries['swift'] = wrapper compiler = env.detect_swift_compiler() self.assertEqual(compiler.version, '1.2345') diff --git a/test cases/common/14 configure file/meson.build b/test cases/common/14 configure file/meson.build index 982ae2a..50393e9 100644 --- a/test cases/common/14 configure file/meson.build +++ b/test cases/common/14 configure file/meson.build @@ -141,6 +141,12 @@ cfile = configure_file(input : 'config.h.in', install_dir : '', configuration : conf) +# test install_dir : false (deprecated) +cfile = configure_file(input : 'config.h.in', + output : 'do_not_get_installed_please.h', + install_dir : false, + configuration : conf) + # test intsall_dir with install: false cfile = configure_file(input : 'config.h.in', output : 'do_not_get_installed_in_install_dir.h', diff --git a/test cases/frameworks/25 hdf5/main.c b/test cases/frameworks/25 hdf5/main.c new file mode 100644 index 0000000..4c46310 --- /dev/null +++ b/test cases/frameworks/25 hdf5/main.c @@ -0,0 +1,30 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "hdf5.h" + +int main(void) +{ +herr_t ier; +unsigned maj, min, rel; + +ier = H5open(); +if (ier) { + fprintf(stderr,"Unable to initialize HDF5: %d\n", ier); + return EXIT_FAILURE; +} + +ier = H5get_libversion(&maj, &min, &rel); +if (ier) { + fprintf(stderr,"HDF5 did not initialize!\n"); + return EXIT_FAILURE; +} +printf("C HDF5 version %d.%d.%d\n", maj, min, rel); + +ier = H5close(); +if (ier) { + fprintf(stderr,"Unable to close HDF5: %d\n", ier); + return EXIT_FAILURE; +} +return EXIT_SUCCESS; +} diff --git a/test cases/frameworks/25 hdf5/main.cpp b/test cases/frameworks/25 hdf5/main.cpp new file mode 100644 index 0000000..477e76b --- /dev/null +++ b/test cases/frameworks/25 hdf5/main.cpp @@ -0,0 +1,29 @@ +#include <iostream> +#include "hdf5.h" + + +int main(void) +{ +herr_t ier; +unsigned maj, min, rel; + +ier = H5open(); +if (ier) { + std::cerr << "Unable to initialize HDF5: " << ier << std::endl; + return EXIT_FAILURE; +} + +ier = H5get_libversion(&maj, &min, &rel); +if (ier) { + std::cerr << "HDF5 did not initialize!" << std::endl; + return EXIT_FAILURE; +} +std::cout << "C++ HDF5 version " << maj << "." << min << "." << rel << std::endl; + +ier = H5close(); +if (ier) { + std::cerr << "Unable to close HDF5: " << ier << std::endl; + return EXIT_FAILURE; +} +return EXIT_SUCCESS; +} diff --git a/test cases/frameworks/25 hdf5/main.f90 b/test cases/frameworks/25 hdf5/main.f90 new file mode 100644 index 0000000..b21abf1 --- /dev/null +++ b/test cases/frameworks/25 hdf5/main.f90 @@ -0,0 +1,17 @@ +use hdf5 + +implicit none + +integer :: ier, major, minor, rel + +call h5open_f(ier) +if (ier /= 0) error stop 'Unable to initialize HDF5' + +call h5get_libversion_f(major, minor, rel, ier) +if (ier /= 0) error stop 'Unable to check HDF5 version' +print '(A,I1,A1,I0.2,A1,I1)','Fortran HDF5 version ',major,'.',minor,'.',rel + +call h5close_f(ier) +if (ier /= 0) error stop 'Unable to close HDF5 library' + +end program diff --git a/test cases/frameworks/25 hdf5/meson.build b/test cases/frameworks/25 hdf5/meson.build new file mode 100644 index 0000000..9033354 --- /dev/null +++ b/test cases/frameworks/25 hdf5/meson.build @@ -0,0 +1,43 @@ +project('hdf5_test', 'c', 'cpp') + +if build_machine.system() == 'darwin' + error('MESON_SKIP_TEST: HDF5 CI image not setup for OSX.') +endif + +if build_machine.system() == 'cygwin' + error('MESON_SKIP_TEST: HDF5 CI image not setup for Cygwin.') +endif + + +# --- C tests +h5c = dependency('hdf5', language : 'c', required : false) +if not h5c.found() + error('MESON_SKIP_TEST: HDF5 C library not found, skipping HDF5 framework tests.') +endif +exec = executable('exec', 'main.c', dependencies : h5c) + +test('HDF5 C', exec) + +# --- C++ tests +h5cpp = dependency('hdf5', language : 'cpp', required : false) +if h5cpp.found() + execpp = executable('execpp', 'main.cpp', dependencies : h5cpp) + test('HDF5 C++', execpp) +endif + +# --- Fortran tests +if build_machine.system() != 'windows' + add_languages('fortran') + + h5f = dependency('hdf5', language : 'fortran', required : false) + if h5f.found() + exef = executable('exef', 'main.f90', dependencies : h5f) + + test('HDF5 Fortran', exef) + endif +endif + +# Check we can apply a version constraint +if h5c.version() != 'unknown' + dependency('hdf5', version: '>=@0@'.format(h5c.version())) +endif diff --git a/test cases/linuxlike/13 cmake dependency/cmake/FindSomethingLikeZLIB.cmake b/test cases/linuxlike/13 cmake dependency/cmake/FindSomethingLikeZLIB.cmake new file mode 100644 index 0000000..a2f8456 --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/cmake/FindSomethingLikeZLIB.cmake @@ -0,0 +1,9 @@ +find_package(ZLIB) + +if(ZLIB_FOUND OR ZLIB_Found) + set(SomethingLikeZLIB_FOUND ON) + set(SomethingLikeZLIB_LIBRARIES ${ZLIB_LIBRARY}) + set(SomethingLikeZLIB_INCLUDE_DIRS ${ZLIB_INCLUDE_DIR}) +else() + set(SomethingLikeZLIB_FOUND OFF) +endif() diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build index 72773b2..a18cd84 100644 --- a/test cases/linuxlike/13 cmake dependency/meson.build +++ b/test cases/linuxlike/13 cmake dependency/meson.build @@ -36,6 +36,12 @@ depf2 = dependency('ZLIB', required : false, method : 'cmake', modules : 'dfggh: assert(depf2.found() == false, 'Invalid CMake targets should fail') +# Try to find a dependency with a custom CMake module + +depm1 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : 'cmake') +depm2 = dependency('SomethingLikeZLIB', required : true, method : 'cmake', cmake_module_path : ['cmake']) +depm3 = dependency('SomethingLikeZLIB', required : true, cmake_module_path : 'cmake') + # Try to compile a test that takes a dep and an include_directories cc = meson.get_compiler('c') diff --git a/test cases/osx/2 library versions/meson.build b/test cases/osx/2 library versions/meson.build index 26f945a..0d21a3a 100644 --- a/test cases/osx/2 library versions/meson.build +++ b/test cases/osx/2 library versions/meson.build @@ -1,15 +1,27 @@ project('library versions', 'c') -zlib_dep = dependency('zlib') - -some = shared_library('some', 'lib.c', - # duplicate the rpath again, in order - # to test Meson's RPATH deduplication - build_rpath : zlib_dep.get_pkgconfig_variable('libdir'), - dependencies : zlib_dep, - version : '1.2.3', - soversion : '7', - install : true) +if run_command(find_program('require_pkgconfig.py'), check: true).stdout().strip() == 'yes' + required = true +else + required = false +endif + +zlib_dep = dependency('zlib', required: required) +if zlib_dep.found() + some = shared_library('some', 'lib.c', + # duplicate the rpath again, in order + # to test Meson's RPATH deduplication + build_rpath : zlib_dep.get_pkgconfig_variable('libdir'), + dependencies : zlib_dep, + version : '1.2.3', + soversion : '7', + install : true) +else + some = shared_library('some', 'lib.c', + version : '1.2.3', + soversion : '7', + install : true) +endif noversion = shared_library('noversion', 'lib.c', install : true) diff --git a/test cases/osx/2 library versions/require_pkgconfig.py b/test cases/osx/2 library versions/require_pkgconfig.py new file mode 100644 index 0000000..3d228aa --- /dev/null +++ b/test cases/osx/2 library versions/require_pkgconfig.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 + +import os +import shutil + +if 'CI' in os.environ or shutil.which('pkg-config'): + print('yes') +else: + print('no') diff --git a/test cases/osx/5 extra frameworks/installed_files.txt b/test cases/osx/5 extra frameworks/installed_files.txt new file mode 100644 index 0000000..2c6bd93 --- /dev/null +++ b/test cases/osx/5 extra frameworks/installed_files.txt @@ -0,0 +1,2 @@ +usr/bin/prog +usr/lib/libstat.a diff --git a/test cases/osx/5 extra frameworks/meson.build b/test cases/osx/5 extra frameworks/meson.build new file mode 100644 index 0000000..cb4847e --- /dev/null +++ b/test cases/osx/5 extra frameworks/meson.build @@ -0,0 +1,13 @@ +project('xcode extra framework test', 'c') + +dep_libs = dependency('OpenGL', method : 'extraframework') +assert(dep_libs.type_name() == 'extraframeworks', 'type_name is ' + dep_libs.type_name()) + +dep_main = dependency('Foundation') +assert(dep_main.type_name() == 'extraframeworks', 'type_name is ' + dep_main.type_name()) + +dep_py = dependency('python', method : 'extraframework') +assert(dep_main.type_name() == 'extraframeworks', 'type_name is ' + dep_main.type_name()) + +stlib = static_library('stat', 'stat.c', install : true, dependencies: dep_libs) +exe = executable('prog', 'prog.c', install : true, dependencies: dep_main) diff --git a/test cases/osx/5 extra frameworks/prog.c b/test cases/osx/5 extra frameworks/prog.c new file mode 100644 index 0000000..11b7fad --- /dev/null +++ b/test cases/osx/5 extra frameworks/prog.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 0; +} diff --git a/test cases/osx/5 extra frameworks/stat.c b/test cases/osx/5 extra frameworks/stat.c new file mode 100644 index 0000000..fa76a65 --- /dev/null +++ b/test cases/osx/5 extra frameworks/stat.c @@ -0,0 +1 @@ +int func() { return 933; } |