| # SPDX-License-Identifier: Apache-2.0 |
| # Copyright 2021 The Meson development team |
| from __future__ import annotations |
| |
| from .common import cmake_is_debug |
| from .. import mlog |
| from ..mesonlib import Version |
| |
| from pathlib import Path |
| import re |
| import typing as T |
| |
| if T.TYPE_CHECKING: |
| from .traceparser import CMakeTraceParser |
| from ..environment import Environment |
| from ..compilers import Compiler |
| from ..dependencies import MissingCompiler |
| |
| # Small duplication of ExtraFramework to parse full |
| # framework paths as exposed by CMake |
| def _get_framework_latest_version(path: Path) -> str: |
| versions: list[Version] = [] |
| for each in path.glob('Versions/*'): |
| # macOS filesystems are usually case-insensitive |
| if each.name.lower() == 'current': |
| continue |
| versions.append(Version(each.name)) |
| if len(versions) == 0: |
| # most system frameworks do not have a 'Versions' directory |
| return 'Headers' |
| return 'Versions/{}/Headers'.format(sorted(versions)[-1]._s) |
| |
| def _get_framework_include_path(path: Path) -> T.Optional[str]: |
| trials = ('Headers', 'Versions/Current/Headers', _get_framework_latest_version(path)) |
| for each in trials: |
| trial = path / each |
| if trial.is_dir(): |
| return trial.as_posix() |
| return None |
| |
| class ResolvedTarget: |
| def __init__(self) -> None: |
| self.include_directories: T.List[str] = [] |
| self.link_flags: T.List[str] = [] |
| self.public_compile_opts: T.List[str] = [] |
| self.libraries: T.List[str] = [] |
| |
| def resolve_cmake_trace_targets(target_name: str, |
| trace: 'CMakeTraceParser', |
| env: 'Environment', |
| *, |
| clib_compiler: T.Union['MissingCompiler', 'Compiler'] = None, |
| not_found_warning: T.Callable[[str], None] = lambda x: None) -> ResolvedTarget: |
| res = ResolvedTarget() |
| targets = [target_name] |
| |
| # recognise arguments we should pass directly to the linker |
| reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-l?pthread)$') |
| reg_is_maybe_bare_lib = re.compile(r'^[a-zA-Z0-9_]+$') |
| |
| is_debug = cmake_is_debug(env) |
| |
| processed_targets: T.List[str] = [] |
| while len(targets) > 0: |
| curr = targets.pop(0) |
| |
| # Skip already processed targets |
| if curr in processed_targets: |
| continue |
| |
| if curr not in trace.targets: |
| curr_path = Path(curr) |
| if reg_is_lib.match(curr): |
| res.libraries += [curr] |
| elif curr_path.is_absolute() and curr_path.exists(): |
| if any(x.endswith('.framework') for x in curr_path.parts): |
| # Frameworks detected by CMake are passed as absolute paths |
| # Split into -F/path/to/ and -framework name |
| path_to_framework = [] |
| # Try to slice off the `Versions/X/name.tbd` |
| for x in curr_path.parts: |
| path_to_framework.append(x) |
| if x.endswith('.framework'): |
| break |
| curr_path = Path(*path_to_framework) |
| framework_path = curr_path.parent |
| framework_name = curr_path.stem |
| res.libraries += [f'-F{framework_path}', '-framework', framework_name] |
| else: |
| res.libraries += [curr] |
| elif reg_is_maybe_bare_lib.match(curr) and clib_compiler: |
| # CMake library dependencies can be passed as bare library names, |
| # CMake brute-forces a combination of prefix/suffix combinations to find the |
| # right library. Assume any bare argument passed which is not also a CMake |
| # target must be a system library we should try to link against. |
| flib = clib_compiler.find_library(curr, env, []) |
| if flib is not None: |
| res.libraries += flib |
| else: |
| not_found_warning(curr) |
| else: |
| not_found_warning(curr) |
| continue |
| |
| tgt = trace.targets[curr] |
| cfgs = [] |
| cfg = '' |
| mlog.debug(tgt) |
| |
| if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties: |
| res.include_directories += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] |
| |
| if 'INTERFACE_LINK_OPTIONS' in tgt.properties: |
| res.link_flags += [x for x in tgt.properties['INTERFACE_LINK_OPTIONS'] if x] |
| |
| if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties: |
| res.public_compile_opts += ['-D' + re.sub('^-D', '', x) for x in tgt.properties['INTERFACE_COMPILE_DEFINITIONS'] if x] |
| |
| if 'INTERFACE_COMPILE_OPTIONS' in tgt.properties: |
| res.public_compile_opts += [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: |
| res.libraries += [x for x in tgt.properties[f'IMPORTED_IMPLIB_{cfg}'] if x] |
| elif 'IMPORTED_IMPLIB' in tgt.properties: |
| res.libraries += [x for x in tgt.properties['IMPORTED_IMPLIB'] if x] |
| elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties: |
| res.libraries += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x] |
| elif 'IMPORTED_LOCATION' in tgt.properties: |
| res.libraries += [x for x in tgt.properties['IMPORTED_LOCATION'] if x] |
| |
| if 'LINK_LIBRARIES' in tgt.properties: |
| targets += [x for x in tgt.properties['LINK_LIBRARIES'] if x] |
| if 'INTERFACE_LINK_LIBRARIES' in tgt.properties: |
| targets += [x for x in tgt.properties['INTERFACE_LINK_LIBRARIES'] if x] |
| |
| if f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}' in tgt.properties: |
| targets += [x for x in tgt.properties[f'IMPORTED_LINK_DEPENDENT_LIBRARIES_{cfg}'] if x] |
| elif 'IMPORTED_LINK_DEPENDENT_LIBRARIES' in tgt.properties: |
| targets += [x for x in tgt.properties['IMPORTED_LINK_DEPENDENT_LIBRARIES'] if x] |
| |
| processed_targets += [curr] |
| |
| # Do not sort flags here -- this breaks |
| # semantics of eg. `-framework CoreAudio` |
| # or `-Lpath/to/root -llibrary` |
| # see eg. #11113 |
| |
| return res |