diff options
Diffstat (limited to 'mesonbuild')
-rw-r--r-- | mesonbuild/cmake/__init__.py | 2 | ||||
-rw-r--r-- | mesonbuild/cmake/client.py | 373 | ||||
-rw-r--r-- | mesonbuild/cmake/interpreter.py | 76 |
3 files changed, 17 insertions, 434 deletions
diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py index 32b8b6c..16c1322 100644 --- a/mesonbuild/cmake/__init__.py +++ b/mesonbuild/cmake/__init__.py @@ -16,7 +16,6 @@ # or an interpreter-based tool. __all__ = [ - 'CMakeClient', 'CMakeExecutor', 'CMakeExecScope', 'CMakeException', @@ -40,7 +39,6 @@ __all__ = [ ] from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args, cmake_is_debug -from .client import CMakeClient from .executor import CMakeExecutor from .fileapi import CMakeFileAPI from .generator import parse_generator_expressions 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 diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 0b6d241..d610312 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -15,20 +15,18 @@ # This class contains the basic functionality needed to run any interpreter # or an interpreter-based tool. -from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration, language_map, backend_generator_map, cmake_get_generator_args, check_cmake_args -from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, ReplyCMakeInputs, ReplyCodeModel +from .common import CMakeException, CMakeTarget, TargetOptions, CMakeConfiguration, language_map, cmake_get_generator_args, check_cmake_args from .fileapi import CMakeFileAPI from .executor import CMakeExecutor from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .tracetargets import resolve_cmake_trace_targets from .. import mlog, mesonlib -from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey +from ..mesonlib import MachineChoice, OrderedSet, path_is_in_root, relative_to_if_possible, OptionKey from ..mesondata import DataFile from ..compilers.compilers import assembler_suffixes, lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from ..programs import ExternalProgram from ..coredata import FORBIDDEN_TARGET_NAMES -from enum import Enum from functools import lru_cache from pathlib import Path import typing as T @@ -763,10 +761,6 @@ class ConverterCustomTarget: mlog.log(' -- inputs: ', mlog.bold(str(self.inputs))) mlog.log(' -- depends: ', mlog.bold(str(self.depends))) -class CMakeAPI(Enum): - SERVER = 1 - FILE = 2 - class CMakeInterpreter: def __init__(self, build: 'Build', subdir: Path, src_dir: Path, install_prefix: Path, env: 'Environment', backend: 'Backend'): self.build = build @@ -779,8 +773,6 @@ class CMakeInterpreter: self.for_machine = MachineChoice.HOST # TODO make parameter self.backend_name = backend.name self.linkers = set() # type: T.Set[str] - self.cmake_api = CMakeAPI.SERVER - self.client = CMakeClient(self.env) self.fileapi = CMakeFileAPI(self.build_dir) # Raw CMake results @@ -811,7 +803,7 @@ class CMakeInterpreter: def configure(self, extra_cmake_options: T.List[str]) -> CMakeExecutor: # Find CMake # TODO: Using MachineChoice.BUILD should always be correct here, but also evaluate the use of self.for_machine - cmake_exe = CMakeExecutor(self.env, '>=3.7', MachineChoice.BUILD) + cmake_exe = CMakeExecutor(self.env, '>=3.14', MachineChoice.BUILD) if not cmake_exe.found(): raise CMakeException('Unable to find CMake') self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, permissive=True) @@ -830,11 +822,7 @@ class CMakeInterpreter: trace_args = self.trace.trace_args() cmcmp_args = [f'-DCMAKE_POLICY_WARNING_{x}=OFF' for x in disable_policy_warnings] - if version_compare(cmake_exe.version(), '>=3.14'): - self.cmake_api = CMakeAPI.FILE - self.fileapi.setup_request() - else: - mlog.deprecation(f'Support for CMake <3.14 (Meson found {cmake_exe.version()}) is deprecated since Meson 0.61.0') + self.fileapi.setup_request() if version_compare(cmake_exe.version(), '<3.17.0'): mlog.warning(textwrap.dedent(f'''\ @@ -876,51 +864,21 @@ class CMakeInterpreter: return cmake_exe def initialise(self, extra_cmake_options: T.List[str]) -> None: - # Run configure the old way because doing it - # with the server doesn't work for some reason - # Additionally, the File API requires a configure anyway - cmake_exe = self.configure(extra_cmake_options) - - # Continue with the file API If supported - if self.cmake_api is CMakeAPI.FILE: - # Parse the result - self.fileapi.load_reply() - - # Load the buildsystem file list - cmake_files = self.fileapi.get_cmake_sources() - self.bs_files = [x.file for x in cmake_files if not x.is_cmake and not x.is_temp] - self.bs_files = [relative_to_if_possible(x, Path(self.env.get_source_dir())) for x in self.bs_files] - self.bs_files = [x for x in self.bs_files if not path_is_in_root(x, Path(self.env.get_build_dir()), resolve=True)] - self.bs_files = list(OrderedSet(self.bs_files)) - - # Load the codemodel configurations - self.codemodel_configs = self.fileapi.get_cmake_configurations() - return - - with self.client.connect(cmake_exe): - generator = backend_generator_map[self.backend_name] - self.client.do_handshake(self.src_dir, self.build_dir, generator, 1) - - # Do a second configure to initialise the server - self.client.query_checked(RequestConfigure(), 'CMake server configure') - - # Generate the build system files - self.client.query_checked(RequestCompute(), 'Generating build system files') - - # 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] - self.bs_files = [relative_to_if_possible(src_dir / x, Path(self.env.get_source_dir()), resolve=True) for x in self.bs_files] + # Configure the CMake project to generate the file API data + self.configure(extra_cmake_options) + + # Parse the result + self.fileapi.load_reply() + + # Load the buildsystem file list + cmake_files = self.fileapi.get_cmake_sources() + self.bs_files = [x.file for x in cmake_files if not x.is_cmake and not x.is_temp] + self.bs_files = [relative_to_if_possible(x, Path(self.env.get_source_dir())) for x in self.bs_files] self.bs_files = [x for x in self.bs_files if not path_is_in_root(x, Path(self.env.get_build_dir()), resolve=True)] self.bs_files = list(OrderedSet(self.bs_files)) - self.codemodel_configs = cm_reply.configs + + # Load the codemodel configurations + self.codemodel_configs = self.fileapi.get_cmake_configurations() def analyse(self) -> None: if self.codemodel_configs is None: |