Merge pull request #7794 from mensinda/cmTyping

typing: CMake module
pull/7806/head
Daniel Mensinger 4 years ago committed by GitHub
commit be2598a4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      mesonbuild/cmake/client.py
  2. 88
      mesonbuild/cmake/common.py
  3. 66
      mesonbuild/cmake/executor.py
  4. 24
      mesonbuild/cmake/fileapi.py
  5. 3
      mesonbuild/cmake/generator.py
  6. 369
      mesonbuild/cmake/interpreter.py
  7. 77
      mesonbuild/cmake/traceparser.py
  8. 10
      mesonbuild/dependencies/base.py
  9. 102
      run_mypy.py

@ -17,7 +17,6 @@
from .common import CMakeException, CMakeConfiguration, CMakeBuildFile
from .executor import CMakeExecutor
from ..environment import Environment
from ..mesonlib import MachineChoice
from .. import mlog
from contextlib import contextmanager
@ -26,6 +25,9 @@ import typing as T
import json
import os
if T.TYPE_CHECKING:
from ..environment import Environment
CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==['
CMAKE_SERVER_END_STR = ']== "CMake Server" ==]'
@ -36,7 +38,7 @@ CMAKE_MESSAGE_TYPES = {
'progress': ['cookie'],
'reply': ['cookie', 'inReplyTo'],
'signal': ['cookie', 'name'],
}
} # type: T.Dict[str, T.List[str]]
CMAKE_REPLY_TYPES = {
'handshake': [],
@ -44,16 +46,16 @@ CMAKE_REPLY_TYPES = {
'compute': [],
'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'],
'codemodel': ['configurations']
}
} # type: T.Dict[str, T.List[str]]
# Base CMake server message classes
class MessageBase:
def __init__(self, msg_type: str, cookie: str):
def __init__(self, msg_type: str, cookie: str) -> None:
self.type = msg_type
self.cookie = cookie
def to_dict(self) -> dict:
def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]:
return {'type': self.type, 'cookie': self.cookie}
def log(self) -> None:
@ -62,21 +64,21 @@ class MessageBase:
class RequestBase(MessageBase):
cookie_counter = 0
def __init__(self, msg_type: str):
def __init__(self, msg_type: str) -> None:
super().__init__(msg_type, self.gen_cookie())
@staticmethod
def gen_cookie():
def gen_cookie() -> str:
RequestBase.cookie_counter += 1
return 'meson_{}'.format(RequestBase.cookie_counter)
class ReplyBase(MessageBase):
def __init__(self, cookie: str, in_reply_to: str):
def __init__(self, cookie: str, in_reply_to: str) -> None:
super().__init__('reply', cookie)
self.in_reply_to = in_reply_to
class SignalBase(MessageBase):
def __init__(self, cookie: str, signal_name: str):
def __init__(self, cookie: str, signal_name: str) -> None:
super().__init__('signal', cookie)
self.signal_name = signal_name
@ -86,7 +88,7 @@ class SignalBase(MessageBase):
# Special Message classes
class Error(MessageBase):
def __init__(self, cookie: str, message: str):
def __init__(self, cookie: str, message: str) -> None:
super().__init__('error', cookie)
self.message = message
@ -94,7 +96,7 @@ class Error(MessageBase):
mlog.error(mlog.bold('CMake server error:'), mlog.red(self.message))
class Message(MessageBase):
def __init__(self, cookie: str, message: str):
def __init__(self, cookie: str, message: str) -> None:
super().__init__('message', cookie)
self.message = message
@ -103,19 +105,21 @@ class Message(MessageBase):
pass
class Progress(MessageBase):
def __init__(self, cookie: str):
def __init__(self, cookie: str) -> None:
super().__init__('progress', cookie)
def log(self) -> None:
pass
class MessageHello(MessageBase):
def __init__(self, supported_protocol_versions: T.List[dict]):
def __init__(self, supported_protocol_versions: T.List[T.Dict[str, int]]) -> None:
super().__init__('hello', '')
self.supported_protocol_versions = supported_protocol_versions
def supports(self, major: int, minor: T.Optional[int] = None) -> bool:
for i in self.supported_protocol_versions:
assert 'major' in i
assert 'minor' in i
if major == i['major']:
if minor is None or minor == i['minor']:
return True
@ -124,7 +128,7 @@ class MessageHello(MessageBase):
# Request classes
class RequestHandShake(RequestBase):
def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None):
def __init__(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None:
super().__init__('handshake')
self.src_dir = src_dir
self.build_dir = build_dir
@ -132,7 +136,7 @@ class RequestHandShake(RequestBase):
self.vers_major = vers_major
self.vers_minor = vers_minor
def to_dict(self) -> dict:
def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]:
vers = {'major': self.vers_major}
if self.vers_minor is not None:
vers['minor'] = self.vers_minor
@ -154,40 +158,40 @@ class RequestConfigure(RequestBase):
super().__init__('configure')
self.args = args
def to_dict(self) -> dict:
def to_dict(self) -> T.Dict[str, T.Union[str, T.List[str], T.Dict[str, int]]]:
res = super().to_dict()
if self.args:
res['cacheArguments'] = self.args
return res
class RequestCompute(RequestBase):
def __init__(self):
def __init__(self) -> None:
super().__init__('compute')
class RequestCMakeInputs(RequestBase):
def __init__(self):
def __init__(self) -> None:
super().__init__('cmakeInputs')
class RequestCodeModel(RequestBase):
def __init__(self):
def __init__(self) -> None:
super().__init__('codemodel')
# Reply classes
class ReplyHandShake(ReplyBase):
def __init__(self, cookie: str):
def __init__(self, cookie: str) -> None:
super().__init__(cookie, 'handshake')
class ReplyConfigure(ReplyBase):
def __init__(self, cookie: str):
def __init__(self, cookie: str) -> None:
super().__init__(cookie, 'configure')
class ReplyCompute(ReplyBase):
def __init__(self, cookie: str):
def __init__(self, cookie: str) -> None:
super().__init__(cookie, 'compute')
class ReplyCMakeInputs(ReplyBase):
def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: T.List[CMakeBuildFile]):
def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: T.List[CMakeBuildFile]) -> None:
super().__init__(cookie, 'cmakeInputs')
self.cmake_root = cmake_root
self.src_dir = src_dir
@ -202,7 +206,7 @@ class ReplyCMakeInputs(ReplyBase):
mlog.log(str(i))
class ReplyCodeModel(ReplyBase):
def __init__(self, data: dict):
def __init__(self, data: T.Dict[str, T.Any]) -> None:
super().__init__(data['cookie'], 'codemodel')
self.configs = []
for i in data['configurations']:
@ -218,9 +222,9 @@ class ReplyCodeModel(ReplyBase):
# Main client class
class CMakeClient:
def __init__(self, env: Environment):
def __init__(self, env: 'Environment') -> None:
self.env = env
self.proc = None
self.proc = None # type: T.Optional[Popen]
self.type_map = {
'error': lambda data: Error(data['cookie'], data['errorMessage']),
'hello': lambda data: MessageHello(data['supportedProtocolVersions']),
@ -228,7 +232,7 @@ class CMakeClient:
'progress': lambda data: Progress(data['cookie']),
'reply': self.resolve_type_reply,
'signal': lambda data: SignalBase(data['cookie'], data['name'])
}
} # type: T.Dict[str, T.Callable[[T.Dict[str, T.Any]], MessageBase]]
self.reply_map = {
'handshake': lambda data: ReplyHandShake(data['cookie']),
@ -236,10 +240,10 @@ class CMakeClient:
'compute': lambda data: ReplyCompute(data['cookie']),
'cmakeInputs': self.resolve_reply_cmakeInputs,
'codemodel': lambda data: ReplyCodeModel(data),
}
} # type: T.Dict[str, T.Callable[[T.Dict[str, T.Any]], ReplyBase]]
def readMessageRaw(self) -> dict:
assert(self.proc is not None)
def readMessageRaw(self) -> T.Dict[str, T.Any]:
assert self.proc is not None
rawData = []
begin = False
while self.proc.poll() is None:
@ -257,7 +261,11 @@ class CMakeClient:
begin = True # Begin of the message
if rawData:
return json.loads('\n'.join(rawData))
res = json.loads('\n'.join(rawData))
assert isinstance(res, dict)
for i in res.keys():
assert isinstance(i, str)
return res
raise CMakeException('Failed to read data from the CMake server')
def readMessage(self) -> MessageBase:
@ -287,7 +295,7 @@ class CMakeClient:
reply.log()
def query_checked(self, request: RequestBase, message: str) -> ReplyBase:
def query_checked(self, request: RequestBase, message: str) -> MessageBase:
reply = self.query(request)
h = mlog.green('SUCCEEDED') if reply.type == 'reply' else mlog.red('FAILED')
mlog.log(message + ':', h)
@ -305,7 +313,7 @@ class CMakeClient:
request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor)
self.query_checked(request, 'CMake server handshake')
def resolve_type_reply(self, data: dict) -> ReplyBase:
def resolve_type_reply(self, data: T.Dict[str, T.Any]) -> ReplyBase:
reply_type = data['inReplyTo']
func = self.reply_map.get(reply_type, None)
if not func:
@ -315,7 +323,7 @@ class CMakeClient:
raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type))
return func(data)
def resolve_reply_cmakeInputs(self, data: dict) -> ReplyCMakeInputs:
def resolve_reply_cmakeInputs(self, data: T.Dict[str, T.Any]) -> ReplyCMakeInputs:
files = []
for i in data['buildFiles']:
for j in i['sources']:
@ -323,7 +331,7 @@ class CMakeClient:
return ReplyCMakeInputs(data['cookie'], data['cmakeRootDirectory'], data['sourceDirectory'], files)
@contextmanager
def connect(self):
def connect(self) -> T.Generator[None, None, None]:
self.startup()
try:
yield

@ -23,12 +23,12 @@ class CMakeException(MesonException):
pass
class CMakeBuildFile:
def __init__(self, file: str, is_cmake: bool, is_temp: bool):
def __init__(self, file: str, is_cmake: bool, is_temp: bool) -> None:
self.file = file
self.is_cmake = is_cmake
self.is_temp = is_temp
def __repr__(self):
def __repr__(self) -> str:
return '<{}: {}; cmake={}; temp={}>'.format(self.__class__.__name__, self.file, self.is_cmake, self.is_temp)
def _flags_to_list(raw: str) -> T.List[str]:
@ -80,29 +80,37 @@ def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]:
return res
class CMakeInclude:
def __init__(self, path: str, isSystem: bool = False):
self.path = path
self.isSystem = isSystem
def __repr__(self) -> str:
return '<CMakeInclude: {} -- isSystem = {}>'.format(self.path, self.isSystem)
class CMakeFileGroup:
def __init__(self, data: dict):
self.defines = data.get('defines', '')
self.flags = _flags_to_list(data.get('compileFlags', ''))
self.includes = data.get('includePath', [])
self.is_generated = data.get('isGenerated', False)
self.language = data.get('language', 'C')
self.sources = data.get('sources', [])
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.defines = data.get('defines', '') # type: str
self.flags = _flags_to_list(data.get('compileFlags', '')) # type: T.List[str]
self.is_generated = data.get('isGenerated', False) # type: bool
self.language = data.get('language', 'C') # type: str
self.sources = data.get('sources', []) # type: T.List[str]
# Fix the include directories
tmp = []
for i in self.includes:
self.includes = [] # type: T.List[CMakeInclude]
for i in data.get('includePath', []):
if isinstance(i, dict) and 'path' in i:
i['isSystem'] = i.get('isSystem', False)
tmp += [i]
isSystem = i.get('isSystem', False)
assert isinstance(isSystem, bool)
assert isinstance(i['path'], str)
self.includes += [CMakeInclude(i['path'], isSystem)]
elif isinstance(i, str):
tmp += [{'path': i, 'isSystem': False}]
self.includes = tmp
self.includes += [CMakeInclude(i)]
def log(self) -> None:
mlog.log('flags =', mlog.bold(', '.join(self.flags)))
mlog.log('defines =', mlog.bold(', '.join(self.defines)))
mlog.log('includes =', mlog.bold(', '.join(self.includes)))
mlog.log('includes =', mlog.bold(', '.join([str(x) for x in self.includes])))
mlog.log('is_generated =', mlog.bold('true' if self.is_generated else 'false'))
mlog.log('language =', mlog.bold(self.language))
mlog.log('sources:')
@ -111,22 +119,22 @@ class CMakeFileGroup:
mlog.log(i)
class CMakeTarget:
def __init__(self, data: dict):
self.artifacts = data.get('artifacts', [])
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.full_name = data.get('fullName', '')
self.install = data.get('hasInstallRule', False)
self.install_paths = list(set(data.get('installPaths', [])))
self.link_lang = data.get('linkerLanguage', '')
self.link_libraries = _flags_to_list(data.get('linkLibraries', ''))
self.link_flags = _flags_to_list(data.get('linkFlags', ''))
self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', ''))
# self.link_path = data.get('linkPath', '')
self.type = data.get('type', 'EXECUTABLE')
# self.is_generator_provided = data.get('isGeneratorProvided', False)
self.files = []
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.artifacts = data.get('artifacts', []) # type: T.List[str]
self.src_dir = data.get('sourceDirectory', '') # type: str
self.build_dir = data.get('buildDirectory', '') # type: str
self.name = data.get('name', '') # type: str
self.full_name = data.get('fullName', '') # type: str
self.install = data.get('hasInstallRule', False) # type: bool
self.install_paths = list(set(data.get('installPaths', []))) # type: T.List[str]
self.link_lang = data.get('linkerLanguage', '') # type: str
self.link_libraries = _flags_to_list(data.get('linkLibraries', '')) # type: T.List[str]
self.link_flags = _flags_to_list(data.get('linkFlags', '')) # type: T.List[str]
self.link_lang_flags = _flags_to_list(data.get('linkLanguageFlags', '')) # type: T.List[str]
# self.link_path = data.get('linkPath', '') # type: str
self.type = data.get('type', 'EXECUTABLE') # type: str
# self.is_generator_provided = data.get('isGeneratorProvided', False) # type: bool
self.files = [] # type: T.List[CMakeFileGroup]
for i in data.get('fileGroups', []):
self.files += [CMakeFileGroup(i)]
@ -152,11 +160,11 @@ class CMakeTarget:
i.log()
class CMakeProject:
def __init__(self, data: dict):
self.src_dir = data.get('sourceDirectory', '')
self.build_dir = data.get('buildDirectory', '')
self.name = data.get('name', '')
self.targets = []
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.src_dir = data.get('sourceDirectory', '') # type: str
self.build_dir = data.get('buildDirectory', '') # type: str
self.name = data.get('name', '') # type: str
self.targets = [] # type: T.List[CMakeTarget]
for i in data.get('targets', []):
self.targets += [CMakeTarget(i)]
@ -171,9 +179,9 @@ class CMakeProject:
i.log()
class CMakeConfiguration:
def __init__(self, data: dict):
self.name = data.get('name', '')
self.projects = []
def __init__(self, data: T.Dict[str, T.Any]) -> None:
self.name = data.get('name', '') # type: str
self.projects = [] # type: T.List[CMakeProject]
for i in data.get('projects', []):
self.projects += [CMakeProject(i)]

@ -38,7 +38,8 @@ if T.TYPE_CHECKING:
from ..dependencies.base import ExternalProgram
from ..compilers import Compiler
TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]]
TYPE_result = T.Tuple[int, T.Optional[str], T.Optional[str]]
TYPE_cache_key = T.Tuple[str, T.Tuple[str, ...], str, T.FrozenSet[T.Tuple[str, str]]]
_MESON_TO_CMAKE_MAPPING = {
'arm': 'ARMCC',
@ -76,8 +77,8 @@ def meson_compiler_to_cmake_id(cobj: 'Compiler') -> str:
class CMakeExecutor:
# The class's copy of the CMake path. Avoids having to search for it
# multiple times in the same Meson invocation.
class_cmakebin = PerMachine(None, None)
class_cmakevers = PerMachine(None, None)
class_cmakebin = PerMachine(None, None) # type: PerMachine[T.Optional[ExternalProgram]]
class_cmakevers = PerMachine(None, None) # type: PerMachine[T.Optional[str]]
class_cmake_cache = {} # type: T.Dict[T.Any, TYPE_result]
def __init__(self, environment: Environment, version: str, for_machine: MachineChoice, silent: bool = False):
@ -89,8 +90,8 @@ class CMakeExecutor:
self.print_cmout = False
self.prefix_paths = [] # type: T.List[str]
self.extra_cmake_args = [] # type: T.List[str]
if self.cmakebin is False:
self.cmakebin = None
if self.cmakebin is None:
return
if not version_compare(self.cmakevers, self.min_version):
@ -102,17 +103,18 @@ class CMakeExecutor:
return
self.prefix_paths = self.environment.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value
env_pref_path = get_env_var(
env_pref_path_raw = get_env_var(
self.for_machine,
self.environment.is_cross_build(),
'CMAKE_PREFIX_PATH')
if env_pref_path is not None:
if env_pref_path_raw is not None:
env_pref_path = [] # type: T.List[str]
if mesonlib.is_windows():
# Cannot split on ':' on Windows because its in the drive letter
env_pref_path = env_pref_path.split(os.pathsep)
env_pref_path = env_pref_path_raw.split(os.pathsep)
else:
# https://github.com/mesonbuild/meson/issues/7294
env_pref_path = re.split(r':|;', env_pref_path)
env_pref_path = re.split(r':|;', env_pref_path_raw)
env_pref_path = [x for x in env_pref_path if x] # Filter out empty strings
if not self.prefix_paths:
self.prefix_paths = []
@ -121,13 +123,14 @@ class CMakeExecutor:
if self.prefix_paths:
self.extra_cmake_args += ['-DCMAKE_PREFIX_PATH={}'.format(';'.join(self.prefix_paths))]
def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple['ExternalProgram', str]:
from ..dependencies.base import find_external_program
def find_cmake_binary(self, environment: Environment, silent: bool = False) -> T.Tuple[T.Optional['ExternalProgram'], T.Optional[str]]:
from ..dependencies.base import find_external_program, NonExistingExternalProgram
# Only search for CMake the first time and store the result in the class
# definition
if CMakeExecutor.class_cmakebin[self.for_machine] is False:
if isinstance(CMakeExecutor.class_cmakebin[self.for_machine], NonExistingExternalProgram):
mlog.debug('CMake binary for %s is cached as not found' % self.for_machine)
return None, None
elif CMakeExecutor.class_cmakebin[self.for_machine] is not None:
mlog.debug('CMake binary for %s is cached.' % self.for_machine)
else:
@ -142,7 +145,7 @@ class CMakeExecutor:
continue
if not silent:
mlog.log('Found CMake:', mlog.bold(potential_cmakebin.get_path()),
'(%s)' % version_if_ok)
'({})'.format(version_if_ok))
CMakeExecutor.class_cmakebin[self.for_machine] = potential_cmakebin
CMakeExecutor.class_cmakevers[self.for_machine] = version_if_ok
break
@ -151,8 +154,9 @@ class CMakeExecutor:
mlog.log('Found CMake:', mlog.red('NO'))
# Set to False instead of None to signify that we've already
# searched for it and not found it
CMakeExecutor.class_cmakebin[self.for_machine] = False
CMakeExecutor.class_cmakebin[self.for_machine] = NonExistingExternalProgram()
CMakeExecutor.class_cmakevers[self.for_machine] = None
return None, None
return CMakeExecutor.class_cmakebin[self.for_machine], CMakeExecutor.class_cmakevers[self.for_machine]
@ -185,19 +189,19 @@ class CMakeExecutor:
if always_capture_stderr is not None:
self.always_capture_stderr = always_capture_stderr
def _cache_key(self, args: T.List[str], build_dir: str, env):
fenv = frozenset(env.items()) if env is not None else None
def _cache_key(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_cache_key:
fenv = frozenset(env.items()) if env is not None else frozenset()
targs = tuple(args)
return (self.cmakebin, targs, build_dir, fenv)
return (self.cmakebin.get_path(), targs, build_dir, fenv)
def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env) -> TYPE_result:
def _call_cmout_stderr(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result:
cmd = self.cmakebin.get_command() + args
proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.PIPE, cwd=build_dir, env=env)
# stdout and stderr MUST be read at the same time to avoid pipe
# blocking issues. The easiest way to do this is with a separate
# thread for one of the pipes.
def print_stdout():
def print_stdout() -> None:
while True:
line = proc.stdout.readline()
if not line:
@ -214,10 +218,10 @@ class CMakeExecutor:
tline_start_reg = re.compile(r'^\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(.*$')
inside_multiline_trace = False
while True:
line = proc.stderr.readline()
if not line:
line_raw = proc.stderr.readline()
if not line_raw:
break
line = line.decode(errors='ignore')
line = line_raw.decode(errors='ignore')
if tline_start_reg.match(line):
raw_trace += line
inside_multiline_trace = not line.endswith(' )\n')
@ -233,7 +237,7 @@ class CMakeExecutor:
return proc.returncode, None, raw_trace
def _call_cmout(self, args: T.List[str], build_dir: str, env) -> TYPE_result:
def _call_cmout(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result:
cmd = self.cmakebin.get_command() + args
proc = S.Popen(cmd, stdout=S.PIPE, stderr=S.STDOUT, cwd=build_dir, env=env)
while True:
@ -245,7 +249,7 @@ class CMakeExecutor:
proc.wait()
return proc.returncode, None, None
def _call_quiet(self, args: T.List[str], build_dir: str, env) -> TYPE_result:
def _call_quiet(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result:
os.makedirs(build_dir, exist_ok=True)
cmd = self.cmakebin.get_command() + args
ret = S.run(cmd, env=env, cwd=build_dir, close_fds=False,
@ -257,7 +261,7 @@ class CMakeExecutor:
mlog.debug("Called `{}` in {} -> {}".format(call, build_dir, rc))
return rc, out, err
def _call_impl(self, args: T.List[str], build_dir: str, env) -> TYPE_result:
def _call_impl(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]]) -> TYPE_result:
if not self.print_cmout:
return self._call_quiet(args, build_dir, env)
else:
@ -266,9 +270,9 @@ class CMakeExecutor:
else:
return self._call_cmout(args, build_dir, env)
def call(self, args: T.List[str], build_dir: str, env=None, disable_cache: bool = False) -> TYPE_result:
def call(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None, disable_cache: bool = False) -> TYPE_result:
if env is None:
env = os.environ
env = os.environ.copy()
args = args + self.extra_cmake_args
if disable_cache:
@ -281,7 +285,7 @@ class CMakeExecutor:
cache[key] = self._call_impl(args, build_dir, env)
return cache[key]
def call_with_fake_build(self, args: T.List[str], build_dir: str, env=None) -> TYPE_result:
def call_with_fake_build(self, args: T.List[str], build_dir: str, env: T.Optional[T.Dict[str, str]] = None) -> TYPE_result:
# First check the cache
cache = CMakeExecutor.class_cmake_cache
key = self._cache_key(args, build_dir, env)
@ -369,7 +373,7 @@ class CMakeExecutor:
set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
set(CMAKE_SIZEOF_VOID_P "{}")
'''.format(c_comp, c_launcher, is_gnu, c_id, c_version,
ctypes.sizeof(ctypes.c_voidp))))
ctypes.sizeof(ctypes.c_void_p))))
if cxx_comp and not cxx_comp_file.is_file():
is_gnu = '1' if cxx_id == 'GNU' else ''
@ -388,7 +392,7 @@ class CMakeExecutor:
set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;mm;CPP)
set(CMAKE_SIZEOF_VOID_P "{}")
'''.format(cxx_comp, cxx_launcher, is_gnu, cxx_id, cxx_version,
ctypes.sizeof(ctypes.c_voidp))))
ctypes.sizeof(ctypes.c_void_p))))
if fortran_comp and not fortran_comp_file.is_file():
fortran_comp_file.write_text(textwrap.dedent('''\
@ -403,7 +407,7 @@ class CMakeExecutor:
set(CMAKE_Fortran_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
set(CMAKE_Fortran_SOURCE_FILE_EXTENSIONS f;F;fpp;FPP;f77;F77;f90;F90;for;For;FOR;f95;F95)
set(CMAKE_SIZEOF_VOID_P "{}")
'''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_voidp))))
'''.format(fortran_comp, fortran_launcher, ctypes.sizeof(ctypes.c_void_p))))
return self.call(args, build_dir, env)

@ -27,8 +27,8 @@ class CMakeFileAPI:
self.api_base_dir = os.path.join(self.build_dir, '.cmake', 'api', 'v1')
self.request_dir = os.path.join(self.api_base_dir, 'query', 'client-meson')
self.reply_dir = os.path.join(self.api_base_dir, 'reply')
self.cmake_sources = []
self.cmake_configurations = []
self.cmake_sources = [] # type: T.List[CMakeBuildFile]
self.cmake_configurations = [] # type: T.List[CMakeConfiguration]
self.kind_resolver_map = {
'codemodel': self._parse_codemodel,
'cmakeFiles': self._parse_cmakeFiles,
@ -87,7 +87,7 @@ class CMakeFileAPI:
self.kind_resolver_map[i['kind']](i)
def _parse_codemodel(self, data: dict) -> None:
def _parse_codemodel(self, data: T.Dict[str, T.Any]) -> None:
assert('configurations' in data)
assert('paths' in data)
@ -100,7 +100,7 @@ class CMakeFileAPI:
# resolved and the resulting data structure is identical
# to the CMake serve output.
def helper_parse_dir(dir_entry: dict) -> T.Tuple[str, str]:
def helper_parse_dir(dir_entry: T.Dict[str, T.Any]) -> T.Tuple[str, str]:
src_dir = dir_entry.get('source', '.')
bld_dir = dir_entry.get('build', '.')
src_dir = src_dir if os.path.isabs(src_dir) else os.path.join(source_dir, src_dir)
@ -110,7 +110,7 @@ class CMakeFileAPI:
return src_dir, bld_dir
def parse_sources(comp_group: dict, tgt: dict) -> T.Tuple[T.List[str], T.List[str], T.List[int]]:
def parse_sources(comp_group: T.Dict[str, T.Any], tgt: T.Dict[str, T.Any]) -> T.Tuple[T.List[str], T.List[str], T.List[int]]:
gen = []
src = []
idx = []
@ -127,7 +127,7 @@ class CMakeFileAPI:
return src, gen, idx
def parse_target(tgt: dict) -> dict:
def parse_target(tgt: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]:
src_dir, bld_dir = helper_parse_dir(cnf.get('paths', {}))
# Parse install paths (if present)
@ -230,7 +230,7 @@ class CMakeFileAPI:
}]
return tgt_data
def parse_project(pro: dict) -> dict:
def parse_project(pro: T.Dict[str, T.Any]) -> T.Dict[str, T.Any]:
# Only look at the first directory specified in directoryIndexes
# TODO Figure out what the other indexes are there for
p_src_dir = source_dir
@ -268,7 +268,7 @@ class CMakeFileAPI:
self.cmake_configurations += [CMakeConfiguration(cnf_data)]
def _parse_cmakeFiles(self, data: dict) -> None:
def _parse_cmakeFiles(self, data: T.Dict[str, T.Any]) -> None:
assert('inputs' in data)
assert('paths' in data)
@ -309,10 +309,14 @@ class CMakeFileAPI:
return data
def _reply_file_content(self, filename: str) -> dict:
def _reply_file_content(self, filename: str) -> T.Dict[str, T.Any]:
real_path = os.path.join(self.reply_dir, filename)
if not os.path.exists(real_path):
raise CMakeException('File "{}" does not exist'.format(real_path))
with open(real_path, 'r') as fp:
return json.load(fp)
data = json.load(fp)
assert isinstance(data, dict)
for i in data.keys():
assert isinstance(i, str)
return data

@ -13,6 +13,7 @@
# limitations under the License.
from .. import mesonlib
import typing as T
def parse_generator_expressions(raw: str) -> str:
'''Parse CMake generator expressions
@ -73,7 +74,7 @@ def parse_generator_expressions(raw: str) -> str:
'ANGLE-R': lambda x: '>',
'COMMA': lambda x: ',',
'SEMICOLON': lambda x: ';',
}
} # type: T.Dict[str, T.Callable[[str], str]]
# Recursively evaluate generator expressions
def eval_generator_expressions() -> str:

@ -15,13 +15,12 @@
# This class contains the basic functionality needed to run any interpreter
# or an interpreter-based tool.
from .common import CMakeException, CMakeTarget, TargetOptions
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel
from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel
from .fileapi import CMakeFileAPI
from .executor import CMakeExecutor
from .traceparser import CMakeTraceParser, CMakeGeneratorTarget
from .. import mlog, mesonlib
from ..environment import Environment
from ..mesonlib import MachineChoice, OrderedSet, version_compare
from ..mesondata import mesondata
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:
from ..build import Build
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
# 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]+)+$')
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
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)]
return self._return_first_valid_key(keys)
def generated(self, name: str) -> T.Optional[T.Union['ConverterTarget', 'ConverterCustomTarget']]:
return self._return_first_valid_key([self._rel_generated_file_key(name), self._base_generated_file_key(name)])
def generated(self, name: str) -> T.Optional['ConverterCustomTarget']:
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
def _rel_path(self, fname: str) -> T.Optional[str]:
@ -213,42 +219,44 @@ class OutputTargetMap:
return '__art_{}__'.format(os.path.basename(fname))
class ConverterTarget:
def __init__(self, target: CMakeTarget, env: Environment):
self.env = env
self.artifacts = target.artifacts
self.src_dir = target.src_dir
self.build_dir = target.build_dir
self.name = target.name
self.cmake_name = target.name
self.full_name = target.full_name
self.type = target.type
self.install = target.install
self.install_dir = ''
def __init__(self, target: CMakeTarget, env: 'Environment') -> None:
self.env = env
self.artifacts = target.artifacts
self.src_dir = target.src_dir
self.build_dir = target.build_dir
self.name = target.name
self.cmake_name = target.name
self.full_name = target.full_name
self.type = target.type
self.install = target.install
self.install_dir = ''
self.link_libraries = target.link_libraries
self.link_flags = target.link_flags + target.link_lang_flags
self.depends_raw = []
self.depends = []
self.link_flags = target.link_flags + target.link_lang_flags
self.depends_raw = [] # type: T.List[str]
self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
if target.install_paths:
self.install_dir = target.install_paths[0]
self.languages = []
self.sources = []
self.generated = []
self.includes = []
self.sys_includes = []
self.link_with = []
self.object_libs = []
self.compile_opts = {}
self.public_compile_opts = []
self.pie = False
self.languages = [] # type: T.List[str]
self.sources = [] # type: T.List[str]
self.generated = [] # type: T.List[str]
self.generated_ctgt = [] # type: T.List[CustomTargetReference]
self.includes = [] # type: T.List[str]
self.sys_includes = [] # type: T.List[str]
self.link_with = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
self.object_libs = [] # type: T.List[ConverterTarget]
self.compile_opts = {} # type: T.Dict[str, T.List[str]]
self.public_compile_opts = [] # type: T.List[str]
self.pie = False
# 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
self.name = _sanitize_cmake_name(self.name)
self.generated_raw = [] # type: T.List[str]
for i in target.files:
# Determine the meson language
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]]
# Handle include directories
self.includes += [x['path'] for x in i.includes if x 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.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.path not in self.sys_includes and x.isSystem]
# Add sources to the right array
if i.is_generated:
self.generated += i.sources
self.generated_raw += i.sources
else:
self.sources += i.sources
@ -306,7 +314,7 @@ class ConverterTarget:
# Sometimes projects pass generated source files as compiler
# flags. Add these as generated sources to ensure that the
# corresponding custom target is run.2
self.generated += [j]
self.generated_raw += [j]
temp += [j]
elif j in blacklist_compiler_flags:
pass
@ -339,7 +347,7 @@ class ConverterTarget:
cfg = ''
otherDeps = []
libraries = []
mlog.debug(tgt)
mlog.debug(str(tgt))
if 'INTERFACE_INCLUDE_DIRECTORIES' in tgt.properties:
self.includes += [x for x in tgt.properties['INTERFACE_INCLUDE_DIRECTORIES'] if x]
@ -424,8 +432,8 @@ class ConverterTarget:
for i in self.languages:
supported += list(lang_suffixes[i])
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.generated = [x for x in self.generated 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_raw = [x for x in self.generated_raw if any([x.endswith(y) for y in supported])]
# Make paths relative
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)
if not os.path.exists(x) and not any([x.endswith(y) for y in obj_suffixes]) and not is_generated:
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()
):
os.makedirs(x)
@ -465,29 +473,27 @@ class ConverterTarget:
return os.path.relpath(x, root_src_dir)
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))
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.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]
# 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
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.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]
self.sources = [x for x in self.sources if x is not None]
# Make sure '.' is always in the include directories
if '.' not in self.includes:
@ -511,20 +517,20 @@ class ConverterTarget:
# Handle explicit CMake add_dependency() calls
for i in self.depends_raw:
tgt = output_target_map.target(i)
if tgt:
self.depends.append(tgt)
dep_tgt = output_target_map.target(i)
if dep_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
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 = [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]
exts = self._all_source_suffixes()
# Temp now stores the source filenames of the object files
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]
for j in temp:
# On some platforms (specifically looking at you Windows with vs20xy backend) CMake does
@ -546,15 +552,17 @@ class ConverterTarget:
break
# 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:
self.includes += tgt.includes
self.sources += tgt.sources
self.generated += tgt.generated
self.sources = list(OrderedSet(self.sources))
self.generated = list(OrderedSet(self.generated))
self.includes = list(OrderedSet(self.includes))
self.includes += tgt.includes
self.sources += tgt.sources
self.generated += tgt.generated
self.generated_ctgt += tgt.generated_ctgt
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
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)
if not lang_opts or 'std' not in lang_opts:
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
to_process = list(self.depends)
processed = []
@ -589,7 +604,7 @@ class ConverterTarget:
new_deps += [i]
self.depends = list(OrderedSet(new_deps))
def cleanup_dependencies(self):
def cleanup_dependencies(self) -> None:
# Clear the dependencies from targets that where moved from
if self.meson_func() in transfer_dependencies_from:
self.depends = []
@ -613,6 +628,7 @@ class ConverterTarget:
mlog.log(' -- sys_includes: ', mlog.bold(str(self.sys_includes)))
mlog.log(' -- sources: ', mlog.bold(str(self.sources)))
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(' -- override_opts: ', mlog.bold(str(self.override_options)))
mlog.log(' -- depends: ', mlog.bold(str(self.depends)))
@ -621,7 +637,7 @@ class ConverterTarget:
mlog.log(' -', key, '=', mlog.bold(str(val)))
class CustomTargetReference:
def __init__(self, ctgt: 'ConverterCustomTarget', index: int):
def __init__(self, ctgt: 'ConverterCustomTarget', index: int) -> None:
self.ctgt = ctgt # type: ConverterCustomTarget
self.index = index # type: int
@ -641,24 +657,25 @@ class ConverterCustomTarget:
tgt_counter = 0 # type: int
out_counter = 0 # type: int
def __init__(self, target: CMakeGeneratorTarget):
assert(target.current_bin_dir is not None)
assert(target.current_src_dir is not None)
def __init__(self, target: CMakeGeneratorTarget) -> None:
assert target.current_bin_dir is not None
assert target.current_src_dir is not None
self.name = target.name
if not self.name:
self.name = 'custom_tgt_{}'.format(ConverterCustomTarget.tgt_counter)
ConverterCustomTarget.tgt_counter += 1
self.cmake_name = str(self.name)
self.cmake_name = str(self.name)
self.original_outputs = list(target.outputs)
self.outputs = [os.path.basename(x) for x in self.original_outputs]
self.conflict_map = {}
self.command = target.command
self.working_dir = target.working_dir
self.depends_raw = target.depends
self.inputs = []
self.depends = []
self.current_bin_dir = Path(target.current_bin_dir)
self.current_src_dir = Path(target.current_src_dir)
self.outputs = [os.path.basename(x) for x in self.original_outputs]
self.conflict_map = {} # type: T.Dict[str, str]
self.command = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]]
self.working_dir = target.working_dir
self.depends_raw = target.depends
self.inputs = [] # type: T.List[T.Union[str, CustomTargetReference]]
self.depends = [] # type: T.List[T.Union[ConverterTarget, ConverterCustomTarget]]
self.current_bin_dir = Path(target.current_bin_dir)
self.current_src_dir = Path(target.current_src_dir)
self._raw_target = target
# Convert the target name to a valid meson target name
self.name = _sanitize_cmake_name(self.name)
@ -700,12 +717,12 @@ class ConverterCustomTarget:
self.outputs = temp_outputs
# Check if the command is a build target
commands = []
for i in self.command:
assert(isinstance(i, list))
cmd = []
commands = [] # type: T.List[T.List[T.Union[str, ConverterTarget]]]
for curr_cmd in self._raw_target.command:
assert(isinstance(curr_cmd, list))
cmd = [] # type: T.List[T.Union[str, ConverterTarget]]
for j in i:
for j in curr_cmd:
if not j:
continue
target = output_target_map.executable(j)
@ -759,9 +776,11 @@ class ConverterCustomTarget:
elif tgt:
self.depends += [tgt]
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
to_process = list(self.depends)
processed = []
@ -799,7 +818,7 @@ class CMakeAPI(Enum):
FILE = 2
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'))
self.build = build
self.subdir = subdir
@ -815,21 +834,21 @@ class CMakeInterpreter:
self.fileapi = CMakeFileAPI(self.build_dir)
# Raw CMake results
self.bs_files = []
self.codemodel_configs = None
self.raw_trace = None
self.bs_files = [] # type: T.List[str]
self.codemodel_configs = None # type: T.Optional[T.List[CMakeConfiguration]]
self.raw_trace = None # type: T.Optional[str]
# Analysed data
self.project_name = ''
self.languages = []
self.targets = []
self.custom_targets = [] # type: T.List[ConverterCustomTarget]
self.trace = CMakeTraceParser('', '') # Will be replaced in analyse
self.project_name = ''
self.languages = [] # type: T.List[str]
self.targets = [] # type: T.List[ConverterTarget]
self.custom_targets = [] # type: T.List[ConverterCustomTarget]
self.trace = CMakeTraceParser('', '') # Will be replaced in analyse
self.output_target_map = OutputTargetMap(self.build_dir)
# Generated meson data
self.generated_targets = {}
self.internal_name_map = {}
self.generated_targets = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]]
self.internal_name_map = {} # type: T.Dict[str, str]
def configure(self, extra_cmake_options: T.List[str]) -> None:
for_machine = MachineChoice.HOST # TODO make parameter
@ -933,9 +952,11 @@ class CMakeInterpreter:
# Get CMake 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
cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model')
assert isinstance(cm_reply, ReplyCodeModel)
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]
@ -958,63 +979,64 @@ class CMakeInterpreter:
# Find all targets
added_target_names = [] # type: T.List[str]
for i in self.codemodel_configs:
for j in i.projects:
for i_0 in self.codemodel_configs:
for j_0 in i_0.projects:
if not self.project_name:
self.project_name = j.name
for k in j.targets:
self.project_name = j_0.name
for k_0 in j_0.targets:
# Avoid duplicate targets from different configurations and known
# dummy CMake internal target types
if k.type not in skip_targets and k.name not in added_target_names:
added_target_names += [k.name]
self.targets += [ConverterTarget(k, self.env)]
if k_0.type not in skip_targets and k_0.name not in added_target_names:
added_target_names += [k_0.name]
self.targets += [ConverterTarget(k_0, self.env)]
# Add interface targets from trace, if not already present.
# This step is required because interface targets were removed from
# the CMake file API output.
api_target_name_list = [x.name for x in self.targets]
for i in self.trace.targets.values():
if i.type != 'INTERFACE' or i.name in api_target_name_list or i.imported:
for i_1 in self.trace.targets.values():
if i_1.type != 'INTERFACE' or i_1.name in api_target_name_list or i_1.imported:
continue
dummy = CMakeTarget({
'name': i.name,
'name': i_1.name,
'type': 'INTERFACE_LIBRARY',
'sourceDirectory': self.src_dir,
'buildDirectory': self.build_dir,
})
self.targets += [ConverterTarget(dummy, self.env)]
for i in self.trace.custom_targets:
self.custom_targets += [ConverterCustomTarget(i)]
for i_2 in self.trace.custom_targets:
self.custom_targets += [ConverterCustomTarget(i_2)]
# generate the output_target_map
for i in [*self.targets, *self.custom_targets]:
self.output_target_map.add(i)
for i_3 in [*self.targets, *self.custom_targets]:
assert isinstance(i_3, (ConverterTarget, ConverterCustomTarget))
self.output_target_map.add(i_3)
# First pass: Basic target cleanup
object_libs = []
custom_target_outputs = [] # type: T.List[str]
for i in self.custom_targets:
i.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace)
for i in self.targets:
i.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace)
if i.type == 'OBJECT_LIBRARY':
object_libs += [i]
self.languages += [x for x in i.languages if x not in self.languages]
for ctgt in self.custom_targets:
ctgt.postprocess(self.output_target_map, self.src_dir, self.subdir, custom_target_outputs, self.trace)
for tgt in self.targets:
tgt.postprocess(self.output_target_map, self.src_dir, self.subdir, self.install_prefix, self.trace)
if tgt.type == 'OBJECT_LIBRARY':
object_libs += [tgt]
self.languages += [x for x in tgt.languages if x not in self.languages]
# Second pass: Detect object library dependencies
for i in self.targets:
i.process_object_libs(object_libs, self._object_lib_workaround())
for tgt in self.targets:
tgt.process_object_libs(object_libs, self._object_lib_workaround())
# Third pass: Reassign dependencies to avoid some loops
for i in self.targets:
i.process_inter_target_dependencies()
for i in self.custom_targets:
i.process_inter_target_dependencies()
for tgt in self.targets:
tgt.process_inter_target_dependencies()
for ctgt in self.custom_targets:
ctgt.process_inter_target_dependencies()
# Fourth pass: Remove rassigned dependencies
for i in self.targets:
i.cleanup_dependencies()
for tgt in self.targets:
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.')
@ -1022,7 +1044,7 @@ class CMakeInterpreter:
if not self.project_name:
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)
def string(value: str) -> StringNode:
@ -1034,7 +1056,7 @@ class CMakeInterpreter:
def number(value: int) -> NumberNode:
return NumberNode(token(val=value))
def nodeify(value):
def nodeify(value: TYPE_mixed_list) -> BaseNode:
if isinstance(value, str):
return string(value)
elif isinstance(value, bool):
@ -1043,34 +1065,38 @@ class CMakeInterpreter:
return number(value)
elif isinstance(value, list):
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:
return IndexNode(node, nodeify(index))
def array(elements) -> ArrayNode:
def array(elements: TYPE_mixed_list) -> ArrayNode:
args = ArgumentNode(token())
if not isinstance(elements, list):
elements = [args]
args.arguments += [nodeify(x) for x in elements if x is not None]
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
kwargs = {} if kwargs is None else kwargs
args_n = ArgumentNode(token())
if not isinstance(args, list):
assert isinstance(args, (str, int, bool, BaseNode))
args = [args]
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}
func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_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
kwargs = {} if kwargs is None else kwargs
args_n = ArgumentNode(token())
if not isinstance(args, list):
assert isinstance(args, (str, int, bool, BaseNode))
args = [args]
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}
@ -1086,9 +1112,9 @@ class CMakeInterpreter:
# Add the run script for custom commands
# Add the targets
processing = []
processed = {}
name_map = {}
processing = [] # type: T.List[str]
processed = {} # type: T.Dict[str, T.Dict[str, T.Optional[str]]]
name_map = {} # type: T.Dict[str, str]
def extract_tgt(tgt: T.Union[ConverterTarget, ConverterCustomTarget, CustomTargetReference]) -> IdNode:
tgt_name = None
@ -1105,24 +1131,24 @@ class CMakeInterpreter:
raise CMakeException('Cycle in CMake inputs/dependencies detected')
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)
if len(ref.ctgt.outputs) == 1:
return tgt_var
else:
return indexed(tgt_var, ref.index)
def process_target(tgt: ConverterTarget):
def process_target(tgt: ConverterTarget) -> None:
detect_cycle(tgt)
# First handle inter target dependencies
link_with = []
objec_libs = [] # type: T.List[IdNode]
sources = []
generated = []
generated_filenames = []
custom_targets = []
dependencies = []
link_with = [] # type: T.List[IdNode]
objec_libs = [] # type: T.List[IdNode]
sources = [] # type: T.List[str]
generated = [] # type: T.List[T.Union[IdNode, IndexNode]]
generated_filenames = [] # type: T.List[str]
custom_targets = [] # type: T.List[ConverterCustomTarget]
dependencies = [] # type: T.List[IdNode]
for i in tgt.link_with:
assert(isinstance(i, ConverterTarget))
if i.name not in processed:
@ -1141,16 +1167,17 @@ class CMakeInterpreter:
dependencies += [extract_tgt(i)]
# Generate the source list and handle generated sources
for i in tgt.sources + tgt.generated:
if isinstance(i, CustomTargetReference):
if i.ctgt.name not in processed:
process_custom_target(i.ctgt)
generated += [resolve_ctgt_ref(i)]
generated_filenames += [i.filename()]
if i.ctgt not in custom_targets:
custom_targets += [i.ctgt]
else:
sources += [i]
sources += tgt.sources
sources += tgt.generated
for ctgt_ref in tgt.generated_ctgt:
ctgt = ctgt_ref.ctgt
if ctgt.name not in processed:
process_custom_target(ctgt)
generated += [resolve_ctgt_ref(ctgt_ref)]
generated_filenames += [ctgt_ref.filename()]
if ctgt not in custom_targets:
custom_targets += [ctgt]
# Add all header files from all used custom targets. This
# ensures that all custom targets are built before any
@ -1158,12 +1185,12 @@ class CMakeInterpreter:
# header files are present. This step is necessary because
# CMake always ensures that a custom target is executed
# before another target if at least one output is used.
for i in custom_targets:
for j in i.outputs:
for ctgt in custom_targets:
for j in ctgt.outputs:
if not is_header(j) or j in generated_filenames:
continue
generated += [resolve_ctgt_ref(i.get_ref(j))]
generated += [resolve_ctgt_ref(ctgt.get_ref(j))]
generated_filenames += [j]
# Determine the meson function to use for the build target
@ -1190,7 +1217,7 @@ class CMakeInterpreter:
'install': install_tgt,
'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options),
'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
if install_tgt and tgt.install_dir:
@ -1212,7 +1239,7 @@ class CMakeInterpreter:
'link_with': id_node(tgt_var),
'compile_args': tgt.public_compile_opts,
'include_directories': id_node(inc_var),
}
} # type: TYPE_mixed_kwargs
if dependencies:
generated += dependencies
@ -1230,7 +1257,7 @@ class CMakeInterpreter:
tgt_var = None
else:
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]
if tgt_func in ['static_library', 'shared_library']:
dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs))
@ -1290,19 +1317,19 @@ class CMakeInterpreter:
'output': tgt.outputs,
'command': command,
'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))]
processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'}
name_map[tgt.cmake_name] = tgt.name
# Now generate the target function calls
for i in self.custom_targets:
if i.name not in processed:
process_custom_target(i)
for i in self.targets:
if i.name not in processed:
process_target(i)
for ctgt in self.custom_targets:
if ctgt.name not in processed:
process_custom_target(ctgt)
for tgt in self.targets:
if tgt.name not in processed:
process_target(tgt)
self.generated_targets = processed
self.internal_name_map = name_map

@ -28,30 +28,37 @@ import json
import textwrap
class CMakeTraceLine:
def __init__(self, file, line, func, args):
def __init__(self, file: str, line: int, func: str, args: T.List[str]) -> None:
self.file = file
self.line = line
self.func = func.lower()
self.args = args
def __repr__(self):
def __repr__(self) -> str:
s = 'CMake TRACE: {0}:{1} {2}({3})'
return s.format(self.file, self.line, self.func, self.args)
class CMakeTarget:
def __init__(self, name, target_type, properties=None, imported: bool = False, tline: T.Optional[CMakeTraceLine] = None):
def __init__(
self,
name: str,
target_type: str,
properties: T.Optional[T.Dict[str, T.List[str]]] = None,
imported: bool = False,
tline: T.Optional[CMakeTraceLine] = None
):
if properties is None:
properties = {}
self.name = name
self.type = target_type
self.properties = properties
self.imported = imported
self.tline = tline
self.depends = []
self.current_bin_dir = None
self.current_src_dir = None
def __repr__(self):
self.name = name
self.type = target_type
self.properties = properties
self.imported = imported
self.tline = tline
self.depends = [] # type: T.List[str]
self.current_bin_dir = None # type: T.Optional[str]
self.current_src_dir = None # type: T.Optional[str]
def __repr__(self) -> str:
s = 'CMake TARGET:\n -- name: {}\n -- type: {}\n -- imported: {}\n -- properties: {{\n{} }}\n -- tline: {}'
propSTR = ''
for i in self.properties:
@ -67,14 +74,14 @@ class CMakeTarget:
assert all([';' not in x for x in self.properties[key]])
class CMakeGeneratorTarget(CMakeTarget):
def __init__(self, name):
def __init__(self, name: str) -> None:
super().__init__(name, 'CUSTOM', {})
self.outputs = [] # type: T.List[str]
self.command = [] # type: T.List[T.List[str]]
self.working_dir = None # type: T.Optional[str]
class CMakeTraceParser:
def __init__(self, cmake_version: str, build_dir: str, permissive: bool = True):
def __init__(self, cmake_version: str, build_dir: str, permissive: bool = True) -> None:
self.vars = {} # type: T.Dict[str, T.List[str]]
self.targets = {} # type: T.Dict[str, CMakeTarget]
@ -117,7 +124,7 @@ class CMakeTraceParser:
# meaning here in the trace parser.
'meson_ps_execute_delayed_calls': self._meson_ps_execute_delayed_calls,
'meson_ps_reload_vars': self._meson_ps_reload_vars,
}
} # type: T.Dict[str, T.Callable[[CMakeTraceLine], None]]
def trace_args(self) -> T.List[str]:
arg_map = {
@ -256,7 +263,7 @@ class CMakeTraceParser:
else:
self.vars[identifier] = value.split(';')
def _cmake_unset(self, tline: CMakeTraceLine):
def _cmake_unset(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/unset.html
if len(tline.args) < 1:
return self._gen_exception('unset', 'requires at least one argument', tline)
@ -264,7 +271,7 @@ class CMakeTraceParser:
if tline.args[0] in self.vars:
del self.vars[tline.args[0]]
def _cmake_add_executable(self, tline: CMakeTraceLine):
def _cmake_add_executable(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/add_executable.html
args = list(tline.args) # Make a working copy
@ -280,7 +287,7 @@ class CMakeTraceParser:
self.targets[args[0]] = CMakeTarget(args[0], 'EXECUTABLE', {}, tline=tline, imported=is_imported)
def _cmake_add_library(self, tline: CMakeTraceLine):
def _cmake_add_library(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/add_library.html
args = list(tline.args) # Make a working copy
@ -314,7 +321,7 @@ class CMakeTraceParser:
else:
self.targets[args[0]] = CMakeTarget(args[0], 'NORMAL', {}, tline=tline)
def _cmake_add_custom_command(self, tline: CMakeTraceLine, name=None):
def _cmake_add_custom_command(self, tline: CMakeTraceLine, name: T.Optional[str] = None) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_command.html
args = self._flatten_args(list(tline.args)) # Commands can be passed as ';' seperated lists
@ -379,7 +386,7 @@ class CMakeTraceParser:
if name:
self.targets[name] = target
def _cmake_add_custom_target(self, tline: CMakeTraceLine):
def _cmake_add_custom_target(self, tline: CMakeTraceLine) -> None:
# DOC: https://cmake.org/cmake/help/latest/command/add_custom_target.html
# We only the first parameter (the target name) is interesting
if len(tline.args) < 1:
@ -494,7 +501,7 @@ class CMakeTraceParser:
arglist = [] # type: T.List[T.Tuple[str, T.List[str]]]
if self.trace_format == 'human':
name = args.pop(0)
values = []
values = [] # type: T.List[str]
prop_regex = re.compile(r'^[A-Z_]+$')
for a in args:
if prop_regex.match(a):
@ -550,7 +557,7 @@ class CMakeTraceParser:
# DOC: https://cmake.org/cmake/help/latest/command/target_link_libraries.html
self._parse_common_target_options('target_link_options', 'LINK_LIBRARIES', 'INTERFACE_LINK_LIBRARIES', tline)
def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: T.Optional[T.List[str]] = None, paths: bool = False):
def _parse_common_target_options(self, func: str, private_prop: str, interface_prop: str, tline: CMakeTraceLine, ignore: T.Optional[T.List[str]] = None, paths: bool = False) -> None:
if ignore is None:
ignore = ['BEFORE']
@ -588,11 +595,11 @@ class CMakeTraceParser:
interface = [x for x in interface if x]
private = [x for x in private if x]
for i in [(private_prop, private), (interface_prop, interface)]:
if not i[0] in self.targets[target].properties:
self.targets[target].properties[i[0]] = []
for j in [(private_prop, private), (interface_prop, interface)]:
if not j[0] in self.targets[target].properties:
self.targets[target].properties[j[0]] = []
self.targets[target].properties[i[0]] += i[1]
self.targets[target].properties[j[0]] += j[1]
def _meson_ps_execute_delayed_calls(self, tline: CMakeTraceLine) -> None:
for l in self.stored_commands:
@ -606,7 +613,7 @@ class CMakeTraceParser:
def _meson_ps_reload_vars(self, tline: CMakeTraceLine) -> None:
self.delayed_commands = self.get_cmake_var('MESON_PS_DELAYED_CALLS')
def _lex_trace_human(self, trace):
def _lex_trace_human(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]:
# The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n'
reg_tline = re.compile(r'\s*(.*\.(cmake|txt))\(([0-9]+)\):\s*(\w+)\(([\s\S]*?) ?\)\s*\n', re.MULTILINE)
reg_other = re.compile(r'[^\n]*\n')
@ -629,17 +636,23 @@ class CMakeTraceParser:
func = mo_file_line.group(4)
args = mo_file_line.group(5)
args = parse_generator_expressions(args)
args = args.split(' ')
args = list(map(lambda x: x.strip(), args))
argl = args.split(' ')
argl = list(map(lambda x: x.strip(), argl))
yield CMakeTraceLine(file, line, func, args)
yield CMakeTraceLine(file, int(line), func, argl)
def _lex_trace_json(self, trace: str):
def _lex_trace_json(self, trace: str) -> T.Generator[CMakeTraceLine, None, None]:
lines = trace.splitlines(keepends=False)
lines.pop(0) # The first line is the version
for i in lines:
data = json.loads(i)
assert isinstance(data['file'], str)
assert isinstance(data['line'], int)
assert isinstance(data['cmd'], str)
assert isinstance(data['args'], list)
args = data['args']
for j in args:
assert isinstance(j, str)
args = [parse_generator_expressions(x) for x in args]
yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args)

@ -2052,26 +2052,26 @@ class ExternalProgram:
def get_command(self) -> T.List[str]:
return self.command[:]
def get_path(self):
def get_path(self) -> str:
return self.path
def get_name(self):
def get_name(self) -> str:
return self.name
class NonExistingExternalProgram(ExternalProgram): # lgtm [py/missing-call-to-init]
"A program that will never exist"
def __init__(self, name='nonexistingprogram'):
def __init__(self, name: str = 'nonexistingprogram') -> None:
self.name = name
self.command = [None]
self.path = None
def __repr__(self):
def __repr__(self) -> str:
r = '<{} {!r} -> {!r}>'
return r.format(self.__class__.__name__, self.name, self.command)
def found(self):
def found(self) -> bool:
return False

@ -7,64 +7,70 @@ from pathlib import Path
import typing as T
modules = [
# fully typed submodules
'mesonbuild/ast',
'mesonbuild/compilers/mixins',
'mesonbuild/scripts',
'mesonbuild/wrap',
# fully typed submodules
'mesonbuild/ast',
'mesonbuild/cmake',
'mesonbuild/compilers/mixins',
'mesonbuild/scripts',
'mesonbuild/wrap',
# specific files
'mesonbuild/arglist.py',
'mesonbuild/compilers/compilers.py',
'mesonbuild/compilers/c_function_attributes.py',
'mesonbuild/compilers/objc.py',
'mesonbuild/compilers/objcpp.py',
# 'mesonbuild/coredata.py',
'mesonbuild/dependencies/boost.py',
'mesonbuild/dependencies/hdf5.py',
'mesonbuild/dependencies/mpi.py',
'mesonbuild/envconfig.py',
'mesonbuild/interpreterbase.py',
'mesonbuild/linkers.py',
'mesonbuild/mcompile.py',
'mesonbuild/mesonlib.py',
'mesonbuild/minit.py',
'mesonbuild/mintro.py',
'mesonbuild/mlog.py',
'mesonbuild/modules/fs.py',
'mesonbuild/mparser.py',
'mesonbuild/msetup.py',
'mesonbuild/mtest.py',
# specific files
'mesonbuild/arglist.py',
'mesonbuild/compilers/compilers.py',
'mesonbuild/compilers/c_function_attributes.py',
'mesonbuild/compilers/objc.py',
'mesonbuild/compilers/objcpp.py',
# 'mesonbuild/coredata.py',
'mesonbuild/dependencies/boost.py',
'mesonbuild/dependencies/hdf5.py',
'mesonbuild/dependencies/mpi.py',
'mesonbuild/envconfig.py',
'mesonbuild/interpreterbase.py',
'mesonbuild/linkers.py',
'mesonbuild/mcompile.py',
'mesonbuild/mesonlib.py',
'mesonbuild/minit.py',
'mesonbuild/mintro.py',
'mesonbuild/mlog.py',
'mesonbuild/modules/fs.py',
'mesonbuild/mparser.py',
'mesonbuild/msetup.py',
'mesonbuild/mtest.py',
'run_mypy.py',
'tools'
'run_mypy.py',
'tools'
]
def check_mypy() -> None:
try:
import mypy
except ImportError:
print('Failed import mypy')
sys.exit(1)
try:
import mypy
except ImportError:
print('Failed import mypy')
sys.exit(1)
def main() -> int:
check_mypy()
check_mypy()
root = Path(__file__).absolute().parent
args = [] # type: T.List[str]
root = Path(__file__).absolute().parent
args = [] # type: T.List[str]
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('-p', '--pretty', action='store_true', help='pretty print mypy errors')
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('-p', '--pretty', action='store_true', help='pretty print mypy errors')
parser.add_argument('-C', '--clear', action='store_true', help='clear the terminal before running mypy')
opts = parser.parse_args()
if opts.pretty:
args.append('--pretty')
opts = parser.parse_args()
if opts.pretty:
args.append('--pretty')
p = subprocess.run(
[sys.executable, '-m', 'mypy'] + args + modules,
cwd=root,
)
return p.returncode
if opts.clear:
print('\x1bc', end='', flush=True)
print('Running mypy (this can take some time) ...')
p = subprocess.run(
[sys.executable, '-m', 'mypy'] + args + modules,
cwd=root,
)
return p.returncode
if __name__ == '__main__':
sys.exit(main())
sys.exit(main())

Loading…
Cancel
Save