aboutsummaryrefslogtreecommitdiffstats
path: root/meson/mesonbuild/dependencies/cmake.py
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /meson/mesonbuild/dependencies/cmake.py
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'meson/mesonbuild/dependencies/cmake.py')
-rw-r--r--meson/mesonbuild/dependencies/cmake.py718
1 files changed, 718 insertions, 0 deletions
diff --git a/meson/mesonbuild/dependencies/cmake.py b/meson/mesonbuild/dependencies/cmake.py
new file mode 100644
index 000000000..047950da5
--- /dev/null
+++ b/meson/mesonbuild/dependencies/cmake.py
@@ -0,0 +1,718 @@
+# Copyright 2013-2021 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName
+from ..mesonlib import is_windows, MesonException, OptionKey, PerMachine, stringlistify, extract_as_list
+from ..mesondata import mesondata
+from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args, CMakeTarget
+from .. import mlog
+from pathlib import Path
+import functools
+import re
+import os
+import shutil
+import textwrap
+import typing as T
+
+if T.TYPE_CHECKING:
+ from ..environment import Environment
+ from ..envconfig import MachineInfo
+
+class CMakeInfo(T.NamedTuple):
+ module_paths: T.List[str]
+ cmake_root: str
+ archs: T.List[str]
+ common_paths: T.List[str]
+
+class CMakeDependency(ExternalDependency):
+ # The class's copy of the CMake path. Avoids having to search for it
+ # multiple times in the same Meson invocation.
+ class_cmakeinfo: PerMachine[T.Optional[CMakeInfo]] = PerMachine(None, None)
+ # 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: T.Optional[str] = None
+
+ def _gen_exception(self, msg: str) -> DependencyException:
+ return DependencyException(f'Dependency {self.name} not found: {msg}')
+
+ def _main_cmake_file(self) -> str:
+ return 'CMakeLists.txt'
+
+ def _extra_cmake_opts(self) -> T.List[str]:
+ return []
+
+ def _map_module_list(self, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]]) -> T.List[T.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.
+ #
+ # Both the modules and components inputs contain the original lists.
+ return modules
+
+ def _map_component_list(self, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]]) -> T.List[T.Tuple[str, bool]]:
+ # Map the input components list to something else. This
+ # function will be executed BEFORE the initial CMake interpreter
+ # pass. Thus variables from the CMakeLists.txt can NOT be accessed.
+ #
+ # Both the modules and components inputs contain the original lists.
+ return components
+
+ 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: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
+ # Gather a list of all languages to support
+ self.language_list = [] # type: T.List[str]
+ if language is None:
+ compilers = None
+ if kwargs.get('native', False):
+ compilers = environment.coredata.compilers.build
+ else:
+ compilers = environment.coredata.compilers.host
+
+ candidates = ['c', 'cpp', 'fortran', 'objc', 'objcxx']
+ self.language_list += [x for x in candidates if x in compilers]
+ else:
+ self.language_list += [language]
+
+ # Add additional languages if required
+ if 'fortran' in self.language_list:
+ self.language_list += ['c']
+
+ # Ensure that the list is unique
+ self.language_list = list(set(self.language_list))
+
+ super().__init__(DependencyTypeName('cmake'), environment, kwargs, language=language)
+ self.name = name
+ self.is_libtool = False
+ # Store a copy of the CMake path on the object itself so it is
+ # stored in the pickled coredata and recovered.
+ self.cmakebin: T.Optional[CMakeExecutor] = None
+ self.cmakeinfo: T.Optional[CMakeInfo] = None
+
+ # Where all CMake "build dirs" are located
+ self.cmake_root_dir = environment.scratch_dir
+
+ # T.List of successfully found modules
+ self.found_modules: T.List[str] = []
+
+ # Initialize with None before the first return to avoid
+ # AttributeError exceptions in derived classes
+ self.traceparser: T.Optional[CMakeTraceParser] = None
+
+ # TODO further evaluate always using MachineChoice.BUILD
+ self.cmakebin = CMakeExecutor(environment, CMakeDependency.class_cmake_version, self.for_machine, silent=self.silent)
+ if not self.cmakebin.found():
+ self.cmakebin = None
+ msg = f'CMake binary for machine {self.for_machine} not found. Giving up.'
+ if self.required:
+ raise DependencyException(msg)
+ mlog.debug(msg)
+ return
+
+ # Setup the trace parser
+ self.traceparser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir())
+
+ cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args'))
+ cm_args = check_cmake_args(cm_args)
+ if CMakeDependency.class_cmakeinfo[self.for_machine] is None:
+ CMakeDependency.class_cmakeinfo[self.for_machine] = self._get_cmake_info(cm_args)
+ self.cmakeinfo = CMakeDependency.class_cmakeinfo[self.for_machine]
+ if self.cmakeinfo is None:
+ raise self._gen_exception('Unable to obtain CMake system information')
+
+ package_version = kwargs.get('cmake_package_version', '')
+ if not isinstance(package_version, str):
+ raise DependencyException('Keyword "cmake_package_version" must be a string.')
+ components = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'components'))]
+ 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_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.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path))
+ if not self._preliminary_find_check(name, cm_path, self.cmakebin.get_cmake_prefix_paths(), environment.machines[self.for_machine]):
+ mlog.debug('Preliminary CMake check failed. Aborting.')
+ return
+ self._detect_dep(name, package_version, modules, components, cm_args)
+
+ def __repr__(self) -> str:
+ return f'<{self.__class__.__name__} {self.name}: {self.is_found} {self.version_reqs}>'
+
+ def _get_cmake_info(self, cm_args: T.List[str]) -> T.Optional[CMakeInfo]:
+ mlog.debug("Extracting basic cmake information")
+
+ # 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
+
+ temp_parser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir())
+ toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir())
+ toolchain.write()
+
+ for i in gen_list:
+ mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto'))
+
+ # Prepare options
+ cmake_opts = temp_parser.trace_args() + toolchain.get_cmake_args() + ['.']
+ cmake_opts += cm_args
+ 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:
+ CMakeDependency.class_working_generator = i
+ break
+
+ mlog.debug(f'CMake failed to gather system information for generator {i} with error code {ret1}')
+ mlog.debug(f'OUT:\n{out1}\n\n\nERR:\n{err1}\n\n')
+
+ # Check if any generator succeeded
+ if ret1 != 0:
+ return None
+
+ try:
+ temp_parser.parse(err1)
+ except MesonException:
+ return None
+
+ def process_paths(l: T.List[str]) -> T.Set[str]:
+ if is_windows():
+ # Cannot split on ':' on Windows because its in the drive letter
+ tmp = [x.split(os.pathsep) for x in l]
+ else:
+ # https://github.com/mesonbuild/meson/issues/7294
+ tmp = [re.split(r':|;', x) for x in l]
+ flattened = [x for sublist in tmp for x in sublist]
+ return set(flattened)
+
+ # Extract the variables and sanity check them
+ root_paths_set = process_paths(temp_parser.get_cmake_var('MESON_FIND_ROOT_PATH'))
+ root_paths_set.update(process_paths(temp_parser.get_cmake_var('MESON_CMAKE_SYSROOT')))
+ root_paths = sorted(root_paths_set)
+ root_paths = [x for x in root_paths if os.path.isdir(x)]
+ module_paths_set = process_paths(temp_parser.get_cmake_var('MESON_PATHS_LIST'))
+ rooted_paths: T.List[str] = []
+ for j in [Path(x) for x in root_paths]:
+ for p in [Path(x) for x in module_paths_set]:
+ rooted_paths.append(str(j / p.relative_to(p.anchor)))
+ module_paths = sorted(module_paths_set.union(rooted_paths))
+ module_paths = [x for x in module_paths if os.path.isdir(x)]
+ archs = temp_parser.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 = CMakeInfo(
+ module_paths=module_paths,
+ cmake_root=temp_parser.get_cmake_var('MESON_CMAKE_ROOT')[0],
+ archs=archs,
+ common_paths=common_paths,
+ )
+
+ mlog.debug(f' -- Module search paths: {res.module_paths}')
+ mlog.debug(f' -- CMake root: {res.cmake_root}')
+ mlog.debug(f' -- CMake architectures: {res.archs}')
+ mlog.debug(f' -- CMake lib search paths: {res.common_paths}')
+
+ return res
+
+ @staticmethod
+ @functools.lru_cache(maxsize=None)
+ def _cached_listdir(path: str) -> T.Tuple[T.Tuple[str, str], ...]:
+ try:
+ return tuple((x, str(x).lower()) for x in os.listdir(path))
+ except OSError:
+ return tuple()
+
+ @staticmethod
+ @functools.lru_cache(maxsize=None)
+ def _cached_isdir(path: str) -> bool:
+ try:
+ return os.path.isdir(path)
+ except OSError:
+ return False
+
+ def _preliminary_find_check(self, name: str, module_path: T.List[str], prefix_path: T.List[str], machine: 'MachineInfo') -> 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
+
+ # Check the directory case insensitive
+ content = self._cached_listdir(i)
+ candidates = ['Find{}.cmake', '{}Config.cmake', '{}-config.cmake']
+ candidates = [x.format(name).lower() for x in candidates]
+ if any([x[1] in candidates for x in content]):
+ 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 = tuple(x for x in content if x[1].startswith(lname))
+ for k in content:
+ if find_module(os.path.join(cm_dir, k[0])):
+ return True
+
+ # <path>/(lib/<arch>|lib*|share)/<name>*/
+ # <path>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/
+ content = self._cached_listdir(i)
+ content = tuple(x for x in content if x[1].startswith(lname))
+ for k in content:
+ if find_module(os.path.join(i, k[0])):
+ 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 user provided prefix paths
+ for i in prefix_path:
+ if search_lib_dirs(i):
+ return True
+
+ # Check PATH
+ system_env = [] # type: T.List[str]
+ for i in os.environ.get('PATH', '').split(os.pathsep):
+ if i.endswith('/bin') or i.endswith('\\bin'):
+ i = i[:-4]
+ if i.endswith('/sbin') or i.endswith('\\sbin'):
+ i = i[:-5]
+ system_env += [i]
+
+ # Check the system paths
+ for i in self.cmakeinfo.module_paths + system_env:
+ if find_module(i):
+ return True
+
+ if search_lib_dirs(i):
+ return True
+
+ content = self._cached_listdir(i)
+ content = tuple(x for x in content if x[1].startswith(lname))
+ for k in content:
+ if search_lib_dirs(os.path.join(i, k[0])):
+ return True
+
+ # Mac framework support
+ if machine.is_darwin():
+ for j in [f'{lname}.framework', f'{lname}.app']:
+ for k in content:
+ if k[1] != j:
+ continue
+ if find_module(os.path.join(i, k[0], 'Resources')) or find_module(os.path.join(i, k[0], 'Version')):
+ return True
+
+ # Check the environment path
+ env_path = os.environ.get(f'{name}_DIR')
+ if env_path and find_module(env_path):
+ return True
+
+ return False
+
+ def _detect_dep(self, name: str, package_version: str, modules: T.List[T.Tuple[str, bool]], components: T.List[T.Tuple[str, bool]], args: T.List[str]) -> None:
+ # Detect a dependency with CMake using the '--find-package' mode
+ # and the trace output (stderr)
+ #
+ # When the trace output is enabled CMake prints all functions with
+ # parameters to stderr as they are executed. Since CMake 3.4.0
+ # variables ("${VAR}") are also replaced in the trace output.
+ mlog.debug('\nDetermining dependency {!r} with CMake executable '
+ '{!r}'.format(name, self.cmakebin.executable_path()))
+
+ # 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
+
+ # Map the components
+ comp_mapped = self._map_component_list(modules, components)
+ toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir())
+ toolchain.write()
+
+ for i in gen_list:
+ mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto'))
+
+ # Prepare options
+ cmake_opts = []
+ cmake_opts += [f'-DNAME={name}']
+ cmake_opts += ['-DARCHS={}'.format(';'.join(self.cmakeinfo.archs))]
+ cmake_opts += [f'-DVERSION={package_version}']
+ cmake_opts += ['-DCOMPS={}'.format(';'.join([x[0] for x in comp_mapped]))]
+ cmake_opts += args
+ cmake_opts += self.traceparser.trace_args()
+ cmake_opts += toolchain.get_cmake_args()
+ cmake_opts += self._extra_cmake_opts()
+ cmake_opts += ['.']
+ if len(i) > 0:
+ cmake_opts = ['-G', i] + cmake_opts
+
+ # Run CMake
+ ret1, out1, err1 = self._call_cmake(cmake_opts, self._main_cmake_file())
+
+ # Current generator was successful
+ if ret1 == 0:
+ CMakeDependency.class_working_generator = i
+ break
+
+ mlog.debug(f'CMake failed for generator {i} and package {name} with error code {ret1}')
+ mlog.debug(f'OUT:\n{out1}\n\n\nERR:\n{err1}\n\n')
+
+ # Check if any generator succeeded
+ if ret1 != 0:
+ return
+
+ try:
+ self.traceparser.parse(err1)
+ except CMakeException as e:
+ e2 = self._gen_exception(str(e))
+ if self.required:
+ raise
+ else:
+ self.compile_args = []
+ self.link_args = []
+ self.is_found = False
+ self.reason = e2
+ return
+
+ # Whether the package is found or not is always stored in PACKAGE_FOUND
+ self.is_found = self.traceparser.var_to_bool('PACKAGE_FOUND')
+ if not self.is_found:
+ return
+
+ # Try to detect the version
+ vers_raw = self.traceparser.get_cmake_var('PACKAGE_VERSION')
+
+ if len(vers_raw) > 0:
+ 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, components)
+ autodetected_module_list = False
+
+ # Check if we need a DEBUG or RELEASE CMake dependencies
+ is_debug = False
+ if OptionKey('b_vscrt') in self.env.coredata.options:
+ is_debug = self.env.coredata.get_option(OptionKey('buildtype')) == 'debug'
+ if self.env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}:
+ is_debug = True
+ else:
+ # Don't directly assign to is_debug to make mypy happy
+ debug_opt = self.env.coredata.get_option(OptionKey('debug'))
+ assert isinstance(debug_opt, bool)
+ is_debug = debug_opt
+
+ # Try guessing a CMake target if none is provided
+ if len(modules) == 0:
+ for i in self.traceparser.targets:
+ tg = i.lower()
+ lname = name.lower()
+ if f'{lname}::{lname}' == tg or lname == tg.replace('::', ''):
+ mlog.debug(f'Guessed CMake target \'{i}\'')
+ modules = [(i, True)]
+ autodetected_module_list = True
+ break
+
+ # Failed to guess a target --> try the old-style method
+ if len(modules) == 0:
+ # Warn when there might be matching imported targets but no automatic match was used
+ partial_modules: T.List[CMakeTarget] = []
+ for k, v in self.traceparser.targets.items():
+ tg = k.lower()
+ lname = name.lower()
+ if tg.startswith(f'{lname}::'):
+ partial_modules += [v]
+ if partial_modules:
+ mlog.warning(textwrap.dedent(f'''\
+ Could not find and exact match for the CMake dependency {name}.
+
+ However, Meson found the following partial matches:
+
+ {[x.name for x in partial_modules]}
+
+ Using imported is recommended, since this approach is less error prone
+ and better supported by Meson. Consider explicitly specifying one of
+ these in the dependency call with:
+
+ dependency('{name}', modules: ['{name}::<name>', ...])
+
+ Meson will now continue to use the old-style {name}_LIBRARIES CMake
+ variables to extract the dependency information since no explicit
+ target is currently specified.
+
+ '''))
+ mlog.debug('More info for the partial match targets:')
+ for tgt in partial_modules:
+ mlog.debug(tgt)
+
+
+ incDirs = [x for x in self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') if x]
+ defs = [x for x in self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') if x]
+ libs_raw = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x]
+
+ # CMake has a "fun" API, where certain keywords describing
+ # configurations can be in the *_LIBRARIES vraiables. See:
+ # - https://github.com/mesonbuild/meson/issues/9197
+ # - https://gitlab.freedesktop.org/libnice/libnice/-/issues/140
+ # - https://cmake.org/cmake/help/latest/command/target_link_libraries.html#overview (the last point in the section)
+ libs: T.List[str] = []
+ cfg_matches = True
+ cm_tag_map = {'debug': is_debug, 'optimized': not is_debug, 'general': True}
+ for i in libs_raw:
+ if i.lower() in cm_tag_map:
+ cfg_matches = cm_tag_map[i.lower()]
+ continue
+ if cfg_matches:
+ libs += [i]
+ # According to the CMake docs, a keyword only works for the
+ # directly the following item and all items without a keyword
+ # are implizitly `general`
+ cfg_matches = True
+
+ # Try to use old style variables if no module is specified
+ if len(libs) > 0:
+ self.compile_args = list(map(lambda x: f'-I{x}', incDirs)) + defs
+ self.link_args = libs
+ mlog.debug(f'using old-style CMake variables for dependency {name}')
+ mlog.debug(f'Include Dirs: {incDirs}')
+ mlog.debug(f'Compiler Definitions: {defs}')
+ mlog.debug(f'Libraries: {libs}')
+ return
+
+ # Even the old-style approach failed. Nothing else we can do here
+ self.is_found = False
+ raise self._gen_exception('CMake: failed to guess a CMake target for {}.\n'
+ 'Try to explicitly specify one or more targets with the "modules" property.\n'
+ 'Valid targets are:\n{}'.format(name, list(self.traceparser.targets.keys())))
+
+ # Set dependencies with CMake targets
+ # recognise arguments we should pass directly to the linker
+ reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-pthread|-delayload:[a-zA-Z0-9_\.]+|[a-zA-Z0-9_]+\.lib)$')
+ reg_is_maybe_bare_lib = re.compile(r'^[a-zA-Z0-9_]+$')
+ processed_targets = []
+ incDirs = []
+ compileDefinitions = []
+ compileOptions = []
+ libraries = []
+ for i, required in modules:
+ if i not in self.traceparser.targets:
+ if not required:
+ mlog.warning('CMake: T.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(self._original_module_name(i), name, list(self.traceparser.targets.keys())))
+
+ targets = [i]
+ if not autodetected_module_list:
+ self.found_modules += [i]
+
+ while len(targets) > 0:
+ curr = targets.pop(0)
+
+ # Skip already processed targets
+ if curr in processed_targets:
+ continue
+
+ tgt = self.traceparser.targets[curr]
+ cfgs = []
+ cfg = ''
+ otherDeps = []
+ mlog.debug(tgt)
+
+ if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
+ incDirs += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x]
+
+ if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties:
+ compileDefinitions += ['-D' + re.sub('^-D', '', x) for x in tgt.properties['INTERFACE_COMPILE_DEFINITIONS'] if x]
+
+ if 'INTERFACE_COMPILE_OPTIONS' in tgt.properties:
+ compileOptions += [x for x in tgt.properties['INTERFACE_COMPILE_OPTIONS'] if x]
+
+ if 'IMPORTED_CONFIGURATIONS' in tgt.properties:
+ cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x]
+ cfg = cfgs[0]
+
+ if is_debug:
+ if 'DEBUG' in cfgs:
+ cfg = 'DEBUG'
+ elif 'RELEASE' in cfgs:
+ cfg = 'RELEASE'
+ else:
+ if 'RELEASE' in cfgs:
+ cfg = 'RELEASE'
+
+ if f'IMPORTED_IMPLIB_{cfg}' in tgt.properties:
+ libraries += [x for x in tgt.properties[f'IMPORTED_IMPLIB_{cfg}'] if x]
+ elif 'IMPORTED_IMPLIB' in tgt.properties:
+ libraries += [x for x in tgt.properties['IMPORTED_IMPLIB'] if x]
+ elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties:
+ libraries += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x]
+ elif 'IMPORTED_LOCATION' in tgt.properties:
+ libraries += [x for x in tgt.properties['IMPORTED_LOCATION'] if x]
+
+ if 'INTERFACE_LINK_LIBRARIES' in tgt.properties:
+ otherDeps += [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x]
+
+ if f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}' in tgt.properties:
+ otherDeps += [x for x in tgt.properties[f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}'] if x]
+ elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properties:
+ otherDeps += [x for x in tgt.properties['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x]
+
+ for j in otherDeps:
+ if j in self.traceparser.targets:
+ targets += [j]
+ elif reg_is_lib.match(j):
+ libraries += [j]
+ elif os.path.isabs(j) and os.path.exists(j):
+ libraries += [j]
+ elif self.env.machines.build.is_windows() and reg_is_maybe_bare_lib.match(j):
+ # On Windows, CMake library dependencies can be passed as bare library names,
+ # e.g. 'version' should translate into 'version.lib'. CMake brute-forces a
+ # combination of prefix/suffix combinations to find the right library, however
+ # as we do not have a compiler environment available to us, we cannot do the
+ # same, but must assume any bare argument passed which is not also a CMake
+ # target must be a system library we should try to link against
+ libraries += [f"{j}.lib"]
+ else:
+ mlog.warning('CMake: Dependency', mlog.bold(j), 'for', mlog.bold(name), 'target', mlog.bold(self._original_module_name(curr)), 'was not found')
+
+ processed_targets += [curr]
+
+ # Make sure all elements in the lists are unique and sorted
+ incDirs = sorted(set(incDirs))
+ compileDefinitions = sorted(set(compileDefinitions))
+ compileOptions = sorted(set(compileOptions))
+ libraries = sorted(set(libraries))
+
+ mlog.debug(f'Include Dirs: {incDirs}')
+ mlog.debug(f'Compiler Definitions: {compileDefinitions}')
+ mlog.debug(f'Compiler Options: {compileOptions}')
+ mlog.debug(f'Libraries: {libraries}')
+
+ self.compile_args = compileOptions + compileDefinitions + [f'-I{x}' for x in incDirs]
+ self.link_args = libraries
+
+ def _get_build_dir(self) -> Path:
+ build_dir = Path(self.cmake_root_dir) / f'cmake_{self.name}'
+ build_dir.mkdir(parents=True, exist_ok=True)
+ return build_dir
+
+ def _setup_cmake_dir(self, cmake_file: str) -> Path:
+ # Setup the CMake build environment and return the "build" directory
+ build_dir = self._get_build_dir()
+
+ # Remove old CMake cache so we can try out multiple generators
+ cmake_cache = build_dir / 'CMakeCache.txt'
+ cmake_files = build_dir / 'CMakeFiles'
+ if cmake_cache.exists():
+ cmake_cache.unlink()
+ shutil.rmtree(cmake_files.as_posix(), ignore_errors=True)
+
+ # Insert language parameters into the CMakeLists.txt and write new CMakeLists.txt
+ cmake_txt = mesondata['dependencies/data/' + cmake_file].data
+
+ # In general, some Fortran CMake find_package() also require C language enabled,
+ # even if nothing from C is directly used. An easy Fortran example that fails
+ # without C language is
+ # find_package(Threads)
+ # To make this general to
+ # any other language that might need this, we use a list for all
+ # languages and expand in the cmake Project(... LANGUAGES ...) statement.
+ from ..cmake import language_map
+ cmake_language = [language_map[x] for x in self.language_list if x in language_map]
+ if not cmake_language:
+ cmake_language += ['NONE']
+
+ cmake_txt = textwrap.dedent("""
+ cmake_minimum_required(VERSION ${{CMAKE_VERSION}})
+ project(MesonTemp LANGUAGES {})
+ """).format(' '.join(cmake_language)) + cmake_txt
+
+ cm_file = build_dir / 'CMakeLists.txt'
+ cm_file.write_text(cmake_txt, encoding='utf-8')
+ mlog.cmd_ci_include(cm_file.absolute().as_posix())
+
+ return build_dir
+
+ def _call_cmake(self,
+ args: T.List[str],
+ cmake_file: str,
+ env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, T.Optional[str], T.Optional[str]]:
+ build_dir = self._setup_cmake_dir(cmake_file)
+ return self.cmakebin.call(args, build_dir, env=env)
+
+ @staticmethod
+ def get_methods() -> T.List[DependencyMethods]:
+ return [DependencyMethods.CMAKE]
+
+ def log_tried(self) -> str:
+ 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 ''
+
+ def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
+ configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
+ default_value: T.Optional[str] = None,
+ pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]:
+ if cmake and self.traceparser is not None:
+ try:
+ v = self.traceparser.vars[cmake]
+ except KeyError:
+ pass
+ else:
+ if len(v) == 1:
+ return v[0]
+ elif v:
+ return v
+ if default_value is not None:
+ return default_value
+ raise DependencyException(f'Could not get cmake variable and no default provided for {self!r}')