From 19b3f65e70e9fda5562775b813d2d74ed8f0c255 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 11:15:57 +0200 Subject: [PATCH 1/9] typing: fix indentation in run_mypy.py --- run_mypy.py | 96 ++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/run_mypy.py b/run_mypy.py index db38112b6..de8418e35 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -7,64 +7,64 @@ 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/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') - 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 + p = subprocess.run( + [sys.executable, '-m', 'mypy'] + args + modules, + cwd=root, + ) + return p.returncode if __name__ == '__main__': - sys.exit(main()) + sys.exit(main()) From 9f695435e71fb1bc1802288410a2fa1e61139a93 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 11:21:36 +0200 Subject: [PATCH 2/9] typing: add convinient option to clear the terminal to run_mypy.py --- run_mypy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/run_mypy.py b/run_mypy.py index de8418e35..668db826e 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -55,11 +55,16 @@ def main() -> int: 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') + 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, From 6dfdb407f0f65eb35eab1fb9f39d94e6e6ca3cf8 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 11:41:10 +0200 Subject: [PATCH 3/9] typing: fully annotate cmake.client --- mesonbuild/cmake/client.py | 78 +++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index b88a67330..2089be400 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.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 From 6a71391de6a5487a6cda7b273e955ae9c1e30ceb Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 18:05:13 +0200 Subject: [PATCH 4/9] typing: fully annotate cmake.common --- mesonbuild/cmake/common.py | 88 ++++++++++++++++++--------------- mesonbuild/cmake/interpreter.py | 4 +- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py index 4510b5d5a..f6bd944a8 100644 --- a/mesonbuild/cmake/common.py +++ b/mesonbuild/cmake/common.py @@ -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 ''.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)] diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 2fdb3289e..30d288ed8 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -264,8 +264,8 @@ 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 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] # Add sources to the right array if i.is_generated: From d9419a4f2a550171f5fce57672c6bdac43e74e85 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 18:31:32 +0200 Subject: [PATCH 5/9] typing: fully annotate cmake.executor --- mesonbuild/cmake/executor.py | 66 +++++++++++++++++---------------- mesonbuild/dependencies/base.py | 10 ++--- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index 2226c0275..d3588f09f 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -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) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index aa513ba33..99030e8e7 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -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 From a1b2f15092b18d625a5fc2a76bd416232c8aeee8 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 19:16:33 +0200 Subject: [PATCH 6/9] typing: fully annotate cmake.fileapi --- mesonbuild/cmake/fileapi.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/mesonbuild/cmake/fileapi.py b/mesonbuild/cmake/fileapi.py index f219f16ca..0405145cd 100644 --- a/mesonbuild/cmake/fileapi.py +++ b/mesonbuild/cmake/fileapi.py @@ -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 From fc80518a07527675e49e037a0e176b80a5f4b302 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 19:19:04 +0200 Subject: [PATCH 7/9] typing: fully annotate cmake.generator --- mesonbuild/cmake/generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py index a30d2de7b..02d56cc5b 100644 --- a/mesonbuild/cmake/generator.py +++ b/mesonbuild/cmake/generator.py @@ -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: From c2d55afcb5581af528470e386eaad7f3856ae741 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 27 Sep 2020 19:37:11 +0200 Subject: [PATCH 8/9] typing: fully annotate cmake.traceparser --- mesonbuild/cmake/traceparser.py | 77 +++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 98b56f5bc..5c4119697 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -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: '(): ( )\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) From 2f3ef6f1afa7ac70f79075f88c9d72ba17c002ba Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Mon, 28 Sep 2020 12:31:23 +0200 Subject: [PATCH 9/9] typing: fully annotate cmake.interpreter --- mesonbuild/cmake/interpreter.py | 369 +++++++++++++++++--------------- run_mypy.py | 1 + 2 files changed, 199 insertions(+), 171 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 30d288ed8..1a7fe7aec 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -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 diff --git a/run_mypy.py b/run_mypy.py index 668db826e..04f563ae9 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -9,6 +9,7 @@ import typing as T modules = [ # fully typed submodules 'mesonbuild/ast', + 'mesonbuild/cmake', 'mesonbuild/compilers/mixins', 'mesonbuild/scripts', 'mesonbuild/wrap',