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
This commit is contained in:
Daniel Mensinger 2021-11-27 19:58:04 +01:00 committed by Jussi Pakkanen
parent 4f882ff8ec
commit 45c5300496
7 changed files with 171 additions and 169 deletions

View File

@ -34,9 +34,12 @@ __all__ = [
'cmake_get_generator_args', 'cmake_get_generator_args',
'cmake_defines_to_args', 'cmake_defines_to_args',
'check_cmake_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 .client import CMakeClient
from .executor import CMakeExecutor from .executor import CMakeExecutor
from .fileapi import CMakeFileAPI from .fileapi import CMakeFileAPI
@ -44,3 +47,4 @@ from .generator import parse_generator_expressions
from .interpreter import CMakeInterpreter from .interpreter import CMakeInterpreter
from .toolchain import CMakeToolchain, CMakeExecScope from .toolchain import CMakeToolchain, CMakeExecScope
from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser from .traceparser import CMakeTarget, CMakeTraceLine, CMakeTraceParser
from .tracetargets import resolve_cmake_trace_targets, ResolvedTarget

View File

@ -61,6 +61,18 @@ blacklist_cmake_defs = [
'MESON_CMAKE_ROOT', '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): class CMakeException(MesonException):
pass pass

View File

@ -21,6 +21,7 @@ from .fileapi import CMakeFileAPI
from .executor import CMakeExecutor from .executor import CMakeExecutor
from .toolchain import CMakeToolchain, CMakeExecScope from .toolchain import CMakeToolchain, CMakeExecScope
from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .traceparser import CMakeTraceParser, CMakeGeneratorTarget
from .tracetargets import resolve_cmake_trace_targets
from .. import mlog, mesonlib from .. import mlog, mesonlib
from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey
from ..mesondata import mesondata from ..mesondata import mesondata
@ -342,84 +343,12 @@ class ConverterTarget:
if tgt: if tgt:
self.depends_raw = trace.targets[self.cmake_name].depends self.depends_raw = trace.targets[self.cmake_name].depends
# TODO refactor this copy paste from CMakeDependency for future releases rtgt = resolve_cmake_trace_targets(self.cmake_name, trace, self.env)
reg_is_lib = re.compile(r'^(-l[a-zA-Z0-9_]+|-l?pthread)$') self.includes += [Path(x) for x in rtgt.include_directories]
to_process = [self.cmake_name] self.link_flags += rtgt.link_flags
processed = [] self.public_compile_opts += rtgt.public_compile_opts
while len(to_process) > 0: self.link_libraries += rtgt.libraries
curr = to_process.pop(0)
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']: 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') mlog.warning('CMake: Target', mlog.bold(self.cmake_name), 'not found in CMake trace. This can lead to build errors')

View File

@ -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

View File

@ -15,7 +15,7 @@
from .base import ExternalDependency, DependencyException, DependencyTypeName from .base import ExternalDependency, DependencyException, DependencyTypeName
from ..mesonlib import is_windows, MesonException, OptionKey, PerMachine, stringlistify, extract_as_list from ..mesonlib import is_windows, MesonException, OptionKey, PerMachine, stringlistify, extract_as_list
from ..mesondata import mesondata 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 .. import mlog
from pathlib import Path from pathlib import Path
import functools import functools
@ -439,18 +439,6 @@ class CMakeDependency(ExternalDependency):
modules = self._map_module_list(modules, components) modules = self._map_module_list(modules, components)
autodetected_module_list = False 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 # Try guessing a CMake target if none is provided
if len(modules) == 0: if len(modules) == 0:
for i in self.traceparser.targets: 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) # - https://cmake.org/cmake/help/latest/command/target_link_libraries.html#overview (the last point in the section)
libs: T.List[str] = [] libs: T.List[str] = []
cfg_matches = True cfg_matches = True
is_debug = cmake_is_debug(self.env)
cm_tag_map = {'debug': is_debug, 'optimized': not is_debug, 'general': True} cm_tag_map = {'debug': is_debug, 'optimized': not is_debug, 'general': True}
for i in libs_raw: for i in libs_raw:
if i.lower() in cm_tag_map: 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 # Try to use old style variables if no module is specified
if len(libs) > 0: if len(libs) > 0:
self.compile_args = list(map(lambda x: f'-I{x}', incDirs)) + defs 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'using old-style CMake variables for dependency {name}')
mlog.debug(f'Include Dirs: {incDirs}') mlog.debug(f'Include Dirs: {incDirs}')
mlog.debug(f'Compiler Definitions: {defs}') mlog.debug(f'Compiler Definitions: {defs}')
@ -536,13 +530,10 @@ class CMakeDependency(ExternalDependency):
# Set dependencies with CMake targets # Set dependencies with CMake targets
# recognise arguments we should pass directly to the linker # 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 = [] incDirs = []
compileDefinitions = []
compileOptions = [] compileOptions = []
libraries = [] libraries = []
for i, required in modules: for i, required in modules:
if i not in self.traceparser.targets: if i not in self.traceparser.targets:
if not required: if not required:
@ -552,92 +543,27 @@ class CMakeDependency(ExternalDependency):
'Try to explicitly specify one or more targets with the "modules" property.\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()))) 'Valid targets are:\n{}'.format(self._original_module_name(i), name, list(self.traceparser.targets.keys())))
targets = [i]
if not autodetected_module_list: if not autodetected_module_list:
self.found_modules += [i] self.found_modules += [i]
while len(targets) > 0: rtgt = resolve_cmake_trace_targets(i ,self.traceparser, self.env,
curr = targets.pop(0) clib_compiler=self.clib_compiler,
not_found_warning=lambda x: mlog.warning('CMake: Dependency', mlog.bold(x), 'for', mlog.bold(name), 'was not found')
# Skip already processed targets )
if curr in processed_targets: incDirs += rtgt.include_directories
continue compileOptions += rtgt.public_compile_opts
libraries += rtgt.libraries + rtgt.link_flags
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]
# Make sure all elements in the lists are unique and sorted # Make sure all elements in the lists are unique and sorted
incDirs = sorted(set(incDirs)) incDirs = sorted(set(incDirs))
compileDefinitions = sorted(set(compileDefinitions))
compileOptions = sorted(set(compileOptions)) compileOptions = sorted(set(compileOptions))
libraries = sorted(set(libraries)) libraries = sorted(set(libraries))
mlog.debug(f'Include Dirs: {incDirs}') mlog.debug(f'Include Dirs: {incDirs}')
mlog.debug(f'Compiler Definitions: {compileDefinitions}')
mlog.debug(f'Compiler Options: {compileOptions}') mlog.debug(f'Compiler Options: {compileOptions}')
mlog.debug(f'Libraries: {libraries}') 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 self.link_args = libraries
def _get_build_dir(self) -> Path: def _get_build_dir(self) -> Path:

View File

@ -0,0 +1,5 @@
find_package(ZLIB)
set(IMPORTEDOLDSTYLE_LIBRARIES ZLIB::ZLIB)
set(IMPORTEDOLDSTYLE_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR})
set(IMPORTEDOLDSTYLE_FOUND ON)

View File

@ -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']) 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') 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) # Test some edge cases with spaces, etc. (but only for CMake >= 3.15)
if cm_vers.version_compare('>=3.15') if cm_vers.version_compare('>=3.15')