typing: fully annotate cmake.interpreter

pull/7794/head
Daniel Mensinger 4 years ago
parent c2d55afcb5
commit 2f3ef6f1af
No known key found for this signature in database
GPG Key ID: 54DD94C131E277D4
  1. 369
      mesonbuild/cmake/interpreter.py
  2. 1
      run_mypy.py

@ -15,13 +15,12 @@
# This class contains the basic functionality needed to run any interpreter # This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool. # or an interpreter-based tool.
from .common import CMakeException, CMakeTarget, TargetOptions from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel
from .fileapi import CMakeFileAPI from .fileapi import CMakeFileAPI
from .executor import CMakeExecutor from .executor import CMakeExecutor
from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .traceparser import CMakeTraceParser, CMakeGeneratorTarget
from .. import mlog, mesonlib from .. import mlog, mesonlib
from ..environment import Environment
from ..mesonlib import MachineChoice, OrderedSet, version_compare from ..mesonlib import MachineChoice, OrderedSet, version_compare
from ..mesondata import mesondata from ..mesondata import mesondata
from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header
@ -51,6 +50,11 @@ from ..mparser import (
if T.TYPE_CHECKING: if T.TYPE_CHECKING:
from ..build import Build from ..build import Build
from ..backend.backends import Backend from ..backend.backends import Backend
from ..environment import Environment
TYPE_mixed = T.Union[str, int, bool, BaseNode]
TYPE_mixed_list = T.Union[TYPE_mixed, T.Sequence[TYPE_mixed]]
TYPE_mixed_kwargs = T.Dict[str, TYPE_mixed_list]
# Disable all warnings automaticall enabled with --trace and friends # Disable all warnings automaticall enabled with --trace and friends
# See https://cmake.org/cmake/help/latest/variable/CMAKE_POLICY_WARNING_CMPNNNN.html # See https://cmake.org/cmake/help/latest/variable/CMAKE_POLICY_WARNING_CMPNNNN.html
@ -138,7 +142,7 @@ class OutputTargetMap:
rm_so_version = re.compile(r'(\.[0-9]+)+$') rm_so_version = re.compile(r'(\.[0-9]+)+$')
def __init__(self, build_dir: str): def __init__(self, build_dir: str):
self.tgt_map = {} self.tgt_map = {} # type: T.Dict[str, T.Union['ConverterTarget', 'ConverterCustomTarget']]
self.build_dir = build_dir self.build_dir = build_dir
def add(self, tgt: T.Union['ConverterTarget', 'ConverterCustomTarget']) -> None: def add(self, tgt: T.Union['ConverterTarget', 'ConverterCustomTarget']) -> None:
@ -185,8 +189,10 @@ class OutputTargetMap:
keys += [self._rel_artifact_key(i), os.path.basename(i), self._base_artifact_key(i)] keys += [self._rel_artifact_key(i), os.path.basename(i), self._base_artifact_key(i)]
return self._return_first_valid_key(keys) return self._return_first_valid_key(keys)
def generated(self, name: str) -> T.Optional[T.Union['ConverterTarget', 'ConverterCustomTarget']]: def generated(self, name: str) -> T.Optional['ConverterCustomTarget']:
return self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)]) res = self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)])
assert res is None or isinstance(res, ConverterCustomTarget)
return res
# Utility functions to generate local keys # Utility functions to generate local keys
def _rel_path(self, fname: str) -> T.Optional[str]: def _rel_path(self, fname: str) -> T.Optional[str]:
@ -213,42 +219,44 @@ class OutputTargetMap:
return '__art_{}__'.format(os.path.basename(fname)) return '__art_{}__'.format(os.path.basename(fname))
class ConverterTarget: class ConverterTarget:
def __init__(self, target: CMakeTarget, env: Environment): def __init__(self, target: CMakeTarget, env: 'Environment') -> None:
self.env = env self.env = env
self.artifacts = target.artifacts self.artifacts = target.artifacts
self.src_dir = target.src_dir self.src_dir = target.src_dir
self.build_dir = target.build_dir self.build_dir = target.build_dir
self.name = target.name self.name = target.name
self.cmake_name = target.name self.cmake_name = target.name
self.full_name = target.full_name self.full_name = target.full_name
self.type = target.type self.type = target.type
self.install = target.install self.install = target.install
self.install_dir = '' self.install_dir = ''
self.link_libraries = target.link_libraries self.link_libraries = target.link_libraries
self.link_flags = target.link_flags + target.link_lang_flags self.link_flags = target.link_flags + target.link_lang_flags
self.depends_raw = [] self.depends_raw = [] # type: T.List[str]
self.depends = [] self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
if target.install_paths: if target.install_paths:
self.install_dir = target.install_paths[0] self.install_dir = target.install_paths[0]
self.languages = [] self.languages = [] # type: T.List[str]
self.sources = [] self.sources = [] # type: T.List[str]
self.generated = [] self.generated = [] # type: T.List[str]
self.includes = [] self.generated_ctgt = [] # type: T.List[CustomTargetReference]
self.sys_includes = [] self.includes = [] # type: T.List[str]
self.link_with = [] self.sys_includes = [] # type: T.List[str]
self.object_libs = [] self.link_with = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
self.compile_opts = {} self.object_libs = [] # type: T.List[ConverterTarget]
self.public_compile_opts = [] self.compile_opts = {} # type: T.Dict[str, T.List[str]]
self.pie = False self.public_compile_opts = [] # type: T.List[str]
self.pie = False
# Project default override options (c_std, cpp_std, etc.) # Project default override options (c_std, cpp_std, etc.)
self.override_options = [] self.override_options = [] # type: T.List[str]
# Convert the target name to a valid meson target name # Convert the target name to a valid meson target name
self.name = _sanitize_cmake_name(self.name) self.name = _sanitize_cmake_name(self.name)
self.generated_raw = [] # type: T.List[str]
for i in target.files: for i in target.files:
# Determine the meson language # Determine the meson language
lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()}
@ -264,12 +272,12 @@ class ConverterTarget:
self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]] self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]]
# Handle include directories # Handle include directories
self.includes += [x.path for x in i.includes if x not in self.includes and not x.isSystem] self.includes += [x.path for x in i.includes if x.path not in self.includes and not x.isSystem]
self.sys_includes += [x.path for x in i.includes if x not in self.sys_includes and x.isSystem] self.sys_includes += [x.path for x in i.includes if x.path not in self.sys_includes and x.isSystem]
# Add sources to the right array # Add sources to the right array
if i.is_generated: if i.is_generated:
self.generated += i.sources self.generated_raw += i.sources
else: else:
self.sources += i.sources self.sources += i.sources
@ -306,7 +314,7 @@ class ConverterTarget:
# Sometimes projects pass generated source files as compiler # Sometimes projects pass generated source files as compiler
# flags. Add these as generated sources to ensure that the # flags. Add these as generated sources to ensure that the
# corresponding custom target is run.2 # corresponding custom target is run.2
self.generated += [j] self.generated_raw += [j]
temp += [j] temp += [j]
elif j in blacklist_compiler_flags: elif j in blacklist_compiler_flags:
pass pass
@ -339,7 +347,7 @@ class ConverterTarget:
cfg = '' cfg = ''
otherDeps = [] otherDeps = []
libraries = [] libraries = []
mlog.debug(tgt) mlog.debug(str(tgt))
if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties: if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
self.includes += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x] self.includes += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x]
@ -424,8 +432,8 @@ class ConverterTarget:
for i in self.languages: for i in self.languages:
supported += list(lang_suffixes[i]) supported += list(lang_suffixes[i])
supported = ['.{}'.format(x) for x in supported] supported = ['.{}'.format(x) for x in supported]
self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])] self.sources = [x for x in self.sources if any([x.endswith(y) for y in supported])]
self.generated = [x for x in self.generated if any([x.endswith(y) for y in supported])] self.generated_raw = [x for x in self.generated_raw if any([x.endswith(y) for y in supported])]
# Make paths relative # Make paths relative
def rel_path(x: str, is_header: bool, is_generated: bool) -> T.Optional[str]: def rel_path(x: str, is_header: bool, is_generated: bool) -> T.Optional[str]:
@ -434,7 +442,7 @@ class ConverterTarget:
x = os.path.normpath(x) x = os.path.normpath(x)
if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated: if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated:
if ( if (
any([os.path.commonpath([x, os.path.normpath(os.path.join(root_src_dir, y))]) == x for y in self.generated]) any([os.path.commonpath([x, os.path.normpath(os.path.join(root_src_dir, y))]) == x for y in self.generated_raw])
and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir() and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir()
): ):
os.makedirs(x) os.makedirs(x)
@ -465,29 +473,27 @@ class ConverterTarget:
return os.path.relpath(x, root_src_dir) return os.path.relpath(x, root_src_dir)
return x return x
def custom_target(x: str):
ctgt = output_target_map.generated(x)
if ctgt:
assert(isinstance(ctgt, ConverterCustomTarget))
ref = ctgt.get_ref(x)
assert(isinstance(ref, CustomTargetReference) and ref.valid())
return ref
return x
build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir)) build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir))
self.generated = [rel_path(x, False, True) for x in self.generated] self.generated_raw = [rel_path(x, False, True) for x in self.generated_raw]
self.includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.includes)] + [build_dir_rel])) self.includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.includes)] + [build_dir_rel]))
self.sys_includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.sys_includes)])) self.sys_includes = list(OrderedSet([rel_path(x, True, False) for x in OrderedSet(self.sys_includes)]))
self.sources = [rel_path(x, False, False) for x in self.sources] self.sources = [rel_path(x, False, False) for x in self.sources]
# Resolve custom targets # Resolve custom targets
self.generated = [custom_target(x) for x in self.generated] for gen_file in self.generated_raw:
ctgt = output_target_map.generated(gen_file)
if ctgt:
assert isinstance(ctgt, ConverterCustomTarget)
ref = ctgt.get_ref(gen_file)
assert isinstance(ref, CustomTargetReference) and ref.valid()
self.generated_ctgt += [ref]
elif gen_file is not None:
self.generated += [gen_file]
# Remove delete entries # Remove delete entries
self.includes = [x for x in self.includes if x is not None] self.includes = [x for x in self.includes if x is not None]
self.sys_includes = [x for x in self.sys_includes if x is not None] self.sys_includes = [x for x in self.sys_includes if x is not None]
self.sources = [x for x in self.sources if x is not None] self.sources = [x for x in self.sources if x is not None]
self.generated = [x for x in self.generated if x is not None]
# Make sure '.' is always in the include directories # Make sure '.' is always in the include directories
if '.' not in self.includes: if '.' not in self.includes:
@ -511,20 +517,20 @@ class ConverterTarget:
# Handle explicit CMake add_dependency() calls # Handle explicit CMake add_dependency() calls
for i in self.depends_raw: for i in self.depends_raw:
tgt = output_target_map.target(i) dep_tgt = output_target_map.target(i)
if tgt: if dep_tgt:
self.depends.append(tgt) self.depends.append(dep_tgt)
def process_object_libs(self, obj_target_list: T.List['ConverterTarget'], linker_workaround: bool): def process_object_libs(self, obj_target_list: T.List['ConverterTarget'], linker_workaround: bool) -> None:
# Try to detect the object library(s) from the generated input sources # Try to detect the object library(s) from the generated input sources
temp = [x for x in self.generated if isinstance(x, str)] temp = [x for x in self.generated]
temp = [os.path.basename(x) for x in temp] temp = [os.path.basename(x) for x in temp]
temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])] temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])]
temp = [os.path.splitext(x)[0] for x in temp] temp = [os.path.splitext(x)[0] for x in temp]
exts = self._all_source_suffixes() exts = self._all_source_suffixes()
# Temp now stores the source filenames of the object files # Temp now stores the source filenames of the object files
for i in obj_target_list: for i in obj_target_list:
source_files = [x for x in i.sources + i.generated if isinstance(x, str)] source_files = [x for x in i.sources + i.generated]
source_files = [os.path.basename(x) for x in source_files] source_files = [os.path.basename(x) for x in source_files]
for j in temp: for j in temp:
# On some platforms (specifically looking at you Windows with vs20xy backend) CMake does # On some platforms (specifically looking at you Windows with vs20xy backend) CMake does
@ -546,15 +552,17 @@ class ConverterTarget:
break break
# Filter out object files from the sources # Filter out object files from the sources
self.generated = [x for x in self.generated if not isinstance(x, str) or not any([x.endswith('.' + y) for y in obj_suffixes])] self.generated = [x for x in self.generated if not any([x.endswith('.' + y) for y in obj_suffixes])]
def _append_objlib_sources(self, tgt: 'ConverterTarget') -> None: def _append_objlib_sources(self, tgt: 'ConverterTarget') -> None:
self.includes += tgt.includes self.includes += tgt.includes
self.sources += tgt.sources self.sources += tgt.sources
self.generated += tgt.generated self.generated += tgt.generated
self.sources = list(OrderedSet(self.sources)) self.generated_ctgt += tgt.generated_ctgt
self.generated = list(OrderedSet(self.generated)) self.includes = list(OrderedSet(self.includes))
self.includes = list(OrderedSet(self.includes)) self.sources = list(OrderedSet(self.sources))
self.generated = list(OrderedSet(self.generated))
self.generated_ctgt = list(OrderedSet(self.generated_ctgt))
# Inherit compiler arguments since they may be required for building # Inherit compiler arguments since they may be required for building
for lang, opts in tgt.compile_opts.items(): for lang, opts in tgt.compile_opts.items():
@ -574,9 +582,16 @@ class ConverterTarget:
lang_opts = self.env.coredata.compiler_options.build.get(lang, None) lang_opts = self.env.coredata.compiler_options.build.get(lang, None)
if not lang_opts or 'std' not in lang_opts: if not lang_opts or 'std' not in lang_opts:
return [] return []
return lang_opts['std'].choices res = lang_opts['std'].choices
# TODO: Get rid of this once we have propper typing for options
assert isinstance(res, list)
for i in res:
assert isinstance(i, str)
def process_inter_target_dependencies(self): return res
def process_inter_target_dependencies(self) -> None:
# Move the dependencies from all transfer_dependencies_from to the target # Move the dependencies from all transfer_dependencies_from to the target
to_process = list(self.depends) to_process = list(self.depends)
processed = [] processed = []
@ -589,7 +604,7 @@ class ConverterTarget:
new_deps += [i] new_deps += [i]
self.depends = list(OrderedSet(new_deps)) self.depends = list(OrderedSet(new_deps))
def cleanup_dependencies(self): def cleanup_dependencies(self) -> None:
# Clear the dependencies from targets that where moved from # Clear the dependencies from targets that where moved from
if self.meson_func() in transfer_dependencies_from: if self.meson_func() in transfer_dependencies_from:
self.depends = [] self.depends = []
@ -613,6 +628,7 @@ class ConverterTarget:
mlog.log(' -- sys_includes: ', mlog.bold(str(self.sys_includes))) mlog.log(' -- sys_includes: ', mlog.bold(str(self.sys_includes)))
mlog.log(' -- sources: ', mlog.bold(str(self.sources))) mlog.log(' -- sources: ', mlog.bold(str(self.sources)))
mlog.log(' -- generated: ', mlog.bold(str(self.generated))) mlog.log(' -- generated: ', mlog.bold(str(self.generated)))
mlog.log(' -- generated_ctgt: ', mlog.bold(str(self.generated_ctgt)))
mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false')) mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false'))
mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options))) mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options)))
mlog.log(' -- depends: ', mlog.bold(str(self.depends))) mlog.log(' -- depends: ', mlog.bold(str(self.depends)))
@ -621,7 +637,7 @@ class ConverterTarget:
mlog.log(' -', key, '=', mlog.bold(str(val))) mlog.log(' -', key, '=', mlog.bold(str(val)))
class CustomTargetReference: class CustomTargetReference:
def __init__(self, ctgt: 'ConverterCustomTarget', index: int): def __init__(self, ctgt: 'ConverterCustomTarget', index: int) -> None:
self.ctgt = ctgt # type: ConverterCustomTarget self.ctgt = ctgt # type: ConverterCustomTarget
self.index = index # type: int self.index = index # type: int
@ -641,24 +657,25 @@ class ConverterCustomTarget:
tgt_counter = 0 # type: int tgt_counter = 0 # type: int
out_counter = 0 # type: int out_counter = 0 # type: int
def __init__(self, target: CMakeGeneratorTarget): def __init__(self, target: CMakeGeneratorTarget) -> None:
assert(target.current_bin_dir is not None) assert target.current_bin_dir is not None
assert(target.current_src_dir is not None) assert target.current_src_dir is not None
self.name = target.name self.name = target.name
if not self.name: if not self.name:
self.name = 'custom_tgt_{}'.format(ConverterCustomTarget.tgt_counter) self.name = 'custom_tgt_{}'.format(ConverterCustomTarget.tgt_counter)
ConverterCustomTarget.tgt_counter += 1 ConverterCustomTarget.tgt_counter += 1
self.cmake_name = str(self.name) self.cmake_name = str(self.name)
self.original_outputs = list(target.outputs) self.original_outputs = list(target.outputs)
self.outputs = [os.path.basename(x) for x in self.original_outputs] self.outputs = [os.path.basename(x) for x in self.original_outputs]
self.conflict_map = {} self.conflict_map = {} # type: T.Dict[str, str]
self.command = target.command self.command = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]]
self.working_dir = target.working_dir self.working_dir = target.working_dir
self.depends_raw = target.depends self.depends_raw = target.depends
self.inputs = [] self.inputs = [] # type: T.List[T.Union[str, CustomTargetReference]]
self.depends = [] self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
self.current_bin_dir = Path(target.current_bin_dir) self.current_bin_dir = Path(target.current_bin_dir)
self.current_src_dir = Path(target.current_src_dir) self.current_src_dir = Path(target.current_src_dir)
self._raw_target = target
# Convert the target name to a valid meson target name # Convert the target name to a valid meson target name
self.name = _sanitize_cmake_name(self.name) self.name = _sanitize_cmake_name(self.name)
@ -700,12 +717,12 @@ class ConverterCustomTarget:
self.outputs = temp_outputs self.outputs = temp_outputs
# Check if the command is a build target # Check if the command is a build target
commands = [] commands = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]]
for i in self.command: for curr_cmd in self._raw_target.command:
assert(isinstance(i, list)) assert(isinstance(curr_cmd, list))
cmd = [] cmd = [] # type: T.List[T.Union[str, ConverterTarget]]
for j in i: for j in curr_cmd:
if not j: if not j:
continue continue
target = output_target_map.executable(j) target = output_target_map.executable(j)
@ -759,9 +776,11 @@ class ConverterCustomTarget:
elif tgt: elif tgt:
self.depends += [tgt] self.depends += [tgt]
elif gen: elif gen:
self.inputs += [gen.get_ref(i)] ctgt_ref = gen.get_ref(i)
assert ctgt_ref is not None
self.inputs += [ctgt_ref]
def process_inter_target_dependencies(self): def process_inter_target_dependencies(self) -> None:
# Move the dependencies from all transfer_dependencies_from to the target # Move the dependencies from all transfer_dependencies_from to the target
to_process = list(self.depends) to_process = list(self.depends)
processed = [] processed = []
@ -799,7 +818,7 @@ class CMakeAPI(Enum):
FILE = 2 FILE = 2
class CMakeInterpreter: class CMakeInterpreter:
def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: Environment, backend: 'Backend'): def __init__(self, build: 'Build', subdir: str, src_dir: str, install_prefix: str, env: 'Environment', backend: 'Backend'):
assert(hasattr(backend, 'name')) assert(hasattr(backend, 'name'))
self.build = build self.build = build
self.subdir = subdir self.subdir = subdir
@ -815,21 +834,21 @@ class CMakeInterpreter:
self.fileapi = CMakeFileAPI(self.build_dir) self.fileapi = CMakeFileAPI(self.build_dir)
# Raw CMake results # Raw CMake results
self.bs_files = [] self.bs_files = [] # type: T.List[str]
self.codemodel_configs = None self.codemodel_configs = None # type: T.Optional[T.List[CMakeConfiguration]]
self.raw_trace = None self.raw_trace = None # type: T.Optional[str]
# Analysed data # Analysed data
self.project_name = '' self.project_name = ''
self.languages = [] self.languages = [] # type: T.List[str]
self.targets = [] self.targets = [] # type: T.List[ConverterTarget]
self.custom_targets = [] # type: T.List[ConverterCustomTarget] self.custom_targets = [] # type: T.List[ConverterCustomTarget]
self.trace = CMakeTraceParser('', '') # Will be replaced in analyse self.trace = CMakeTraceParser('', '') # Will be replaced in analyse
self.output_target_map = OutputTargetMap(self.build_dir) self.output_target_map = OutputTargetMap(self.build_dir)
# Generated meson data # Generated meson data
self.generated_targets = {} self.generated_targets = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]]
self.internal_name_map = {} self.internal_name_map = {} # type: T.Dict[str, str]
def configure(self, extra_cmake_options: T.List[str]) -> None: def configure(self, extra_cmake_options: T.List[str]) -> None:
for_machine = MachineChoice.HOST # TODO make parameter for_machine = MachineChoice.HOST # TODO make parameter
@ -933,9 +952,11 @@ class CMakeInterpreter:
# Get CMake build system files # Get CMake build system files
bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files') bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files')
assert isinstance(bs_reply, ReplyCMakeInputs)
# Now get the CMake code model # Now get the CMake code model
cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model') cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model')
assert isinstance(cm_reply, ReplyCodeModel)
src_dir = bs_reply.src_dir src_dir = bs_reply.src_dir
self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp]
@ -958,63 +979,64 @@ class CMakeInterpreter:
# Find all targets # Find all targets
added_target_names = [] # type: T.List[str] added_target_names = [] # type: T.List[str]
for i in self.codemodel_configs: for i_0 in self.codemodel_configs:
for j in i.projects: for j_0 in i_0.projects:
if not self.project_name: if not self.project_name:
self.project_name = j.name self.project_name = j_0.name
for k in j.targets: for k_0 in j_0.targets:
# Avoid duplicate targets from different configurations and known # Avoid duplicate targets from different configurations and known
# dummy CMake internal target types # dummy CMake internal target types
if k.type not in skip_targets and k.name not in added_target_names: if k_0.type not in skip_targets and k_0.name not in added_target_names:
added_target_names += [k.name] added_target_names += [k_0.name]
self.targets += [ConverterTarget(k, self.env)] self.targets += [ConverterTarget(k_0, self.env)]
# Add interface targets from trace, if not already present. # Add interface targets from trace, if not already present.
# This step is required because interface targets were removed from # This step is required because interface targets were removed from
# the CMake file API output. # the CMake file API output.
api_target_name_list = [x.name for x in self.targets] api_target_name_list = [x.name for x in self.targets]
for i in self.trace.targets.values(): for i_1 in self.trace.targets.values():
if i.type != 'INTERFACE' or i.name in api_target_name_list or i.imported: if i_1.type != 'INTERFACE' or i_1.name in api_target_name_list or i_1.imported:
continue continue
dummy = CMakeTarget({ dummy = CMakeTarget({
'name': i.name, 'name': i_1.name,
'type': 'INTERFACE_LIBRARY', 'type': 'INTERFACE_LIBRARY',
'sourceDirectory': self.src_dir, 'sourceDirectory': self.src_dir,
'buildDirectory': self.build_dir, 'buildDirectory': self.build_dir,
}) })
self.targets += [ConverterTarget(dummy, self.env)] self.targets += [ConverterTarget(dummy, self.env)]
for i in self.trace.custom_targets: for i_2 in self.trace.custom_targets:
self.custom_targets += [ConverterCustomTarget(i)] self.custom_targets += [ConverterCustomTarget(i_2)]
# generate the output_target_map # generate the output_target_map
for i in [*self.targets, *self.custom_targets]: for i_3 in [*self.targets, *self.custom_targets]:
self.output_target_map.add(i) assert isinstance(i_3, (ConverterTarget, ConverterCustomTarget))
self.output_target_map.add(i_3)
# First pass: Basic target cleanup # First pass: Basic target cleanup
object_libs = [] object_libs = []
custom_target_outputs = [] # type: T.List[str] custom_target_outputs = [] # type: T.List[str]
for i in self.custom_targets: for ctgt in self.custom_targets:
i.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace) ctgt.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace)
for i in self.targets: for tgt in self.targets:
i.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace) tgt.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace)
if i.type == 'OBJECT_LIBRARY': if tgt.type == 'OBJECT_LIBRARY':
object_libs += [i] object_libs += [tgt]
self.languages += [x for x in i.languages if x not in self.languages] self.languages += [x for x in tgt.languages if x not in self.languages]
# Second pass: Detect object library dependencies # Second pass: Detect object library dependencies
for i in self.targets: for tgt in self.targets:
i.process_object_libs(object_libs, self._object_lib_workaround()) tgt.process_object_libs(object_libs, self._object_lib_workaround())
# Third pass: Reassign dependencies to avoid some loops # Third pass: Reassign dependencies to avoid some loops
for i in self.targets: for tgt in self.targets:
i.process_inter_target_dependencies() tgt.process_inter_target_dependencies()
for i in self.custom_targets: for ctgt in self.custom_targets:
i.process_inter_target_dependencies() ctgt.process_inter_target_dependencies()
# Fourth pass: Remove rassigned dependencies # Fourth pass: Remove rassigned dependencies
for i in self.targets: for tgt in self.targets:
i.cleanup_dependencies() tgt.cleanup_dependencies()
mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.') mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.')
@ -1022,7 +1044,7 @@ class CMakeInterpreter:
if not self.project_name: if not self.project_name:
raise CMakeException('CMakeInterpreter was not analysed') raise CMakeException('CMakeInterpreter was not analysed')
def token(tid: str = 'string', val='') -> Token: def token(tid: str = 'string', val: TYPE_mixed = '') -> Token:
return Token(tid, self.subdir, 0, 0, 0, None, val) return Token(tid, self.subdir, 0, 0, 0, None, val)
def string(value: str) -> StringNode: def string(value: str) -> StringNode:
@ -1034,7 +1056,7 @@ class CMakeInterpreter:
def number(value: int) -> NumberNode: def number(value: int) -> NumberNode:
return NumberNode(token(val=value)) return NumberNode(token(val=value))
def nodeify(value): def nodeify(value: TYPE_mixed_list) -> BaseNode:
if isinstance(value, str): if isinstance(value, str):
return string(value) return string(value)
elif isinstance(value, bool): elif isinstance(value, bool):
@ -1043,34 +1065,38 @@ class CMakeInterpreter:
return number(value) return number(value)
elif isinstance(value, list): elif isinstance(value, list):
return array(value) return array(value)
return value elif isinstance(value, BaseNode):
return value
raise RuntimeError('invalid type of value: {} ({})'.format(type(value).__name__, str(value)))
def indexed(node: BaseNode, index: int) -> IndexNode: def indexed(node: BaseNode, index: int) -> IndexNode:
return IndexNode(node, nodeify(index)) return IndexNode(node, nodeify(index))
def array(elements) -> ArrayNode: def array(elements: TYPE_mixed_list) -> ArrayNode:
args = ArgumentNode(token()) args = ArgumentNode(token())
if not isinstance(elements, list): if not isinstance(elements, list):
elements = [args] elements = [args]
args.arguments += [nodeify(x) for x in elements if x is not None] args.arguments += [nodeify(x) for x in elements if x is not None]
return ArrayNode(args, 0, 0, 0, 0) return ArrayNode(args, 0, 0, 0, 0)
def function(name: str, args=None, kwargs=None) -> FunctionNode: def function(name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> FunctionNode:
args = [] if args is None else args args = [] if args is None else args
kwargs = {} if kwargs is None else kwargs kwargs = {} if kwargs is None else kwargs
args_n = ArgumentNode(token()) args_n = ArgumentNode(token())
if not isinstance(args, list): if not isinstance(args, list):
assert isinstance(args, (str, int, bool, BaseNode))
args = [args] args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n) func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n)
return func_n return func_n
def method(obj: BaseNode, name: str, args=None, kwargs=None) -> MethodNode: def method(obj: BaseNode, name: str, args: T.Optional[TYPE_mixed_list] = None, kwargs: T.Optional[TYPE_mixed_kwargs] = None) -> MethodNode:
args = [] if args is None else args args = [] if args is None else args
kwargs = {} if kwargs is None else kwargs kwargs = {} if kwargs is None else kwargs
args_n = ArgumentNode(token()) args_n = ArgumentNode(token())
if not isinstance(args, list): if not isinstance(args, list):
assert isinstance(args, (str, int, bool, BaseNode))
args = [args] args = [args]
args_n.arguments = [nodeify(x) for x in args if x is not None] args_n.arguments = [nodeify(x) for x in args if x is not None]
args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None} args_n.kwargs = {id_node(k): nodeify(v) for k, v in kwargs.items() if v is not None}
@ -1086,9 +1112,9 @@ class CMakeInterpreter:
# Add the run script for custom commands # Add the run script for custom commands
# Add the targets # Add the targets
processing = [] processing = [] # type: T.List[str]
processed = {} processed = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]]
name_map = {} name_map = {} # type: T.Dict[str, str]
def extract_tgt(tgt: T.Union[ConverterTarget, ConverterCustomTarget, CustomTargetReference]) -> IdNode: def extract_tgt(tgt: T.Union[ConverterTarget, ConverterCustomTarget, CustomTargetReference]) -> IdNode:
tgt_name = None tgt_name = None
@ -1105,24 +1131,24 @@ class CMakeInterpreter:
raise CMakeException('Cycle in CMake inputs/dependencies detected') raise CMakeException('Cycle in CMake inputs/dependencies detected')
processing.append(tgt.name) processing.append(tgt.name)
def resolve_ctgt_ref(ref: CustomTargetReference) -> BaseNode: def resolve_ctgt_ref(ref: CustomTargetReference) -> T.Union[IdNode, IndexNode]:
tgt_var = extract_tgt(ref) tgt_var = extract_tgt(ref)
if len(ref.ctgt.outputs) == 1: if len(ref.ctgt.outputs) == 1:
return tgt_var return tgt_var
else: else:
return indexed(tgt_var, ref.index) return indexed(tgt_var, ref.index)
def process_target(tgt: ConverterTarget): def process_target(tgt: ConverterTarget) -> None:
detect_cycle(tgt) detect_cycle(tgt)
# First handle inter target dependencies # First handle inter target dependencies
link_with = [] link_with = [] # type: T.List[IdNode]
objec_libs = [] # type: T.List[IdNode] objec_libs = [] # type: T.List[IdNode]
sources = [] sources = [] # type: T.List[str]
generated = [] generated = [] # type: T.List[T.Union[IdNode, IndexNode]]
generated_filenames = [] generated_filenames = [] # type: T.List[str]
custom_targets = [] custom_targets = [] # type: T.List[ConverterCustomTarget]
dependencies = [] dependencies = [] # type: T.List[IdNode]
for i in tgt.link_with: for i in tgt.link_with:
assert(isinstance(i, ConverterTarget)) assert(isinstance(i, ConverterTarget))
if i.name not in processed: if i.name not in processed:
@ -1141,16 +1167,17 @@ class CMakeInterpreter:
dependencies += [extract_tgt(i)] dependencies += [extract_tgt(i)]
# Generate the source list and handle generated sources # Generate the source list and handle generated sources
for i in tgt.sources + tgt.generated: sources += tgt.sources
if isinstance(i, CustomTargetReference): sources += tgt.generated
if i.ctgt.name not in processed:
process_custom_target(i.ctgt) for ctgt_ref in tgt.generated_ctgt:
generated += [resolve_ctgt_ref(i)] ctgt = ctgt_ref.ctgt
generated_filenames += [i.filename()] if ctgt.name not in processed:
if i.ctgt not in custom_targets: process_custom_target(ctgt)
custom_targets += [i.ctgt] generated += [resolve_ctgt_ref(ctgt_ref)]
else: generated_filenames += [ctgt_ref.filename()]
sources += [i] if ctgt not in custom_targets:
custom_targets += [ctgt]
# Add all header files from all used custom targets. This # Add all header files from all used custom targets. This
# ensures that all custom targets are built before any # ensures that all custom targets are built before any
@ -1158,12 +1185,12 @@ class CMakeInterpreter:
# header files are present. This step is necessary because # header files are present. This step is necessary because
# CMake always ensures that a custom target is executed # CMake always ensures that a custom target is executed
# before another target if at least one output is used. # before another target if at least one output is used.
for i in custom_targets: for ctgt in custom_targets:
for j in i.outputs: for j in ctgt.outputs:
if not is_header(j) or j in generated_filenames: if not is_header(j) or j in generated_filenames:
continue continue
generated += [resolve_ctgt_ref(i.get_ref(j))] generated += [resolve_ctgt_ref(ctgt.get_ref(j))]
generated_filenames += [j] generated_filenames += [j]
# Determine the meson function to use for the build target # Determine the meson function to use for the build target
@ -1190,7 +1217,7 @@ class CMakeInterpreter:
'install': install_tgt, 'install': install_tgt,
'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options), 'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options),
'objects': [method(x, 'extract_all_objects') for x in objec_libs], 'objects': [method(x, 'extract_all_objects') for x in objec_libs],
} } # type: TYPE_mixed_kwargs
# Only set if installed and only override if it is set # Only set if installed and only override if it is set
if install_tgt and tgt.install_dir: if install_tgt and tgt.install_dir:
@ -1212,7 +1239,7 @@ class CMakeInterpreter:
'link_with': id_node(tgt_var), 'link_with': id_node(tgt_var),
'compile_args': tgt.public_compile_opts, 'compile_args': tgt.public_compile_opts,
'include_directories': id_node(inc_var), 'include_directories': id_node(inc_var),
} } # type: TYPE_mixed_kwargs
if dependencies: if dependencies:
generated += dependencies generated += dependencies
@ -1230,7 +1257,7 @@ class CMakeInterpreter:
tgt_var = None tgt_var = None
else: else:
src_node = assign(src_var, function('files', sources)) src_node = assign(src_var, function('files', sources))
tgt_node = assign(tgt_var, function(tgt_func, [tgt_var, [id_node(src_var)] + generated], tgt_kwargs)) tgt_node = assign(tgt_var, function(tgt_func, [tgt_var, id_node(src_var), *generated], tgt_kwargs))
node_list += [src_node, tgt_node] node_list += [src_node, tgt_node]
if tgt_func in ['static_library', 'shared_library']: if tgt_func in ['static_library', 'shared_library']:
dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs))
@ -1290,19 +1317,19 @@ class CMakeInterpreter:
'output': tgt.outputs, 'output': tgt.outputs,
'command': command, 'command': command,
'depends': [resolve_source(x) for x in tgt.depends], 'depends': [resolve_source(x) for x in tgt.depends],
} } # type: TYPE_mixed_kwargs
root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))] root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))]
processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'} processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'}
name_map[tgt.cmake_name] = tgt.name name_map[tgt.cmake_name] = tgt.name
# Now generate the target function calls # Now generate the target function calls
for i in self.custom_targets: for ctgt in self.custom_targets:
if i.name not in processed: if ctgt.name not in processed:
process_custom_target(i) process_custom_target(ctgt)
for i in self.targets: for tgt in self.targets:
if i.name not in processed: if tgt.name not in processed:
process_target(i) process_target(tgt)
self.generated_targets = processed self.generated_targets = processed
self.internal_name_map = name_map self.internal_name_map = name_map

@ -9,6 +9,7 @@ import typing as T
modules = [ modules = [
# fully typed submodules # fully typed submodules
'mesonbuild/ast', 'mesonbuild/ast',
'mesonbuild/cmake',
'mesonbuild/compilers/mixins', 'mesonbuild/compilers/mixins',
'mesonbuild/scripts', 'mesonbuild/scripts',
'mesonbuild/wrap', 'mesonbuild/wrap',

Loading…
Cancel
Save