# 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_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.public_link_flags += [x for x in tgt.properties['INTERFACE_LINK_OPTIONS'] if x] res.link_flags += res.public_link_flags 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: targets += [x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x] elif 'IMPORTED_LOCATION' in tgt.properties: targets += [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