The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
655 lines
29 KiB
655 lines
29 KiB
# 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 |
|
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 |
|
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 ..mesonlib import MachineInfo |
|
|
|
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(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 = None |
|
|
|
def _gen_exception(self, msg): |
|
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, language: T.Optional[str] = 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__('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 = None |
|
self.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 = [] |
|
|
|
# Initialize with None before the first return to avoid |
|
# AttributeError exceptions in derived classes |
|
self.traceparser = None # type: CMakeTraceParser |
|
|
|
# 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): |
|
s = '<{0} {1}: {2} {3}>' |
|
return s.format(self.__class__.__name__, self.name, self.is_found, |
|
self.version_reqs) |
|
|
|
def _get_cmake_info(self, cm_args): |
|
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 |
|
|
|
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 |
|
l = [x.split(os.pathsep) for x in l] |
|
else: |
|
# https://github.com/mesonbuild/meson/issues/7294 |
|
l = [re.split(r':|;', x) for x in l] |
|
l = [x for sublist in l for x in sublist] |
|
return set(l) |
|
|
|
# Extract the variables and sanity check them |
|
root_paths = process_paths(temp_parser.get_cmake_var('MESON_FIND_ROOT_PATH')) |
|
root_paths.update(process_paths(temp_parser.get_cmake_var('MESON_CMAKE_SYSROOT'))) |
|
root_paths = sorted(root_paths) |
|
root_paths = list(filter(lambda x: os.path.isdir(x), root_paths)) |
|
module_paths = process_paths(temp_parser.get_cmake_var('MESON_PATHS_LIST')) |
|
rooted_paths = [] |
|
for j in [Path(x) for x in root_paths]: |
|
for i in [Path(x) for x in module_paths]: |
|
rooted_paths.append(str(j / i.relative_to(i.anchor))) |
|
module_paths = sorted(module_paths.union(rooted_paths)) |
|
module_paths = list(filter(lambda x: os.path.isdir(x), module_paths)) |
|
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 = { |
|
'module_paths': module_paths, |
|
'cmake_root': temp_parser.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'])) |
|
|
|
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 () |
|
|
|
@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 = list(filter(lambda x: x[1].startswith(lname), content)) |
|
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 = list(filter(lambda x: x[1].startswith(lname), content)) |
|
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 = list(filter(lambda x: x[1].startswith(lname), content)) |
|
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 ['{}.framework', '{}.app']: |
|
j = j.format(lname) |
|
if j in content: |
|
if find_module(os.path.join(i, j[0], 'Resources')) or find_module(os.path.join(i, j[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]): |
|
# 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: |
|
e = self._gen_exception(str(e)) |
|
if self.required: |
|
raise |
|
else: |
|
self.compile_args = [] |
|
self.link_args = [] |
|
self.is_found = False |
|
self.reason = e |
|
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 |
|
|
|
# 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: |
|
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 = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x] |
|
|
|
# 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 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: |
|
is_debug = self.env.coredata.get_option(OptionKey('debug')) |
|
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) |
|
mlog.cmd_ci_include(cm_file.absolute().as_posix()) |
|
|
|
return build_dir |
|
|
|
def _call_cmake(self, args, cmake_file: str, env=None): |
|
build_dir = self._setup_cmake_dir(cmake_file) |
|
return self.cmakebin.call(args, build_dir, env=env) |
|
|
|
@staticmethod |
|
def get_methods(): |
|
return [DependencyMethods.CMAKE] |
|
|
|
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 '' |
|
|
|
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}')
|
|
|