1# Copyright 2013-2021 The Meson development team
2
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6
7#     http://www.apache.org/licenses/LICENSE-2.0
8
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from .base import ExternalDependency, DependencyException, DependencyMethods, DependencyTypeName
16from ..mesonlib import is_windows, MesonException, OptionKey, PerMachine, stringlistify, extract_as_list
17from ..mesondata import mesondata
18from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args, CMakeTarget
19from .. import mlog
20from pathlib import Path
21import functools
22import re
23import os
24import shutil
25import textwrap
26import typing as T
27
28if T.TYPE_CHECKING:
29    from ..environment import Environment
30    from ..envconfig import MachineInfo
31
32class CMakeInfo(T.NamedTuple):
33    module_paths: T.List[str]
34    cmake_root: str
35    archs: T.List[str]
36    common_paths: T.List[str]
37
38class CMakeDependency(ExternalDependency):
39    # The class's copy of the CMake path. Avoids having to search for it
40    # multiple times in the same Meson invocation.
41    class_cmakeinfo: PerMachine[T.Optional[CMakeInfo]] = PerMachine(None, None)
42    # Version string for the minimum CMake version
43    class_cmake_version = '>=3.4'
44    # CMake generators to try (empty for no generator)
45    class_cmake_generators = ['', 'Ninja', 'Unix Makefiles', 'Visual Studio 10 2010']
46    class_working_generator: T.Optional[str] = None
47
48    def _gen_exception(self, msg: str) -> DependencyException:
49        return DependencyException(f'Dependency {self.name} not found: {msg}')
50
51    def _main_cmake_file(self) -> str:
52        return 'CMakeLists.txt'
53
54    def _extra_cmake_opts(self) -> T.List[str]:
55        return []
56
57    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]]:
58        # Map the input module list to something else
59        # This function will only be executed AFTER the initial CMake
60        # interpreter pass has completed. Thus variables defined in the
61        # CMakeLists.txt can be accessed here.
62        #
63        # Both the modules and components inputs contain the original lists.
64        return modules
65
66    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]]:
67        # Map the input components list to something else. This
68        # function will be executed BEFORE the initial CMake interpreter
69        # pass. Thus variables from the CMakeLists.txt can NOT be accessed.
70        #
71        # Both the modules and components inputs contain the original lists.
72        return components
73
74    def _original_module_name(self, module: str) -> str:
75        # Reverse the module mapping done by _map_module_list for
76        # one module
77        return module
78
79    def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any], language: T.Optional[str] = None) -> None:
80        # Gather a list of all languages to support
81        self.language_list = []  # type: T.List[str]
82        if language is None:
83            compilers = None
84            if kwargs.get('native', False):
85                compilers = environment.coredata.compilers.build
86            else:
87                compilers = environment.coredata.compilers.host
88
89            candidates = ['c', 'cpp', 'fortran', 'objc', 'objcxx']
90            self.language_list += [x for x in candidates if x in compilers]
91        else:
92            self.language_list += [language]
93
94        # Add additional languages if required
95        if 'fortran' in self.language_list:
96            self.language_list += ['c']
97
98        # Ensure that the list is unique
99        self.language_list = list(set(self.language_list))
100
101        super().__init__(DependencyTypeName('cmake'), environment, kwargs, language=language)
102        self.name = name
103        self.is_libtool = False
104        # Store a copy of the CMake path on the object itself so it is
105        # stored in the pickled coredata and recovered.
106        self.cmakebin:  T.Optional[CMakeExecutor] = None
107        self.cmakeinfo: T.Optional[CMakeInfo]     = None
108
109        # Where all CMake "build dirs" are located
110        self.cmake_root_dir = environment.scratch_dir
111
112        # T.List of successfully found modules
113        self.found_modules: T.List[str] = []
114
115        # Initialize with None before the first return to avoid
116        # AttributeError exceptions in derived classes
117        self.traceparser: T.Optional[CMakeTraceParser] = None
118
119        # TODO further evaluate always using MachineChoice.BUILD
120        self.cmakebin = CMakeExecutor(environment, CMakeDependency.class_cmake_version, self.for_machine, silent=self.silent)
121        if not self.cmakebin.found():
122            self.cmakebin = None
123            msg = f'CMake binary for machine {self.for_machine} not found. Giving up.'
124            if self.required:
125                raise DependencyException(msg)
126            mlog.debug(msg)
127            return
128
129        # Setup the trace parser
130        self.traceparser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir())
131
132        cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args'))
133        cm_args = check_cmake_args(cm_args)
134        if CMakeDependency.class_cmakeinfo[self.for_machine] is None:
135            CMakeDependency.class_cmakeinfo[self.for_machine] = self._get_cmake_info(cm_args)
136        self.cmakeinfo = CMakeDependency.class_cmakeinfo[self.for_machine]
137        if self.cmakeinfo is None:
138            raise self._gen_exception('Unable to obtain CMake system information')
139
140        package_version = kwargs.get('cmake_package_version', '')
141        if not isinstance(package_version, str):
142            raise DependencyException('Keyword "cmake_package_version" must be a string.')
143        components = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'components'))]
144        modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))]
145        modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))]
146        cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path'))
147        cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path]
148        if cm_path:
149            cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path))
150        if not self._preliminary_find_check(name, cm_path, self.cmakebin.get_cmake_prefix_paths(), environment.machines[self.for_machine]):
151            mlog.debug('Preliminary CMake check failed. Aborting.')
152            return
153        self._detect_dep(name, package_version, modules, components, cm_args)
154
155    def __repr__(self) -> str:
156        return f'<{self.__class__.__name__} {self.name}: {self.is_found} {self.version_reqs}>'
157
158    def _get_cmake_info(self, cm_args: T.List[str]) -> T.Optional[CMakeInfo]:
159        mlog.debug("Extracting basic cmake information")
160
161        # Try different CMake generators since specifying no generator may fail
162        # in cygwin for some reason
163        gen_list = []
164        # First try the last working generator
165        if CMakeDependency.class_working_generator is not None:
166            gen_list += [CMakeDependency.class_working_generator]
167        gen_list += CMakeDependency.class_cmake_generators
168
169        temp_parser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir())
170        toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir())
171        toolchain.write()
172
173        for i in gen_list:
174            mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto'))
175
176            # Prepare options
177            cmake_opts = temp_parser.trace_args() + toolchain.get_cmake_args() + ['.']
178            cmake_opts += cm_args
179            if len(i) > 0:
180                cmake_opts = ['-G', i] + cmake_opts
181
182            # Run CMake
183            ret1, out1, err1 = self._call_cmake(cmake_opts, 'CMakePathInfo.txt')
184
185            # Current generator was successful
186            if ret1 == 0:
187                CMakeDependency.class_working_generator = i
188                break
189
190            mlog.debug(f'CMake failed to gather system information for generator {i} with error code {ret1}')
191            mlog.debug(f'OUT:\n{out1}\n\n\nERR:\n{err1}\n\n')
192
193        # Check if any generator succeeded
194        if ret1 != 0:
195            return None
196
197        try:
198            temp_parser.parse(err1)
199        except MesonException:
200            return None
201
202        def process_paths(l: T.List[str]) -> T.Set[str]:
203            if is_windows():
204                # Cannot split on ':' on Windows because its in the drive letter
205                tmp = [x.split(os.pathsep) for x in l]
206            else:
207                # https://github.com/mesonbuild/meson/issues/7294
208                tmp = [re.split(r':|;', x) for x in l]
209            flattened = [x for sublist in tmp for x in sublist]
210            return set(flattened)
211
212        # Extract the variables and sanity check them
213        root_paths_set = process_paths(temp_parser.get_cmake_var('MESON_FIND_ROOT_PATH'))
214        root_paths_set.update(process_paths(temp_parser.get_cmake_var('MESON_CMAKE_SYSROOT')))
215        root_paths = sorted(root_paths_set)
216        root_paths = [x for x in root_paths if os.path.isdir(x)]
217        module_paths_set = process_paths(temp_parser.get_cmake_var('MESON_PATHS_LIST'))
218        rooted_paths: T.List[str] = []
219        for j in [Path(x) for x in root_paths]:
220            for p in [Path(x) for x in module_paths_set]:
221                rooted_paths.append(str(j / p.relative_to(p.anchor)))
222        module_paths = sorted(module_paths_set.union(rooted_paths))
223        module_paths = [x for x in module_paths if os.path.isdir(x)]
224        archs = temp_parser.get_cmake_var('MESON_ARCH_LIST')
225
226        common_paths = ['lib', 'lib32', 'lib64', 'libx32', 'share']
227        for i in archs:
228            common_paths += [os.path.join('lib', i)]
229
230        res = CMakeInfo(
231            module_paths=module_paths,
232            cmake_root=temp_parser.get_cmake_var('MESON_CMAKE_ROOT')[0],
233            archs=archs,
234            common_paths=common_paths,
235        )
236
237        mlog.debug(f'  -- Module search paths:    {res.module_paths}')
238        mlog.debug(f'  -- CMake root:             {res.cmake_root}')
239        mlog.debug(f'  -- CMake architectures:    {res.archs}')
240        mlog.debug(f'  -- CMake lib search paths: {res.common_paths}')
241
242        return res
243
244    @staticmethod
245    @functools.lru_cache(maxsize=None)
246    def _cached_listdir(path: str) -> T.Tuple[T.Tuple[str, str], ...]:
247        try:
248            return tuple((x, str(x).lower()) for x in os.listdir(path))
249        except OSError:
250            return tuple()
251
252    @staticmethod
253    @functools.lru_cache(maxsize=None)
254    def _cached_isdir(path: str) -> bool:
255        try:
256            return os.path.isdir(path)
257        except OSError:
258            return False
259
260    def _preliminary_find_check(self, name: str, module_path: T.List[str], prefix_path: T.List[str], machine: 'MachineInfo') -> bool:
261        lname = str(name).lower()
262
263        # Checks <path>, <path>/cmake, <path>/CMake
264        def find_module(path: str) -> bool:
265            for i in [path, os.path.join(path, 'cmake'), os.path.join(path, 'CMake')]:
266                if not self._cached_isdir(i):
267                    continue
268
269                # Check the directory case insensitive
270                content = self._cached_listdir(i)
271                candidates = ['Find{}.cmake', '{}Config.cmake', '{}-config.cmake']
272                candidates = [x.format(name).lower() for x in candidates]
273                if any([x[1] in candidates for x in content]):
274                    return True
275            return False
276
277        # Search in <path>/(lib/<arch>|lib*|share) for cmake files
278        def search_lib_dirs(path: str) -> bool:
279            for i in [os.path.join(path, x) for x in self.cmakeinfo.common_paths]:
280                if not self._cached_isdir(i):
281                    continue
282
283                # Check <path>/(lib/<arch>|lib*|share)/cmake/<name>*/
284                cm_dir = os.path.join(i, 'cmake')
285                if self._cached_isdir(cm_dir):
286                    content = self._cached_listdir(cm_dir)
287                    content = tuple(x for x in content if x[1].startswith(lname))
288                    for k in content:
289                        if find_module(os.path.join(cm_dir, k[0])):
290                            return True
291
292                # <path>/(lib/<arch>|lib*|share)/<name>*/
293                # <path>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/
294                content = self._cached_listdir(i)
295                content = tuple(x for x in content if x[1].startswith(lname))
296                for k in content:
297                    if find_module(os.path.join(i, k[0])):
298                        return True
299
300            return False
301
302        # Check the user provided and system module paths
303        for i in module_path + [os.path.join(self.cmakeinfo.cmake_root, 'Modules')]:
304            if find_module(i):
305                return True
306
307        # Check the user provided prefix paths
308        for i in prefix_path:
309            if search_lib_dirs(i):
310                return True
311
312        # Check PATH
313        system_env = []  # type: T.List[str]
314        for i in os.environ.get('PATH', '').split(os.pathsep):
315            if i.endswith('/bin') or i.endswith('\\bin'):
316                i = i[:-4]
317            if i.endswith('/sbin') or i.endswith('\\sbin'):
318                i = i[:-5]
319            system_env += [i]
320
321        # Check the system paths
322        for i in self.cmakeinfo.module_paths + system_env:
323            if find_module(i):
324                return True
325
326            if search_lib_dirs(i):
327                return True
328
329            content = self._cached_listdir(i)
330            content = tuple(x for x in content if x[1].startswith(lname))
331            for k in content:
332                if search_lib_dirs(os.path.join(i, k[0])):
333                    return True
334
335            # Mac framework support
336            if machine.is_darwin():
337                for j in [f'{lname}.framework', f'{lname}.app']:
338                    for k in content:
339                        if k[1] != j:
340                            continue
341                        if find_module(os.path.join(i, k[0], 'Resources')) or find_module(os.path.join(i, k[0], 'Version')):
342                            return True
343
344        # Check the environment path
345        env_path = os.environ.get(f'{name}_DIR')
346        if env_path and find_module(env_path):
347            return True
348
349        return False
350
351    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:
352        # Detect a dependency with CMake using the '--find-package' mode
353        # and the trace output (stderr)
354        #
355        # When the trace output is enabled CMake prints all functions with
356        # parameters to stderr as they are executed. Since CMake 3.4.0
357        # variables ("${VAR}") are also replaced in the trace output.
358        mlog.debug('\nDetermining dependency {!r} with CMake executable '
359                   '{!r}'.format(name, self.cmakebin.executable_path()))
360
361        # Try different CMake generators since specifying no generator may fail
362        # in cygwin for some reason
363        gen_list = []
364        # First try the last working generator
365        if CMakeDependency.class_working_generator is not None:
366            gen_list += [CMakeDependency.class_working_generator]
367        gen_list += CMakeDependency.class_cmake_generators
368
369        # Map the components
370        comp_mapped = self._map_component_list(modules, components)
371        toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir())
372        toolchain.write()
373
374        for i in gen_list:
375            mlog.debug('Try CMake generator: {}'.format(i if len(i) > 0 else 'auto'))
376
377            # Prepare options
378            cmake_opts = []
379            cmake_opts += [f'-DNAME={name}']
380            cmake_opts += ['-DARCHS={}'.format(';'.join(self.cmakeinfo.archs))]
381            cmake_opts += [f'-DVERSION={package_version}']
382            cmake_opts += ['-DCOMPS={}'.format(';'.join([x[0] for x in comp_mapped]))]
383            cmake_opts += args
384            cmake_opts += self.traceparser.trace_args()
385            cmake_opts += toolchain.get_cmake_args()
386            cmake_opts += self._extra_cmake_opts()
387            cmake_opts += ['.']
388            if len(i) > 0:
389                cmake_opts = ['-G', i] + cmake_opts
390
391            # Run CMake
392            ret1, out1, err1 = self._call_cmake(cmake_opts, self._main_cmake_file())
393
394            # Current generator was successful
395            if ret1 == 0:
396                CMakeDependency.class_working_generator = i
397                break
398
399            mlog.debug(f'CMake failed for generator {i} and package {name} with error code {ret1}')
400            mlog.debug(f'OUT:\n{out1}\n\n\nERR:\n{err1}\n\n')
401
402        # Check if any generator succeeded
403        if ret1 != 0:
404            return
405
406        try:
407            self.traceparser.parse(err1)
408        except CMakeException as e:
409            e2 = self._gen_exception(str(e))
410            if self.required:
411                raise
412            else:
413                self.compile_args = []
414                self.link_args = []
415                self.is_found = False
416                self.reason = e2
417                return
418
419        # Whether the package is found or not is always stored in PACKAGE_FOUND
420        self.is_found = self.traceparser.var_to_bool('PACKAGE_FOUND')
421        if not self.is_found:
422            return
423
424        # Try to detect the version
425        vers_raw = self.traceparser.get_cmake_var('PACKAGE_VERSION')
426
427        if len(vers_raw) > 0:
428            self.version = vers_raw[0]
429            self.version.strip('"\' ')
430
431        # Post-process module list. Used in derived classes to modify the
432        # module list (append prepend a string, etc.).
433        modules = self._map_module_list(modules, components)
434        autodetected_module_list = False
435
436        # Check if we need a DEBUG or RELEASE CMake dependencies
437        is_debug = False
438        if OptionKey('b_vscrt') in self.env.coredata.options:
439            is_debug = self.env.coredata.get_option(OptionKey('buildtype')) == 'debug'
440            if self.env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}:
441                is_debug = True
442        else:
443            # Don't directly assign to is_debug to make mypy happy
444            debug_opt = self.env.coredata.get_option(OptionKey('debug'))
445            assert isinstance(debug_opt, bool)
446            is_debug = debug_opt
447
448        # Try guessing a CMake target if none is provided
449        if len(modules) == 0:
450            for i in self.traceparser.targets:
451                tg = i.lower()
452                lname = name.lower()
453                if f'{lname}::{lname}' == tg or lname == tg.replace('::', ''):
454                    mlog.debug(f'Guessed CMake target \'{i}\'')
455                    modules = [(i, True)]
456                    autodetected_module_list = True
457                    break
458
459        # Failed to guess a target --> try the old-style method
460        if len(modules) == 0:
461            # Warn when there might be matching imported targets but no automatic match was used
462            partial_modules: T.List[CMakeTarget] = []
463            for k, v in self.traceparser.targets.items():
464                tg = k.lower()
465                lname = name.lower()
466                if tg.startswith(f'{lname}::'):
467                    partial_modules += [v]
468            if partial_modules:
469                mlog.warning(textwrap.dedent(f'''\
470                    Could not find and exact match for the CMake dependency {name}.
471
472                    However, Meson found the following partial matches:
473
474                        {[x.name for x in partial_modules]}
475
476                    Using imported is recommended, since this approach is less error prone
477                    and better supported by Meson. Consider explicitly specifying one of
478                    these in the dependency call with:
479
480                        dependency('{name}', modules: ['{name}::<name>', ...])
481
482                    Meson will now continue to use the old-style {name}_LIBRARIES CMake
483                    variables to extract the dependency information since no explicit
484                    target is currently specified.
485
486                '''))
487                mlog.debug('More info for the partial match targets:')
488                for tgt in partial_modules:
489                    mlog.debug(tgt)
490
491
492            incDirs = [x for x in self.traceparser.get_cmake_var('PACKAGE_INCLUDE_DIRS') if x]
493            defs = [x for x in self.traceparser.get_cmake_var('PACKAGE_DEFINITIONS') if x]
494            libs_raw = [x for x in self.traceparser.get_cmake_var('PACKAGE_LIBRARIES') if x]
495
496            # CMake has a "fun" API, where certain keywords describing
497            # configurations can be in the *_LIBRARIES vraiables. See:
498            # - https://github.com/mesonbuild/meson/issues/9197
499            # - https://gitlab.freedesktop.org/libnice/libnice/-/issues/140
500            # - https://cmake.org/cmake/help/latest/command/target_link_libraries.html#overview  (the last point in the section)
501            libs: T.List[str] = []
502            cfg_matches = True
503            cm_tag_map = {'debug': is_debug, 'optimized': not is_debug, 'general': True}
504            for i in libs_raw:
505                if i.lower() in cm_tag_map:
506                    cfg_matches = cm_tag_map[i.lower()]
507                    continue
508                if cfg_matches:
509                    libs += [i]
510                # According to the CMake docs, a keyword only works for the
511                # directly the following item and all items without a keyword
512                # are implizitly `general`
513                cfg_matches = True
514
515            # Try to use old style variables if no module is specified
516            if len(libs) > 0:
517                self.compile_args = list(map(lambda x: f'-I{x}', incDirs)) + defs
518                self.link_args = libs
519                mlog.debug(f'using old-style CMake variables for dependency {name}')
520                mlog.debug(f'Include Dirs:         {incDirs}')
521                mlog.debug(f'Compiler Definitions: {defs}')
522                mlog.debug(f'Libraries:            {libs}')
523                return
524
525            # Even the old-style approach failed. Nothing else we can do here
526            self.is_found = False
527            raise self._gen_exception('CMake: failed to guess a CMake target for {}.\n'
528                                      'Try to explicitly specify one or more targets with the "modules" property.\n'
529                                      'Valid targets are:\n{}'.format(name, list(self.traceparser.targets.keys())))
530
531        # Set dependencies with CMake targets
532        # recognise arguments we should pass directly to the linker
533        reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-pthread|-delayload:[a-zA-Z0-9_\.]+|[a-zA-Z0-9_]+\.lib)$')
534        reg_is_maybe_bare_lib = re.compile(r'^[a-zA-Z0-9_]+$')
535        processed_targets = []
536        incDirs = []
537        compileDefinitions = []
538        compileOptions = []
539        libraries = []
540        for i, required in modules:
541            if i not in self.traceparser.targets:
542                if not required:
543                    mlog.warning('CMake: T.Optional module', mlog.bold(self._original_module_name(i)), 'for', mlog.bold(name), 'was not found')
544                    continue
545                raise self._gen_exception('CMake: invalid module {} for {}.\n'
546                                          'Try to explicitly specify one or more targets with the "modules" property.\n'
547                                          'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.traceparser.targets.keys())))
548
549            targets = [i]
550            if not autodetected_module_list:
551                self.found_modules += [i]
552
553            while len(targets) > 0:
554                curr = targets.pop(0)
555
556                # Skip already processed targets
557                if curr in processed_targets:
558                    continue
559
560                tgt = self.traceparser.targets[curr]
561                cfgs = []
562                cfg = ''
563                otherDeps = []
564                mlog.debug(tgt)
565
566                if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
567                    incDirs += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x]
568
569                if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties:
570                    compileDefinitions += ['-D' + re.sub('^-D', '', x) for x in tgt.properties['INTERFACE_COMPILE_DEFINITIONS'] if x]
571
572                if 'INTERFACE_COMPILE_OPTIONS' in tgt.properties:
573                    compileOptions += [x for x in tgt.properties['INTERFACE_COMPILE_OPTIONS'] if x]
574
575                if 'IMPORTED_CONFIGURATIONS' in tgt.properties:
576                    cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x]
577                    cfg = cfgs[0]
578
579                if is_debug:
580                    if 'DEBUG' in cfgs:
581                        cfg = 'DEBUG'
582                    elif 'RELEASE' in cfgs:
583                        cfg = 'RELEASE'
584                else:
585                    if 'RELEASE' in cfgs:
586                        cfg = 'RELEASE'
587
588                if f'IMPORTED_IMPLIB_{cfg}' in tgt.properties:
589                    libraries += [x for x in tgt.properties[f'IMPORTED_IMPLIB_{cfg}'] if x]
590                elif 'IMPORTED_IMPLIB' in tgt.properties:
591                    libraries += [x for x in tgt.properties['IMPORTED_IMPLIB'] if x]
592                elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties:
593                    libraries += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x]
594                elif 'IMPORTED_LOCATION' in tgt.properties:
595                    libraries += [x for x in tgt.properties['IMPORTED_LOCATION'] if x]
596
597                if 'INTERFACE_LINK_LIBRARIES' in tgt.properties:
598                    otherDeps += [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x]
599
600                if f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}' in tgt.properties:
601                    otherDeps += [x for x in tgt.properties[f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}'] if x]
602                elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properties:
603                    otherDeps += [x for x in tgt.properties['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x]
604
605                for j in otherDeps:
606                    if j in self.traceparser.targets:
607                        targets += [j]
608                    elif reg_is_lib.match(j):
609                        libraries += [j]
610                    elif os.path.isabs(j) and os.path.exists(j):
611                        libraries += [j]
612                    elif self.env.machines.build.is_windows() and reg_is_maybe_bare_lib.match(j):
613                        # On Windows, CMake library dependencies can be passed as bare library names,
614                        # e.g. 'version' should translate into 'version.lib'. CMake brute-forces a
615                        # combination of prefix/suffix combinations to find the right library, however
616                        # as we do not have a compiler environment available to us, we cannot do the
617                        # same, but must assume any bare argument passed which is not also a CMake
618                        # target must be a system library we should try to link against
619                        libraries += [f"{j}.lib"]
620                    else:
621                        mlog.warning('CMake: Dependency', mlog.bold(j), 'for', mlog.bold(name), 'target', mlog.bold(self._original_module_name(curr)), 'was not found')
622
623                processed_targets += [curr]
624
625        # Make sure all elements in the lists are unique and sorted
626        incDirs = sorted(set(incDirs))
627        compileDefinitions = sorted(set(compileDefinitions))
628        compileOptions = sorted(set(compileOptions))
629        libraries = sorted(set(libraries))
630
631        mlog.debug(f'Include Dirs:         {incDirs}')
632        mlog.debug(f'Compiler Definitions: {compileDefinitions}')
633        mlog.debug(f'Compiler Options:     {compileOptions}')
634        mlog.debug(f'Libraries:            {libraries}')
635
636        self.compile_args = compileOptions + compileDefinitions + [f'-I{x}' for x in incDirs]
637        self.link_args = libraries
638
639    def _get_build_dir(self) -> Path:
640        build_dir = Path(self.cmake_root_dir) / f'cmake_{self.name}'
641        build_dir.mkdir(parents=True, exist_ok=True)
642        return build_dir
643
644    def _setup_cmake_dir(self, cmake_file: str) -> Path:
645        # Setup the CMake build environment and return the "build" directory
646        build_dir = self._get_build_dir()
647
648        # Remove old CMake cache so we can try out multiple generators
649        cmake_cache = build_dir / 'CMakeCache.txt'
650        cmake_files = build_dir / 'CMakeFiles'
651        if cmake_cache.exists():
652            cmake_cache.unlink()
653        shutil.rmtree(cmake_files.as_posix(), ignore_errors=True)
654
655        # Insert language parameters into the CMakeLists.txt and write new CMakeLists.txt
656        cmake_txt = mesondata['dependencies/data/' + cmake_file].data
657
658        # In general, some Fortran CMake find_package() also require C language enabled,
659        # even if nothing from C is directly used. An easy Fortran example that fails
660        # without C language is
661        #   find_package(Threads)
662        # To make this general to
663        # any other language that might need this, we use a list for all
664        # languages and expand in the cmake Project(... LANGUAGES ...) statement.
665        from ..cmake import language_map
666        cmake_language = [language_map[x] for x in self.language_list if x in language_map]
667        if not cmake_language:
668            cmake_language += ['NONE']
669
670        cmake_txt = textwrap.dedent("""
671            cmake_minimum_required(VERSION ${{CMAKE_VERSION}})
672            project(MesonTemp LANGUAGES {})
673        """).format(' '.join(cmake_language)) + cmake_txt
674
675        cm_file = build_dir / 'CMakeLists.txt'
676        cm_file.write_text(cmake_txt, encoding='utf-8')
677        mlog.cmd_ci_include(cm_file.absolute().as_posix())
678
679        return build_dir
680
681    def _call_cmake(self,
682                    args: T.List[str],
683                    cmake_file: str,
684                    env: T.Optional[T.Dict[str, str]] = None) -> T.Tuple[int, T.Optional[str], T.Optional[str]]:
685        build_dir = self._setup_cmake_dir(cmake_file)
686        return self.cmakebin.call(args, build_dir, env=env)
687
688    @staticmethod
689    def get_methods() -> T.List[DependencyMethods]:
690        return [DependencyMethods.CMAKE]
691
692    def log_tried(self) -> str:
693        return self.type_name
694
695    def log_details(self) -> str:
696        modules = [self._original_module_name(x) for x in self.found_modules]
697        modules = sorted(set(modules))
698        if modules:
699            return 'modules: ' + ', '.join(modules)
700        return ''
701
702    def get_variable(self, *, cmake: T.Optional[str] = None, pkgconfig: T.Optional[str] = None,
703                     configtool: T.Optional[str] = None, internal: T.Optional[str] = None,
704                     default_value: T.Optional[str] = None,
705                     pkgconfig_define: T.Optional[T.List[str]] = None) -> T.Union[str, T.List[str]]:
706        if cmake and self.traceparser is not None:
707            try:
708                v = self.traceparser.vars[cmake]
709            except KeyError:
710                pass
711            else:
712                if len(v) == 1:
713                    return v[0]
714                elif v:
715                    return v
716        if default_value is not None:
717            return default_value
718        raise DependencyException(f'Could not get cmake variable and no default provided for {self!r}')
719