diff options
Diffstat (limited to 'mesonbuild/cmake/client.py')
-rw-r--r-- | mesonbuild/cmake/client.py | 373 |
1 files changed, 0 insertions, 373 deletions
diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py deleted file mode 100644 index bcbb52e..0000000 --- a/mesonbuild/cmake/client.py +++ /dev/null @@ -1,373 +0,0 @@ -# Copyright 2019 The Meson development team - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This class contains the basic functionality needed to run any interpreter -# or an interpreter-based tool. - -from .common import CMakeException, CMakeConfiguration, CMakeBuildFile -from .. import mlog -from contextlib import contextmanager -from subprocess import Popen, PIPE, TimeoutExpired -from pathlib import Path -import typing as T -import json - -if T.TYPE_CHECKING: - from ..environment import Environment - from .executor import CMakeExecutor - -CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==[' -CMAKE_SERVER_END_STR = ']== "CMake Server" ==]' - -CMAKE_MESSAGE_TYPES = { - 'error': ['cookie', 'errorMessage'], - 'hello': ['supportedProtocolVersions'], - 'message': ['cookie', 'message'], - 'progress': ['cookie'], - 'reply': ['cookie', 'inReplyTo'], - 'signal': ['cookie', 'name'], -} # type: T.Dict[str, T.List[str]] - -CMAKE_REPLY_TYPES = { - 'handshake': [], - 'configure': [], - '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) -> None: - self.type = msg_type - self.cookie = cookie - - 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: - mlog.warning('CMake server message of type', mlog.bold(type(self).__name__), 'has no log function') - -class RequestBase(MessageBase): - cookie_counter = 0 - - def __init__(self, msg_type: str) -> None: - super().__init__(msg_type, self.gen_cookie()) - - @staticmethod - def gen_cookie() -> str: - RequestBase.cookie_counter += 1 - return f'meson_{RequestBase.cookie_counter}' - -class ReplyBase(MessageBase): - 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) -> None: - super().__init__('signal', cookie) - self.signal_name = signal_name - - def log(self) -> None: - mlog.log(mlog.bold('CMake signal:'), mlog.yellow(self.signal_name)) - -# Special Message classes - -class Error(MessageBase): - def __init__(self, cookie: str, message: str) -> None: - super().__init__('error', cookie) - self.message = message - - def log(self) -> None: - mlog.error(mlog.bold('CMake server error:'), mlog.red(self.message)) - -class Message(MessageBase): - def __init__(self, cookie: str, message: str) -> None: - super().__init__('message', cookie) - self.message = message - - def log(self) -> None: - #mlog.log(mlog.bold('CMake:'), self.message) - pass - -class Progress(MessageBase): - 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[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 - return False - -# Request classes - -class RequestHandShake(RequestBase): - def __init__(self, src_dir: Path, build_dir: Path, 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 - self.generator = generator - self.vers_major = vers_major - self.vers_minor = vers_minor - - 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 - - # Old CMake versions (3.7) want '/' even on Windows - self.src_dir = self.src_dir.resolve() - self.build_dir = self.build_dir.resolve() - - return { - **super().to_dict(), - 'sourceDirectory': self.src_dir.as_posix(), - 'buildDirectory': self.build_dir.as_posix(), - 'generator': self.generator, - 'protocolVersion': vers - } - -class RequestConfigure(RequestBase): - def __init__(self, args: T.Optional[T.List[str]] = None): - super().__init__('configure') - self.args = args - - 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) -> None: - super().__init__('compute') - -class RequestCMakeInputs(RequestBase): - def __init__(self) -> None: - super().__init__('cmakeInputs') - -class RequestCodeModel(RequestBase): - def __init__(self) -> None: - super().__init__('codemodel') - -# Reply classes - -class ReplyHandShake(ReplyBase): - def __init__(self, cookie: str) -> None: - super().__init__(cookie, 'handshake') - -class ReplyConfigure(ReplyBase): - def __init__(self, cookie: str) -> None: - super().__init__(cookie, 'configure') - -class ReplyCompute(ReplyBase): - def __init__(self, cookie: str) -> None: - super().__init__(cookie, 'compute') - -class ReplyCMakeInputs(ReplyBase): - def __init__(self, cookie: str, cmake_root: Path, src_dir: Path, build_files: T.List[CMakeBuildFile]) -> None: - super().__init__(cookie, 'cmakeInputs') - self.cmake_root = cmake_root - self.src_dir = src_dir - self.build_files = build_files - - def log(self) -> None: - mlog.log('CMake root: ', mlog.bold(self.cmake_root.as_posix())) - mlog.log('Source dir: ', mlog.bold(self.src_dir.as_posix())) - mlog.log('Build files:', mlog.bold(str(len(self.build_files)))) - with mlog.nested(): - for i in self.build_files: - mlog.log(str(i)) - -class ReplyCodeModel(ReplyBase): - def __init__(self, data: T.Dict[str, T.Any]) -> None: - super().__init__(data['cookie'], 'codemodel') - self.configs = [] - for i in data['configurations']: - self.configs += [CMakeConfiguration(i)] - - def log(self) -> None: - mlog.log('CMake code mode:') - for idx, i in enumerate(self.configs): - mlog.log(f'Configuration {idx}:') - with mlog.nested(): - i.log() - -# Main client class - -class CMakeClient: - def __init__(self, env: 'Environment') -> None: - self.env = env - self.proc = None # type: T.Optional[Popen] - self.type_map = { - 'error': lambda data: Error(data['cookie'], data['errorMessage']), - 'hello': lambda data: MessageHello(data['supportedProtocolVersions']), - 'message': lambda data: Message(data['cookie'], data['message']), - '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']), - 'configure': lambda data: ReplyConfigure(data['cookie']), - '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) -> T.Dict[str, T.Any]: - assert self.proc is not None - rawData = [] - begin = False - while self.proc.poll() is None: - line = self.proc.stdout.readline() - if not line: - break - line = line.decode('utf-8') - line = line.strip() - - if begin and line == CMAKE_SERVER_END_STR: - break # End of the message - elif begin: - rawData += [line] - elif line == CMAKE_SERVER_BEGIN_STR: - begin = True # Begin of the message - - if 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: - raw_data = self.readMessageRaw() - if 'type' not in raw_data: - raise CMakeException('The "type" attribute is missing from the message') - msg_type = raw_data['type'] - func = self.type_map.get(msg_type, None) - if not func: - raise CMakeException(f'Recieved unknown message type "{msg_type}"') - for i in CMAKE_MESSAGE_TYPES[msg_type]: - if i not in raw_data: - raise CMakeException(f'Key "{i}" is missing from CMake server message type {msg_type}') - return func(raw_data) - - def writeMessage(self, msg: MessageBase) -> None: - raw_data = '\n{}\n{}\n{}\n'.format(CMAKE_SERVER_BEGIN_STR, json.dumps(msg.to_dict(), indent=2), CMAKE_SERVER_END_STR) - self.proc.stdin.write(raw_data.encode('ascii')) - self.proc.stdin.flush() - - def query(self, request: RequestBase) -> MessageBase: - self.writeMessage(request) - while True: - reply = self.readMessage() - if reply.cookie == request.cookie and reply.type in ['reply', 'error']: - return reply - - reply.log() - - 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) - if reply.type != 'reply': - reply.log() - raise CMakeException('CMake server query failed') - return reply - - def do_handshake(self, src_dir: Path, build_dir: Path, generator: str, vers_major: int, vers_minor: T.Optional[int] = None) -> None: - # CMake prints the hello message on startup - msg = self.readMessage() - if not isinstance(msg, MessageHello): - raise CMakeException('Recieved an unexpected message from the CMake server') - - request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor) - self.query_checked(request, 'CMake server handshake') - - 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: - raise CMakeException(f'Recieved unknown reply type "{reply_type}"') - for i in ['cookie'] + CMAKE_REPLY_TYPES[reply_type]: - if i not in data: - raise CMakeException(f'Key "{i}" is missing from CMake server message type {type}') - return func(data) - - def resolve_reply_cmakeInputs(self, data: T.Dict[str, T.Any]) -> ReplyCMakeInputs: - files = [] - for i in data['buildFiles']: - for j in i['sources']: - files += [CMakeBuildFile(Path(j), i['isCMake'], i['isTemporary'])] - return ReplyCMakeInputs(data['cookie'], Path(data['cmakeRootDirectory']), Path(data['sourceDirectory']), files) - - @contextmanager - def connect(self, cmake_exe: 'CMakeExecutor') -> T.Generator[None, None, None]: - self.startup(cmake_exe) - try: - yield - finally: - self.shutdown() - - def startup(self, cmake_exe: 'CMakeExecutor') -> None: - if self.proc is not None: - raise CMakeException('The CMake server was already started') - assert cmake_exe.found() - - mlog.debug('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_exe.version())) - self.proc = Popen(cmake_exe.get_command() + ['-E', 'server', '--experimental', '--debug'], stdin=PIPE, stdout=PIPE) - - def shutdown(self) -> None: - if self.proc is None: - return - - mlog.debug('Shutting down the CMake server') - - # Close the pipes to exit - self.proc.stdin.close() - self.proc.stdout.close() - - # Wait for CMake to finish - try: - self.proc.wait(timeout=2) - except TimeoutExpired: - # Terminate CMake if there is a timeout - # terminate() may throw a platform specific exception if the process has already - # terminated. This may be the case if there is a race condition (CMake exited after - # the timeout but before the terminate() call). Additionally, this behavior can - # also be triggered on cygwin if CMake crashes. - # See https://github.com/mesonbuild/meson/pull/4969#issuecomment-499413233 - try: - self.proc.terminate() - except Exception: - pass - - self.proc = None |