diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 7a10d694f..8f38d6c80 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -35,7 +35,7 @@ from .. import mesonlib from ..compilers import clib_langs from ..environment import BinaryTable, Environment, MachineInfo from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine -from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify +from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list from ..mesonlib import Version, LibType # These must be defined in this file to avoid cyclical references. @@ -971,6 +971,24 @@ class CMakeDependency(ExternalDependency): def _gen_exception(self, msg): return DependencyException('Dependency {} not found: {}'.format(self.name, msg)) + def _main_cmake_file(self) -> str: + return 'CMakeLists.txt' + + def _extra_cmake_opts(self) -> List[str]: + return [] + + def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]: + # Map the input module list to something else + # This function will only be executed AFTER the initial CMake + # interpreter pass has completed. Thus variables defined in the + # CMakeLists.txt can be accessed here. + return modules + + def _original_module_name(self, module: str) -> str: + # Reverse the module mapping done by _map_module_list for + # one module + return module + def __init__(self, name: str, environment: Environment, kwargs, language=None): super().__init__('cmake', environment, language, kwargs) self.name = name @@ -990,6 +1008,9 @@ class CMakeDependency(ExternalDependency): # Where all CMake "build dirs" are located self.cmake_root_dir = environment.scratch_dir + # List of successfully found modules + self.found_modules = [] + # When finding dependencies for cross-compiling, we don't care about # the 'native' CMake binary # TODO: Test if this works as expected @@ -1061,15 +1082,10 @@ class CMakeDependency(ExternalDependency): if self.cmakeinfo is None: raise self._gen_exception('Unable to obtain CMake system information') - modules = kwargs.get('modules', []) - cm_path = kwargs.get('cmake_module_path', []) - cm_args = kwargs.get('cmake_args', []) - if not isinstance(modules, list): - modules = [modules] - if not isinstance(cm_path, list): - cm_path = [cm_path] - if not isinstance(cm_args, list): - cm_args = [cm_args] + modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))] + modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))] + cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path')) + cm_args = stringlistify(extract_as_list(kwargs, 'cmake_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))] @@ -1242,7 +1258,7 @@ class CMakeDependency(ExternalDependency): return False - def _detect_dep(self, name: str, modules: List[str], args: List[str]): + def _detect_dep(self, name: str, modules: List[Tuple[str, bool]], args: List[str]): # Detect a dependency with CMake using the '--find-package' mode # and the trace output (stderr) # @@ -1265,11 +1281,12 @@ class CMakeDependency(ExternalDependency): # Prepare options cmake_opts = ['--trace-expand', '-DNAME={}'.format(name), '-DARCHS={}'.format(';'.join(self.cmakeinfo['archs']))] + args + ['.'] + cmake_opts += self._extra_cmake_opts() if len(i) > 0: cmake_opts = ['-G', i] + cmake_opts # Run CMake - ret1, out1, err1 = self._call_cmake(cmake_opts, 'CMakeLists.txt') + ret1, out1, err1 = self._call_cmake(cmake_opts, self._main_cmake_file()) # Current generator was successful if ret1 == 0: @@ -1327,6 +1344,11 @@ class CMakeDependency(ExternalDependency): self.version = vers_raw[0] self.version.strip('"\' ') + # Post-process module list. Used in derived classes to modify the + # module list (append prepend a string, etc.). + modules = self._map_module_list(modules) + autodetected_module_list = False + # Try guessing a CMake target if none is provided if len(modules) == 0: for i in self.targets: @@ -1334,17 +1356,19 @@ class CMakeDependency(ExternalDependency): lname = name.lower() if '{}::{}'.format(lname, lname) == tg or lname == tg.replace('::', ''): mlog.debug('Guessed CMake target \'{}\''.format(i)) - modules = [i] + modules = [(i, True)] + autodetected_module_list = True break # Failed to guess a target --> try the old-style method if len(modules) == 0: incDirs = self.get_first_cmake_var_of(['PACKAGE_INCLUDE_DIRS']) + defs = self.get_first_cmake_var_of(['PACKAGE_DEFINITIONS']) libs = self.get_first_cmake_var_of(['PACKAGE_LIBRARIES']) # Try to use old style variables if no module is specified if len(libs) > 0: - self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) + self.compile_args = list(map(lambda x: '-I{}'.format(x), incDirs)) + defs self.link_args = libs mlog.debug('using old-style CMake variables for dependency {}'.format(name)) return @@ -1361,13 +1385,19 @@ class CMakeDependency(ExternalDependency): compileDefinitions = [] compileOptions = [] libraries = [] - for i in modules: + for i, required in modules: if i not in self.targets: - raise self._gen_exception('CMake: invalid CMake target {} for {}.\n' + if not required: + mlog.warning('CMake: Optional module', mlog.bold(self._original_module_name(i)), 'for', mlog.bold(name), 'was not found') + continue + raise self._gen_exception('CMake: invalid module {} for {}.\n' 'Try to explicitly specify one or more targets with the "modules" property.\n' - 'Valid targets are:\n{}'.format(i, name, list(self.targets.keys()))) + 'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.targets.keys()))) targets = [i] + if not autodetected_module_list: + self.found_modules += [i] + while len(targets) > 0: curr = targets.pop(0) @@ -1432,7 +1462,7 @@ class CMakeDependency(ExternalDependency): self.compile_args = compileOptions + compileDefinitions + list(map(lambda x: '-I{}'.format(x), incDirs)) self.link_args = libraries - def get_first_cmake_var_of(self, var_list): + def get_first_cmake_var_of(self, var_list: List[str]) -> List[str]: # Return the first found CMake variable in list var_list for i in var_list: if i in self.vars: @@ -1440,7 +1470,7 @@ class CMakeDependency(ExternalDependency): return [] - def get_cmake_var(self, var): + def get_cmake_var(self, var: str) -> List[str]: # Return the value of the CMake variable var or an empty list if var does not exist if var in self.vars: return self.vars[var] @@ -1747,6 +1777,13 @@ set(CMAKE_SIZEOF_VOID_P "{}") def log_tried(self): return self.type_name + def log_details(self) -> str: + modules = [self._original_module_name(x) for x in self.found_modules] + modules = sorted(set(modules)) + if modules: + return 'modules: ' + ', '.join(modules) + return '' + class DubDependency(ExternalDependency): class_dubbin = None diff --git a/mesonbuild/dependencies/data/CMakeListsLLVM.txt b/mesonbuild/dependencies/data/CMakeListsLLVM.txt new file mode 100644 index 000000000..9d3e41234 --- /dev/null +++ b/mesonbuild/dependencies/data/CMakeListsLLVM.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION} ) + +set(PACKAGE_FOUND FALSE) + +while(TRUE) + find_package(LLVM REQUIRED CONFIG QUIET) + + # ARCHS has to be set via the CMD interface + if(LLVM_FOUND OR "${ARCHS}" STREQUAL "") + break() + endif() + + list(GET ARCHS 0 CMAKE_LIBRARY_ARCHITECTURE) + list(REMOVE_AT ARCHS 0) +endwhile() + +if(LLVM_FOUND) + set(PACKAGE_FOUND TRUE) + + foreach(mod IN LISTS LLVM_MESON_MODULES) + # Reset variables + set(out_mods) + set(real_mods) + + # Generate a lower and upper case version + string(TOLOWER "${mod}" mod_L) + string(TOUPPER "${mod}" mod_U) + + # Get the mapped components + llvm_map_components_to_libnames(out_mods ${mod} ${mod_L} ${mod_U}) + list(SORT out_mods) + list(REMOVE_DUPLICATES out_mods) + + # Make sure that the modules exist + foreach(i IN LISTS out_mods) + if(TARGET ${i}) + list(APPEND real_mods ${i}) + endif() + endforeach() + + # Set the output variables + set(MESON_LLVM_TARGETS_${mod} ${real_mods}) + foreach(i IN LISTS real_mods) + set(MESON_TARGET_TO_LLVM_${i} ${mod}) + endforeach() + endforeach() + + # Check the following variables: + # LLVM_PACKAGE_VERSION + # LLVM_VERSION + # LLVM_VERSION_STRING + if(NOT DEFINED PACKAGE_VERSION) + if(DEFINED LLVM_PACKAGE_VERSION) + set(PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}") + elseif(DEFINED LLVM_VERSION) + set(PACKAGE_VERSION "${LLVM_VERSION}") + elseif(DEFINED LLVM_VERSION_STRING) + set(PACKAGE_VERSION "${LLVM_VERSION_STRING}") + endif() + endif() + + # Check the following variables: + # LLVM_LIBRARIES + # LLVM_LIBS + set(libs) + if(DEFINED LLVM_LIBRARIES) + set(libs LLVM_LIBRARIES) + elseif(DEFINED LLVM_LIBS) + set(libs LLVM_LIBS) + endif() + + # Check the following variables: + # LLVM_INCLUDE_DIRS + # LLVM_INCLUDES + # LLVM_INCLUDE_DIR + set(includes) + if(DEFINED LLVM_INCLUDE_DIRS) + set(includes LLVM_INCLUDE_DIRS) + elseif(DEFINED LLVM_INCLUDES) + set(includes LLVM_INCLUDES) + elseif(DEFINED LLVM_INCLUDE_DIR) + set(includes LLVM_INCLUDE_DIR) + endif() + + # Check the following variables: + # LLVM_DEFINITIONS + set(definitions) + if(DEFINED LLVM_DEFINITIONS) + set(definitions LLVM_DEFINITIONS) + endif() + + set(PACKAGE_INCLUDE_DIRS "${${includes}}") + set(PACKAGE_DEFINITIONS "${${definitions}}") + set(PACKAGE_LIBRARIES "${${libs}}") +endif() diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 5ee85bf9c..4d541a348 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -20,14 +20,16 @@ import glob import os import re -from .. import mesonlib +from .. import mesonlib, mlog from ..mesonlib import version_compare, stringlistify, extract_as_list, MachineChoice from .base import ( DependencyException, DependencyMethods, ExternalDependency, PkgConfigDependency, - strip_system_libdirs, ConfigToolDependency, + strip_system_libdirs, ConfigToolDependency, CMakeDependency ) from .misc import ThreadDependency +from typing import List, Tuple + def get_shared_library_suffix(environment, native): """This is only gauranteed to work for languages that compile to machine @@ -192,7 +194,7 @@ class GMockDependency(ExternalDependency): return [DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM] -class LLVMDependency(ConfigToolDependency): +class LLVMDependencyConfigTool(ConfigToolDependency): """ LLVM uses a special tool, llvm-config, which has arguments for getting c args, cxx args, and ldargs as well as version. @@ -399,6 +401,66 @@ class LLVMDependency(ConfigToolDependency): return 'modules: ' + ', '.join(self.module_details) return '' +class LLVMDependencyCMake(CMakeDependency): + def __init__(self, env, kwargs): + self.llvm_modules = stringlistify(extract_as_list(kwargs, 'modules')) + self.llvm_opt_modules = stringlistify(extract_as_list(kwargs, 'optional_modules')) + super().__init__(name='LLVM', environment=env, language='cpp', kwargs=kwargs) + + # Extract extra include directories and definitions + inc_dirs = self.get_cmake_var('PACKAGE_INCLUDE_DIRS') + defs = self.get_cmake_var('PACKAGE_DEFINITIONS') + temp = ['-I' + x for x in inc_dirs] + defs + self.compile_args += [x for x in temp if x not in self.compile_args] + self._add_sub_dependency(ThreadDependency, env, kwargs) + + def _main_cmake_file(self) -> str: + # Use a custom CMakeLists.txt for LLVM + return 'CMakeListsLLVM.txt' + + def _extra_cmake_opts(self) -> List[str]: + return ['-DLLVM_MESON_MODULES={}'.format(';'.join(self.llvm_modules + self.llvm_opt_modules))] + + def _map_module_list(self, modules: List[Tuple[str, bool]]) -> List[Tuple[str, bool]]: + res = [] + for mod, required in modules: + cm_targets = self.get_cmake_var('MESON_LLVM_TARGETS_{}'.format(mod)) + if not cm_targets: + if required: + raise self._gen_exception('LLVM module {} was not found'.format(mod)) + else: + mlog.warning('Optional LLVM module', mlog.bold(mod), 'was not found') + continue + for i in cm_targets: + res += [(i, required)] + return res + + def _original_module_name(self, module: str) -> str: + orig_name = self.get_cmake_var('MESON_TARGET_TO_LLVM_{}'.format(module)) + if orig_name: + return orig_name[0] + return module + +class LLVMDependency(ExternalDependency): + def __init__(self, env, kwargs): + super().__init__('LLVM', env, 'cpp', kwargs) + + @classmethod + def _factory(cls, env, kwargs): + methods = cls._process_method_kw(kwargs) + candidates = [] + + if DependencyMethods.CMAKE in methods: + candidates.append(functools.partial(LLVMDependencyCMake, env, kwargs)) + + if DependencyMethods.CONFIG_TOOL in methods: + candidates.append(functools.partial(LLVMDependencyConfigTool, env, kwargs)) + + return candidates + + @staticmethod + def get_methods(): + return [DependencyMethods.CMAKE, DependencyMethods.CONFIG_TOOL] class ValgrindDependency(PkgConfigDependency): ''' diff --git a/setup.py b/setup.py index 07bd3dde5..aab740e93 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ packages = ['mesonbuild', 'mesonbuild.modules', 'mesonbuild.scripts', 'mesonbuild.wrap'] -package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakePathInfo.txt']} +package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakeListsLLVM.txt', 'data/CMakePathInfo.txt']} data_files = [] if sys.platform != 'win32': # Only useful on UNIX-like systems diff --git a/test cases/common/161 config tool variable/meson.build b/test cases/common/161 config tool variable/meson.build index 0643042d8..95841179c 100644 --- a/test cases/common/161 config tool variable/meson.build +++ b/test cases/common/161 config tool variable/meson.build @@ -15,7 +15,7 @@ project('config tool variable', 'cpp') -dep_llvm = dependency('llvm', required : false) +dep_llvm = dependency('llvm', method : 'config-tool', required : false) if not dep_llvm.found() error('MESON_SKIP_TEST LLVM not installed.') endif diff --git a/test cases/frameworks/15 llvm/meson.build b/test cases/frameworks/15 llvm/meson.build index fc750842b..af94daeae 100644 --- a/test cases/frameworks/15 llvm/meson.build +++ b/test cases/frameworks/15 llvm/meson.build @@ -35,7 +35,7 @@ foreach static : [true, false] llvm_dep = dependency( 'llvm', modules : ['bitwriter', 'asmprinter', 'executionengine', 'target', - 'mcjit', 'nativecodegen'], + 'mcjit', 'nativecodegen', 'amdgpu'], required : false, static : static, ) diff --git a/test cases/unit/47 native file binary/meson.build b/test cases/unit/47 native file binary/meson.build index 4489ac1e6..c9b6e5de4 100644 --- a/test cases/unit/47 native file binary/meson.build +++ b/test cases/unit/47 native file binary/meson.build @@ -8,7 +8,7 @@ if case == 'find_program' assert(result.stdout().strip().endswith('12345'), 'Didn\'t load bash from config file') elif case == 'config_dep' add_languages('cpp') - dep = dependency('llvm') + dep = dependency('llvm', method : 'config-tool') assert(dep.get_configtool_variable('version').endswith('12345'), 'Didn\'t load llvm from config file') elif case == 'python3' prog = import('python3').find_python()