diff options
author | Daniel Mensinger <daniel@mensinger-ka.de> | 2019-02-12 18:51:09 +0100 |
---|---|---|
committer | Daniel Mensinger <daniel@mensinger-ka.de> | 2019-02-26 09:33:26 +0100 |
commit | cdc338b74337e29c903bb73b61dc6f22877208e7 (patch) | |
tree | f1e3ab563c48aeddd94e51b7612dc210bd3a1256 | |
parent | 6efc94b5dd0d360218eec3fae98caf3a87a757b9 (diff) | |
download | meson-cdc338b74337e29c903bb73b61dc6f22877208e7.zip meson-cdc338b74337e29c903bb73b61dc6f22877208e7.tar.gz meson-cdc338b74337e29c903bb73b61dc6f22877208e7.tar.bz2 |
Preliminary CMake module search
-rw-r--r-- | mesonbuild/dependencies/base.py | 187 | ||||
-rw-r--r-- | mesonbuild/dependencies/data/CMakeLists.txt | 18 | ||||
-rw-r--r-- | mesonbuild/dependencies/data/CMakePathInfo.txt | 29 | ||||
-rw-r--r-- | setup.py | 2 |
4 files changed, 211 insertions, 25 deletions
diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index a192bd4..e98c122 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -37,6 +37,7 @@ 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 +from typing import List # These must be defined in this file to avoid cyclical references. packages = {} @@ -916,12 +917,17 @@ class CMakeDependency(ExternalDependency): # multiple times in the same Meson invocation. class_cmakebin = PerMachine(None, None, None) class_cmakevers = PerMachine(None, None, None) + class_cmakeinfo = PerMachine(None, None, None) # We cache all pkg-config subprocess invocations to avoid redundant calls cmake_cache = {} # Version string for the minimum CMake version class_cmake_version = '>=3.4' # CMake generators to try (empty for no generator) class_cmake_generators = ['', 'Ninja', 'Unix Makefiles', 'Visual Studio 10 2010'] + class_working_generator = None + # Cache for os.listdir and os.path.exists + class_listdir_cache = {} + class_isdir_cache = {} def _gen_exception(self, msg): return DependencyException('Dependency {} not found: {}'.format(self.name, msg)) @@ -934,6 +940,7 @@ class CMakeDependency(ExternalDependency): # stored in the pickled coredata and recovered. self.cmakebin = None self.cmakevers = None + self.cmakeinfo = None # Dict of CMake variables: '<var_name>': ['list', 'of', 'values'] self.vars = {} @@ -1009,6 +1016,12 @@ class CMakeDependency(ExternalDependency): mlog.debug(msg) return + if CMakeDependency.class_cmakeinfo[for_machine] is None: + CMakeDependency.class_cmakeinfo[for_machine] = self._get_cmake_info() + self.cmakeinfo = CMakeDependency.class_cmakeinfo[for_machine] + 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', []) @@ -1021,6 +1034,8 @@ class CMakeDependency(ExternalDependency): 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))] + if not self._preliminary_find_check(name, cm_path): + return self._detect_dep(name, modules, cm_args) def __repr__(self): @@ -1028,6 +1043,150 @@ class CMakeDependency(ExternalDependency): return s.format(self.__class__.__name__, self.name, self.is_found, self.version_reqs) + def _get_cmake_info(self): + mlog.debug("Extracting basic cmake information") + res = {} + + # Try different CMake generators since specifying no generator may fail + # in cygwin for some reason + gen_list = [] + # First try the last working generator + if CMakeDependency.class_working_generator is not None: + gen_list += [CMakeDependency.class_working_generator] + gen_list += CMakeDependency.class_cmake_generators + + for i in gen_list: + mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto')) + + # Prepare options + cmake_opts = ['--trace-expand', '.'] + if len(i) > 0: + cmake_opts = ['-G', i] + cmake_opts + + # Run CMake + ret1, out1, err1 = self._call_cmake(cmake_opts, 'CMakePathInfo.txt') + + # Current generator was successful + if ret1 == 0: + break + + mlog.debug('CMake failed to gather system information for generator {} with error code {}'.format(i, ret1)) + mlog.debug('OUT:\n{}\n\n\nERR:\n{}\n\n'.format(out1, err1)) + + # Check if any generator succeeded + if ret1 != 0: + return None + + try: + # First parse the trace + lexer1 = self._lex_trace(err1) + + # Primary pass -- parse all invocations of set + for l in lexer1: + if l.func == 'set': + self._cmake_set(l) + except: + return None + + # Extract the variables and sanity check them + module_paths = list(sorted(list(set(self.get_cmake_var('MESON_PATHS_LIST'))))) + module_paths = list(filter(lambda x: os.path.isdir(x), module_paths)) + archs = self.get_cmake_var('MESON_ARCH_LIST') + + common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share'] + for i in archs: + common_paths += [os.path.join('lib', i)] + + res = { + 'module_paths': module_paths, + 'cmake_root': self.get_cmake_var('MESON_CMAKE_ROOT')[0], + 'archs': archs, + 'common_paths': common_paths + } + + mlog.debug(' -- Module search paths: {}'.format(res['module_paths'])) + mlog.debug(' -- CMake root: {}'.format(res['cmake_root'])) + mlog.debug(' -- CMake architectures: {}'.format(res['archs'])) + mlog.debug(' -- CMake lib search paths: {}'.format(res['common_paths'])) + + # Reset variables + self.vars = {} + return res + + def _cached_listdir(self, path: str) -> List[str]: + if path not in CMakeDependency.class_listdir_cache: + CMakeDependency.class_listdir_cache[path] = list(map(lambda x: x.lower(), os.listdir(path))) + return CMakeDependency.class_listdir_cache[path] + + def _cached_isdir(self, path: str) -> bool: + if path not in CMakeDependency.class_isdir_cache: + CMakeDependency.class_isdir_cache[path] = os.path.isdir(path) + return CMakeDependency.class_isdir_cache[path] + + def _preliminary_find_check(self, name: str, module_path: List[str]) -> bool: + lname = str(name).lower() + # Checks <path>, <path>/cmake, <path>/CMake + def find_module(path: str) -> bool: + for i in [path, os.path.join(path, 'cmake'), os.path.join(path, 'CMake')]: + if not self._cached_isdir(i): + continue + + for j in ['Find{}.cmake', '{}Config.cmake', '{}-config.cmake']: + if os.path.isfile(os.path.join(i, j.format(name))): + return True + return False + + # Search in <path>/(lib/<arch>|lib*|share) for cmake files + def search_lib_dirs(path: str) -> bool: + for i in [os.path.join(path, x) for x in self.cmakeinfo['common_paths']]: + if not self._cached_isdir(i): + continue + + # Check <path>/(lib/<arch>|lib*|share)/cmake/<name>*/ + cm_dir = os.path.join(i, 'cmake') + if self._cached_isdir(cm_dir): + content = self._cached_listdir(cm_dir) + content = list(filter(lambda x: x.startswith(lname), content)) + for k in content: + if find_module(os.path.join(cm_dir, k)): + return True + + # <path>/(lib/<arch>|lib*|share)/<name>*/ + # <path>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/ + content = self._cached_listdir(i) + content = list(filter(lambda x: x.startswith(lname), content)) + for k in content: + if find_module(os.path.join(i, k)): + return True + + return False + + # Check the user provided and system module paths + for i in module_path + [os.path.join(self.cmakeinfo['cmake_root'], 'Modules')]: + if find_module(i): + return True + + # Check the system paths + for i in self.cmakeinfo['module_paths']: + if find_module(i): + return True + + if search_lib_dirs(i): + return True + + content = self._cached_listdir(i) + content = list(filter(lambda x: x.startswith(lname), content)) + for k in content: + if search_lib_dirs(os.path.join(i, k)): + return True + + # Check the environment path + env_path = os.environ.get('{}_DIR'.format(name)) + if env_path and find_module(env_path): + return True + + return False + 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) @@ -1040,11 +1199,17 @@ class CMakeDependency(ExternalDependency): # Try different CMake generators since specifying no generator may fail # in cygwin for some reason - for i in CMakeDependency.class_cmake_generators: + gen_list = [] + # First try the last working generator + if CMakeDependency.class_working_generator is not None: + gen_list += [CMakeDependency.class_working_generator] + gen_list += CMakeDependency.class_cmake_generators + + for i in gen_list: mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto')) # Prepare options - cmake_opts = ['--trace-expand', '-DNAME={}'.format(name)] + args + ['.'] + cmake_opts = ['--trace-expand', '-DNAME={}'.format(name), '-DARCHS={}'.format(';'.join(self.cmakeinfo['archs']))] + args + ['.'] if len(i) > 0: cmake_opts = ['-G', i] + cmake_opts @@ -1053,6 +1218,7 @@ class CMakeDependency(ExternalDependency): # Current generator was successful if ret1 == 0: + CMakeDependency.class_working_generator = i break mlog.debug('CMake failed for generator {} and package {} with error code {}'.format(i, name, ret1)) @@ -1221,7 +1387,7 @@ class CMakeDependency(ExternalDependency): def get_cmake_var(self, var): # Return the value of the CMake variable var or an empty list if var does not exist - for var in self.vars: + if var in self.vars: return self.vars[var] return [] @@ -1456,10 +1622,11 @@ set(CMAKE_SIZEOF_VOID_P "{}") # Copy the CMakeLists.txt cmake_lists = '{}/CMakeLists.txt'.format(build_dir) - if not os.path.exists(cmake_lists): - dir_path = os.path.dirname(os.path.realpath(__file__)) - src_cmake = '{}/data/{}'.format(dir_path, cmake_file) - shutil.copyfile(src_cmake, cmake_lists) + dir_path = os.path.dirname(os.path.realpath(__file__)) + src_cmake = '{}/data/{}'.format(dir_path, cmake_file) + if os.path.exists(cmake_lists): + os.remove(cmake_lists) + shutil.copyfile(src_cmake, cmake_lists) self._setup_compiler(build_dir) self._reset_cmake_cache(build_dir) @@ -1485,9 +1652,9 @@ set(CMAKE_SIZEOF_VOID_P "{}") # First check if cached, if not call the real cmake function cache = CMakeDependency.cmake_cache - if (self.cmakebin, targs, fenv) not in cache: - cache[(self.cmakebin, targs, fenv)] = self._call_cmake_real(args, cmake_file, env) - return cache[(self.cmakebin, targs, fenv)] + if (self.cmakebin, targs, cmake_file, fenv) not in cache: + cache[(self.cmakebin, targs, cmake_file, fenv)] = self._call_cmake_real(args, cmake_file, env) + return cache[(self.cmakebin, targs, cmake_file, fenv)] @staticmethod def get_methods(): diff --git a/mesonbuild/dependencies/data/CMakeLists.txt b/mesonbuild/dependencies/data/CMakeLists.txt index 6f51681..64f5b23 100644 --- a/mesonbuild/dependencies/data/CMakeLists.txt +++ b/mesonbuild/dependencies/data/CMakeLists.txt @@ -1,16 +1,5 @@ cmake_minimum_required(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION} ) -# Inspired by CMakeDetermineCompilerABI.cmake to set CMAKE_LIBRARY_ARCHITECTURE -set(LIB_ARCH_LIST) -if(CMAKE_LIBRARY_ARCHITECTURE_REGEX) - file(GLOB implicit_dirs RELATIVE /lib /lib/*-linux-gnu* ) - foreach(dir ${implicit_dirs}) - if("${dir}" MATCHES "${CMAKE_LIBRARY_ARCHITECTURE_REGEX}") - list(APPEND LIB_ARCH_LIST "${dir}") - endif() - endforeach() -endif() - set(PACKAGE_FOUND FALSE) set(_packageName "${NAME}") string(TOUPPER "${_packageName}" PACKAGE_NAME) @@ -18,12 +7,13 @@ string(TOUPPER "${_packageName}" PACKAGE_NAME) while(TRUE) find_package("${NAME}" QUIET) - if(${_packageName}_FOUND OR ${PACKAGE_NAME}_FOUND OR "${LIB_ARCH_LIST}" STREQUAL "") + # ARCHS has to be set via the CMD interface + if(${_packageName}_FOUND OR ${PACKAGE_NAME}_FOUND OR "${ARCHS}" STREQUAL "") break() endif() - list(GET LIB_ARCH_LIST 0 CMAKE_LIBRARY_ARCHITECTURE) - list(REMOVE_AT LIB_ARCH_LIST 0) + list(GET ARCHS 0 CMAKE_LIBRARY_ARCHITECTURE) + list(REMOVE_AT ARCHS 0) endwhile() if(${_packageName}_FOUND OR ${PACKAGE_NAME}_FOUND) diff --git a/mesonbuild/dependencies/data/CMakePathInfo.txt b/mesonbuild/dependencies/data/CMakePathInfo.txt new file mode 100644 index 0000000..713c2da --- /dev/null +++ b/mesonbuild/dependencies/data/CMakePathInfo.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}) + +set(TMP_PATHS_LIST) +list(APPEND TMP_PATHS_LIST ${CMAKE_PREFIX_PATH}) +list(APPEND TMP_PATHS_LIST ${CMAKE_FRAMEWORK_PATH}) +list(APPEND TMP_PATHS_LIST ${CMAKE_APPBUNDLE_PATH}) +list(APPEND TMP_PATHS_LIST $ENV{CMAKE_PREFIX_PATH}) +list(APPEND TMP_PATHS_LIST $ENV{CMAKE_FRAMEWORK_PATH}) +list(APPEND TMP_PATHS_LIST $ENV{CMAKE_APPBUNDLE_PATH}) +list(APPEND TMP_PATHS_LIST ${CMAKE_SYSTEM_PREFIX_PATH}) +list(APPEND TMP_PATHS_LIST ${CMAKE_SYSTEM_FRAMEWORK_PATH}) +list(APPEND TMP_PATHS_LIST ${CMAKE_SYSTEM_APPBUNDLE_PATH}) + +set(LIB_ARCH_LIST) +if(CMAKE_LIBRARY_ARCHITECTURE_REGEX) + file(GLOB implicit_dirs RELATIVE /lib /lib/*-linux-gnu* ) + foreach(dir ${implicit_dirs}) + if("${dir}" MATCHES "${CMAKE_LIBRARY_ARCHITECTURE_REGEX}") + list(APPEND LIB_ARCH_LIST "${dir}") + endif() + endforeach() +endif() + +# "Export" these variables: +set(MESON_ARCH_LIST ${LIB_ARCH_LIST}) +set(MESON_PATHS_LIST ${TMP_PATHS_LIST}) +set(MESON_CMAKE_ROOT ${CMAKE_ROOT}) + +message(STATUS ${TMP_PATHS_LIST}) @@ -35,7 +35,7 @@ packages = ['mesonbuild', 'mesonbuild.modules', 'mesonbuild.scripts', 'mesonbuild.wrap'] -package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt']} +package_data = {'mesonbuild.dependencies': ['data/CMakeLists.txt', 'data/CMakePathInfo.txt']} data_files = [] if sys.platform != 'win32': # Only useful on UNIX-like systems |