From 45c5300496486ff9f1f3d47a01cdf19b8fa7e877 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 27 Nov 2021 19:58:04 +0100 Subject: cmake: Fix old style dependency lookup with imported targets This also includes some refactoring, since the alternaticve would have been to duplicate the huge traceparser target code block again. fixes #9581 --- mesonbuild/cmake/__init__.py | 6 +- mesonbuild/cmake/common.py | 12 +++ mesonbuild/cmake/interpreter.py | 83 ++------------- mesonbuild/cmake/tracetargets.py | 117 +++++++++++++++++++++ mesonbuild/dependencies/cmake.py | 108 +++---------------- .../cmake/FindImportedOldStyle.cmake | 5 + .../linuxlike/13 cmake dependency/meson.build | 9 ++ 7 files changed, 171 insertions(+), 169 deletions(-) create mode 100644 mesonbuild/cmake/tracetargets.py create mode 100644 test cases/linuxlike/13 cmake dependency/cmake/FindImportedOldStyle.cmake diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py index d39bf24..32b8b6c 100644 --- a/mesonbuild/cmake/__init__.py +++ b/mesonbuild/cmake/__init__.py @@ -34,9 +34,12 @@ __all__ = [ 'cmake_get_generator_args', 'cmake_defines_to_args', 'check_cmake_args', + 'cmake_is_debug', + 'resolve_cmake_trace_targets', + 'ResolvedTarget', ] -from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args +from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args, cmake_is_debug from .client import CMakeClient from .executor import CMakeExecutor from .fileapi import CMakeFileAPI @@ -44,3 +47,4 @@ from .generator import parse_generator_expressions from .interpreter import CMakeInterpreter from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser +from .tracetargets import resolve_cmake_trace_targets, ResolvedTarget diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index f6ba5ec..7130248 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -61,6 +61,18 @@ blacklist_cmake_defs = [ 'MESON_CMAKE_ROOT', ] +def cmake_is_debug(env: 'Environment') -> bool: + if OptionKey('b_vscrt') in env.coredata.options: + is_debug = env.coredata.get_option(OptionKey('buildtype')) == 'debug' + if env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: + is_debug = True + return is_debug + else: + # Don't directly assign to is_debug to make mypy happy + debug_opt = env.coredata.get_option(OptionKey('debug')) + assert isinstance(debug_opt, bool) + return debug_opt + class CMakeException(MesonException): pass diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 47459a2..a8ed67c 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -21,6 +21,7 @@ from .fileapi import CMakeFileAPI from .executor import CMakeExecutor from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTraceParser, CMakeGeneratorTarget +from .tracetargets import resolve_cmake_trace_targets from .. import mlog, mesonlib from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey from ..mesondata import mesondata @@ -342,84 +343,12 @@ class ConverterTarget: if tgt: self.depends_raw = trace.targets[self.cmake_name].depends - # TODO refactor this copy paste from CMakeDependency for future releases - reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-l?pthread)$') - to_process = [self.cmake_name] - processed = [] - while len(to_process) > 0: - curr = to_process.pop(0) + rtgt = resolve_cmake_trace_targets(self.cmake_name, trace, self.env) + self.includes += [Path(x) for x in rtgt.include_directories] + self.link_flags += rtgt.link_flags + self.public_compile_opts += rtgt.public_compile_opts + self.link_libraries += rtgt.libraries - if curr in processed or curr not in trace.targets: - continue - - tgt = trace.targets[curr] - cfgs = [] - cfg = '' - otherDeps = [] - libraries = [] - mlog.debug(str(tgt)) - - if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties: - self.includes += [Path(x) for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] - - if 'INTERFACE_LINK_OPTIONS' in tgt.properties: - self.link_flags += [x for x in tgt.properties['INTERFACE_LINK_OPTIONS'] if x] - - if 'INTERFACE_COMPILE_DEFINITIONS' in tgt.properties: - self.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: - self.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 'CONFIGURATIONS' in tgt.properties: - cfgs += [x for x in tgt.properties['CONFIGURATIONS'] if x] - cfg = cfgs[0] - - 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 'LINK_LIBRARIES' in tgt.properties: - otherDeps += [x for x in tgt.properties['LINK_LIBRARIES'] 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 trace.targets: - to_process += [j] - elif reg_is_lib.match(j) or Path(j).exists(): - libraries += [j] - - for j in libraries: - if j not in self.link_libraries: - self.link_libraries += [j] - - processed += [curr] elif self.type.upper() not in ['EXECUTABLE', 'OBJECT_LIBRARY']: mlog.warning('CMake: Target', mlog.bold(self.cmake_name), 'not found in CMake trace. This can lead to build errors') diff --git a/mesonbuild/cmake/tracetargets.py b/mesonbuild/cmake/tracetargets.py new file mode 100644 index 0000000..21aad86 --- /dev/null +++ b/mesonbuild/cmake/tracetargets.py @@ -0,0 +1,117 @@ +# SPDX-License-Identifer: Apache-2.0 +# Copyright 2021 The Meson development team + +from .common import cmake_is_debug +from .. import mlog + +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 + +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.Optional['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: + if reg_is_lib.match(curr): + res.libraries += [curr] + elif Path(curr).is_absolute() and Path(curr).exists(): + res.libraries += [curr] + elif env.machines.build.is_windows() and reg_is_maybe_bare_lib.match(curr) and clib_compiler is not None: + # On Windows, 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. + res.libraries += clib_compiler.find_library(curr, env, []) + 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] + + res.include_directories = sorted(set(res.include_directories)) + res.link_flags = sorted(set(res.link_flags)) + res.public_compile_opts = sorted(set(res.public_compile_opts)) + res.libraries = sorted(set(res.libraries)) + + return res diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index 1b5e2f0..a6898f8 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -15,7 +15,7 @@ from .base import ExternalDependency, DependencyException, 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 ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args, CMakeTarget, resolve_cmake_trace_targets, cmake_is_debug from .. import mlog from pathlib import Path import functools @@ -439,18 +439,6 @@ class CMakeDependency(ExternalDependency): 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: @@ -506,6 +494,7 @@ class CMakeDependency(ExternalDependency): # - 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 + is_debug = cmake_is_debug(self.env) cm_tag_map = {'debug': is_debug, 'optimized': not is_debug, 'general': True} for i in libs_raw: if i.lower() in cm_tag_map: @@ -521,7 +510,12 @@ class CMakeDependency(ExternalDependency): # 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 + self.link_args = [] + for j in libs: + rtgt = resolve_cmake_trace_targets(j, self.traceparser, self.env, clib_compiler=self.clib_compiler) + self.link_args += rtgt.libraries + self.compile_args += [f'-I{x}' for x in rtgt.include_directories] + self.compile_args += rtgt.public_compile_opts mlog.debug(f'using old-style CMake variables for dependency {name}') mlog.debug(f'Include Dirs: {incDirs}') mlog.debug(f'Compiler Definitions: {defs}') @@ -536,13 +530,10 @@ class CMakeDependency(ExternalDependency): # 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: @@ -552,92 +543,27 @@ class CMakeDependency(ExternalDependency): '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, - # 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. - libraries += self.clib_compiler.find_library(j, self.env, []) - 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] + rtgt = resolve_cmake_trace_targets(i ,self.traceparser, self.env, + clib_compiler=self.clib_compiler, + not_found_warning=lambda x: mlog.warning('CMake: Dependency', mlog.bold(x), 'for', mlog.bold(name), 'was not found') + ) + incDirs += rtgt.include_directories + compileOptions += rtgt.public_compile_opts + libraries += rtgt.libraries + rtgt.link_flags # 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.compile_args = compileOptions + [f'-I{x}' for x in incDirs] self.link_args = libraries def _get_build_dir(self) -> Path: diff --git a/test cases/linuxlike/13 cmake dependency/cmake/FindImportedOldStyle.cmake b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedOldStyle.cmake new file mode 100644 index 0000000..595b887 --- /dev/null +++ b/test cases/linuxlike/13 cmake dependency/cmake/FindImportedOldStyle.cmake @@ -0,0 +1,5 @@ +find_package(ZLIB) + +set(IMPORTEDOLDSTYLE_LIBRARIES ZLIB::ZLIB) +set(IMPORTEDOLDSTYLE_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}) +set(IMPORTEDOLDSTYLE_FOUND ON) diff --git a/test cases/linuxlike/13 cmake dependency/meson.build b/test cases/linuxlike/13 cmake dependency/meson.build index 94d07d1..ae4cc42 100644 --- a/test cases/linuxlike/13 cmake dependency/meson.build +++ b/test cases/linuxlike/13 cmake dependency/meson.build @@ -58,6 +58,15 @@ depm1 = dependency('SomethingLikeZLIB', required : true, components : 'required_ depm2 = dependency('SomethingLikeZLIB', required : true, components : 'required_comp', method : 'cmake', cmake_module_path : ['cmake']) depm3 = dependency('SomethingLikeZLIB', required : true, components : ['required_comp'], cmake_module_path : 'cmake') + +# Mix of imported targets and old style variables + +depio1 = dependency('ImportedOldStyle', required : true, cmake_module_path : 'cmake') + +# Try to actually link with depio1, since we are doing even more "fun" stuff there +exe4 = executable('zlibprog4', 'prog.c', dependencies : depio1) +test('zlibtest4', exe4) + # Test some edge cases with spaces, etc. (but only for CMake >= 3.15) if cm_vers.version_compare('>=3.15') -- cgit v1.1