From a9a3b3ffe6bb13d03799070ef551c56645f51527 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Thu, 21 Feb 2019 14:50:50 +0100 Subject: Added method kwarg to subproject --- mesonbuild/interpreter.py | 46 ++++++++++++++++++++++++++++++++++------------ mesonbuild/wrap/wrap.py | 3 ++- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index a945f70..29e6632 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2027,7 +2027,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'both_libraries': known_library_kwargs, 'library': known_library_kwargs, 'subdir': {'if_found'}, - 'subproject': {'version', 'default_options', 'required'}, + 'subproject': {'version', 'default_options', 'required', 'method'}, 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite', 'protocol'}, 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, @@ -2412,6 +2412,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) + @FeatureNewKwargs('subproject', '0.50.0', ['method']) @permittedKwargs(permitted_kwargs['subproject']) @stringArgs def func_subproject(self, nodes, args, kwargs): @@ -2432,6 +2433,7 @@ external dependencies (including libraries) must go to "dependencies".''') default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) default_options = coredata.create_options_dict(default_options) + method = kwargs.get('method', 'auto') if dirname == '': raise InterpreterException('Subproject dir name must not be empty.') if dirname[0] == '.': @@ -2473,22 +2475,27 @@ external dependencies (including libraries) must go to "dependencies".''') raise e subdir = os.path.join(self.subproject_dir, resolved) + subdir_abs = os.path.join(subproject_dir_abs, resolved) os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True + + # Determine the method to use when 'auto' is given + if method == 'auto': + if os.path.exists(os.path.join(subdir_abs, environment.build_filename)): + method = 'meson' + elif os.path.exists(os.path.join(subdir_abs, 'CMakeLists.txt')): + method = 'cmake' + mlog.log() with mlog.nested(): - mlog.log('Executing subproject', mlog.bold(dirname), '\n') + mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n') try: - with mlog.nested(): - new_build = self.build.copy() - subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, - self.modules, default_options) - subi.subprojects = self.subprojects - - subi.subproject_stack = self.subproject_stack + [dirname] - current_active = self.active_projectname - subi.run() - mlog.log('Subproject', mlog.bold(dirname), 'finished.') + if method == 'meson': + return self.do_subproject_meson(dirname, subdir, default_options, required, kwargs) + elif method == 'cmake': + return self.do_subproject_cmake(dirname, subdir, required, kwargs) + else: + raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) # Invalid code is always an error except InvalidCode: raise @@ -2502,6 +2509,18 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(dirname) raise e + def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs): + with mlog.nested(): + new_build = self.build.copy() + subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, + self.modules, default_options) + subi.subprojects = self.subprojects + + subi.subproject_stack = self.subproject_stack + [dirname] + current_active = self.active_projectname + subi.run() + mlog.log('Subproject', mlog.bold(dirname), 'finished.') + mlog.log() if 'version' in kwargs: @@ -2518,6 +2537,9 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] + def do_subproject_cmake(self, dirname, subdir, required, kwargs): + raise InterpreterException('CMake subprojects are currently a stub') + def get_option_internal(self, optname): for opts in chain( [self.coredata.base_options, compilers.base_options, self.coredata.builtins], diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 3eb68a7..47db985 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -123,9 +123,10 @@ class Resolver: raise WrapException('Directory key must be a name and not a path') self.dirname = os.path.join(self.subdir_root, self.directory) meson_file = os.path.join(self.dirname, 'meson.build') + cmake_file = os.path.join(self.dirname, 'CMakeLists.txt') # The directory is there and has meson.build? Great, use it. - if os.path.exists(meson_file): + if os.path.exists(meson_file) or os.path.exists(cmake_file): return self.directory # Check if the subproject is a git submodule -- cgit v1.1 From e1863719cb7b72630dba885ac5c6dd19c758619f Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Thu, 21 Feb 2019 16:49:41 +0100 Subject: cmake: Made CMake executable finding availiable for other functions --- mesonbuild/dependencies/base.py | 76 ++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index a6fb0b6..9a14232 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1059,10 +1059,43 @@ class CMakeDependency(ExternalDependency): # List of successfully found modules self.found_modules = [] + self.cmakebin, self.cmakevers, for_machine = self.find_cmake_binary(environment, self.want_cross, self.silent) + if self.cmakebin is False: + self.cmakebin = None + msg = 'No CMake binary for machine %s not found. Giving up.' % for_machine + if self.required: + raise DependencyException(msg) + mlog.debug(msg) + return + + if CMakeDependency.class_cmakeinfo[for_machine] is None: + CMakeDependency.class_cmakeinfo[for_machine] = self._get_cmake_info() + self.cmakeinfo = CMakeDependency.class_cmakeinfo[for_machine] + if self.cmakeinfo is None: + raise self._gen_exception('Unable to obtain CMake system information') + + modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))] + modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))] + cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path')) + cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path] + cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args')) + if cm_path: + cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path)) + + pref_path = self.env.coredata.builtins_per_machine[for_machine]['cmake_prefix_path'].value + if pref_path: + cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path))) + + if not self._preliminary_find_check(name, cm_path, pref_path, environment.machines[for_machine]): + return + self._detect_dep(name, modules, cm_args) + + @staticmethod + def find_cmake_binary(environment: Environment, want_cross: bool = False, silent: bool = False) -> Tuple[str, str, MachineChoice]: # When finding dependencies for cross-compiling, we don't care about # the 'native' CMake binary # TODO: Test if this works as expected - if environment.is_cross_build() and not self.want_cross: + if environment.is_cross_build() and not want_cross: for_machine = MachineChoice.BUILD else: for_machine = MachineChoice.HOST @@ -1097,54 +1130,24 @@ class CMakeDependency(ExternalDependency): for potential_cmakebin in search(): mlog.debug('Trying CMake binary {} for machine {} at {}' .format(potential_cmakebin.name, for_machine, potential_cmakebin.command)) - version_if_ok = self.check_cmake(potential_cmakebin) + version_if_ok = CMakeDependency.check_cmake(potential_cmakebin) if not version_if_ok: continue - if not self.silent: + if not silent: mlog.log('Found CMake:', mlog.bold(potential_cmakebin.get_path()), '(%s)' % version_if_ok) CMakeDependency.class_cmakebin[for_machine] = potential_cmakebin CMakeDependency.class_cmakevers[for_machine] = version_if_ok break else: - if not self.silent: + if not silent: 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 CMakeDependency.class_cmakebin[for_machine] = False CMakeDependency.class_cmakevers[for_machine] = None - self.cmakebin = CMakeDependency.class_cmakebin[for_machine] - self.cmakevers = CMakeDependency.class_cmakevers[for_machine] - if self.cmakebin is False: - self.cmakebin = None - msg = 'No CMake binary for machine %s not found. Giving up.' % for_machine - if self.required: - raise DependencyException(msg) - mlog.debug(msg) - return - - if CMakeDependency.class_cmakeinfo[for_machine] is None: - CMakeDependency.class_cmakeinfo[for_machine] = self._get_cmake_info() - self.cmakeinfo = CMakeDependency.class_cmakeinfo[for_machine] - if self.cmakeinfo is None: - raise self._gen_exception('Unable to obtain CMake system information') - - modules = [(x, True) for x in stringlistify(extract_as_list(kwargs, 'modules'))] - modules += [(x, False) for x in stringlistify(extract_as_list(kwargs, 'optional_modules'))] - cm_path = stringlistify(extract_as_list(kwargs, 'cmake_module_path')) - cm_path = [x if os.path.isabs(x) else os.path.join(environment.get_source_dir(), x) for x in cm_path] - cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args')) - if cm_path: - cm_args.append('-DCMAKE_MODULE_PATH=' + ';'.join(cm_path)) - - pref_path = self.env.coredata.builtins_per_machine[for_machine]['cmake_prefix_path'].value - if pref_path: - cm_args.append('-DCMAKE_PREFIX_PATH={}'.format(';'.join(pref_path))) - - if not self._preliminary_find_check(name, cm_path, pref_path, environment.machines[for_machine]): - return - self._detect_dep(name, modules, cm_args) + return CMakeDependency.class_cmakebin[for_machine], CMakeDependency.class_cmakevers[for_machine], for_machine def __repr__(self): s = '<{0} {1}: {2} {3}>' @@ -1841,7 +1844,8 @@ set(CMAKE_SIZEOF_VOID_P "{}") def get_methods(): return [DependencyMethods.CMAKE] - def check_cmake(self, cmakebin): + @staticmethod + def check_cmake(cmakebin): if not cmakebin.found(): mlog.log('Did not find CMake {!r}'.format(cmakebin.name)) return None -- cgit v1.1 From 8d3bd6eea8e0504ddaa979b14ad230f327eb1185 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Thu, 21 Feb 2019 21:30:42 +0100 Subject: cmake: Server handshake --- mesonbuild/cmake/__init__.py | 26 +++ mesonbuild/cmake/client.py | 254 +++++++++++++++++++++ mesonbuild/cmake/common.py | 21 ++ mesonbuild/cmake/interpreter.py | 48 ++++ mesonbuild/interpreter.py | 13 +- setup.py | 1 + test cases/cmake/1 basic/main.cpp | 10 + test cases/cmake/1 basic/meson.build | 6 + .../cmake/1 basic/subprojects/cmMod/CMakeLists.txt | 6 + .../cmake/1 basic/subprojects/cmMod/cmMod.cpp | 11 + .../cmake/1 basic/subprojects/cmMod/cmMod.hpp | 12 + 11 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 mesonbuild/cmake/__init__.py create mode 100644 mesonbuild/cmake/client.py create mode 100644 mesonbuild/cmake/common.py create mode 100644 mesonbuild/cmake/interpreter.py create mode 100644 test cases/cmake/1 basic/main.cpp create mode 100644 test cases/cmake/1 basic/meson.build create mode 100644 test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt create mode 100644 test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp create mode 100644 test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py new file mode 100644 index 0000000..d449324 --- /dev/null +++ b/mesonbuild/cmake/__init__.py @@ -0,0 +1,26 @@ +# 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. + +__all__ = [ + 'CMakeClient', + 'CMakeException', + 'CMakeInterpreter', +] + +from .common import CMakeException +from .client import CMakeClient +from .interpreter import CMakeInterpreter diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py new file mode 100644 index 0000000..219acae --- /dev/null +++ b/mesonbuild/cmake/client.py @@ -0,0 +1,254 @@ +# 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 +from ..environment import Environment +from ..dependencies.base import CMakeDependency, ExternalProgram +from .. import mlog +from contextlib import contextmanager +from subprocess import Popen, PIPE, TimeoutExpired +from typing import List, Optional +import json + +CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==[' +CMAKE_SERVER_END_STR = ']== "CMake Server" ==]' + +CMAKE_MESSAGE_TYPES = { + 'error': ['cookie', 'errorMessage'], + 'hello': ['supportedProtocolVersions'], + 'reply': ['cookie', 'inReplyTo'], +} + +CMAKE_REPLY_TYPES = { + 'handshake': [], +} + +# Base CMake server message classes + +class MessageBase: + def __init__(self, type: str, cookie: str): + self.type = type + self.cookie = cookie + + def to_dict(self) -> dict: + 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, type: str): + super().__init__(type, self.gen_cookie()) + + @staticmethod + def gen_cookie(): + RequestBase.cookie_counter += 1 + return 'meson_{}'.format(RequestBase.cookie_counter) + +class ReplyBase(MessageBase): + def __init__(self, cookie: str, in_reply_to: str): + super().__init__('reply', cookie) + self.in_reply_to = in_reply_to + +# Special Message classes + +class Error(MessageBase): + def __init__(self, cookie: str, message: str): + super().__init__('error', cookie) + self.message = message + + def log(self) -> None: + mlog.error(mlog.bold('CMake server error:'), mlog.red(self.message)) + +class MessageHello(MessageBase): + def __init__(self, supported_protocol_versions: List[dict]): + super().__init__('hello', '') + self.supported_protocol_versions = supported_protocol_versions + + def supports(self, major: int, minor: Optional[int] = None) -> bool: + for i in self.supported_protocol_versions: + 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: str, build_dir: str, generator: str, vers_major: int, vers_minor: Optional[int] = 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) -> dict: + vers = {'major': self.vers_major} + if self.vers_minor is not None: + vers['minor'] = self.vers_minor + return { + **super().to_dict(), + 'sourceDirectory': self.src_dir, + 'buildDirectory': self.build_dir, + 'generator': self.generator, + 'protocolVersion': vers + } + +# Reply classes + +class ReplyHandShake(ReplyBase): + def __init__(self, cookie: str): + super().__init__(cookie, 'handshake') + +class CMakeClient: + def __init__(self, env: Environment): + self.env = env + self.proc = None + self.type_map = { + 'hello': self.resolve_type_hello, + 'error': self.resolve_type_error, + 'reply': self.resolve_type_reply, + } + + self.reply_map = { + 'handshake': self.resolve_reply_handshake, + } + + def readMessageRaw(self) -> dict: + 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: + return json.loads('\n'.join(rawData)) + 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') + type = raw_data['type'] + func = self.type_map.get(type, None) + if not func: + raise CMakeException('Recieved unknown message type "{}"'.format(type)) + for i in CMAKE_MESSAGE_TYPES[type]: + if i not in raw_data: + raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, 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: + return reply + + reply.log() + + def do_handshake(self, src_dir: str, build_dir: str, generator: str, vers_major: int, vers_minor: 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) + reply = self.query(request) + if not isinstance(reply, ReplyHandShake): + reply.log() + mlog.log('CMake server handshake:', mlog.red('FAILED')) + raise CMakeException('Failed to perform the handshake with the CMake server') + mlog.log('CMake server handshake:', mlog.green('OK')) + + def resolve_type_error(self, data: dict) -> Error: + return Error(data['cookie'], data['errorMessage']) + + def resolve_type_hello(self, data: dict) -> MessageHello: + return MessageHello(data['supportedProtocolVersions']) + + def resolve_type_reply(self, data: dict) -> ReplyBase: + reply_type = data['inReplyTo'] + func = self.reply_map.get(reply_type, None) + if not func: + raise CMakeException('Recieved unknown reply type "{}"'.format(reply_type)) + for i in ['cookie'] + CMAKE_REPLY_TYPES[reply_type]: + if i not in data: + raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type)) + return func(data) + + def resolve_reply_handshake(self, data: dict) -> ReplyHandShake: + return ReplyHandShake(data['cookie']) + + @contextmanager + def connect(self): + self.startup() + try: + yield + finally: + self.shutdown() + + def startup(self) -> None: + if self.proc is not None: + raise CMakeException('The CMake server was already started') + + cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env) + if cmake_exe is None or cmake_exe is False: + raise CMakeException('Unable to find CMake') + assert(isinstance(cmake_exe, ExternalProgram)) + if not cmake_exe.found(): + raise CMakeException('Unable to find CMake') + + mlog.log('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_vers)) + 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.log('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: + self.proc.terminate() + + self.proc = None diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py new file mode 100644 index 0000000..217247e --- /dev/null +++ b/mesonbuild/cmake/common.py @@ -0,0 +1,21 @@ +# 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 ..mesonlib import MesonException + +class CMakeException(MesonException): + pass diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py new file mode 100644 index 0000000..03fceca --- /dev/null +++ b/mesonbuild/cmake/interpreter.py @@ -0,0 +1,48 @@ +# 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 +from .client import CMakeClient +from .. import mlog +from ..build import Build +from ..environment import Environment +from ..backend.backends import Backend +import os + +CMAKE_BACKEND_GENERATOR_MAP = { + 'ninja': 'Ninja', + 'xcode': 'Xcode', + 'vs2010': 'Visual Studio 10 2010', + 'vs2015': 'Visual Studio 15 2017', + 'vs2017': 'Visual Studio 15 2017', +} + +class CMakeInterpreter: + def __init__(self, build: Build, src_dir: str, build_dir: str, env: Environment, backend: Backend): + assert(hasattr(backend, 'name')) + self.build = build + self.src_dir = src_dir + self.build_dir = build_dir + self.env = env + self.backend_name = backend.name + self.client = CMakeClient(self.env) + os.makedirs(self.build_dir, exist_ok=True) + + def run(self) -> None: + with self.client.connect(): + generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] + self.client.do_handshake(self.src_dir, self.build_dir, generator, 1) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 29e6632..05636db 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -31,6 +31,7 @@ from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabl from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs from .interpreterbase import ObjectHolder from .modules import ModuleReturnValue +from .cmake import CMakeInterpreter import os, shutil, uuid import re, shlex @@ -2493,7 +2494,7 @@ external dependencies (including libraries) must go to "dependencies".''') if method == 'meson': return self.do_subproject_meson(dirname, subdir, default_options, required, kwargs) elif method == 'cmake': - return self.do_subproject_cmake(dirname, subdir, required, kwargs) + return self.do_subproject_cmake(dirname, subdir_abs, required, kwargs) else: raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) # Invalid code is always an error @@ -2513,7 +2514,7 @@ external dependencies (including libraries) must go to "dependencies".''') with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, - self.modules, default_options) + self.modules, default_options) subi.subprojects = self.subprojects subi.subproject_stack = self.subproject_stack + [dirname] @@ -2538,7 +2539,13 @@ external dependencies (including libraries) must go to "dependencies".''') return self.subprojects[dirname] def do_subproject_cmake(self, dirname, subdir, required, kwargs): - raise InterpreterException('CMake subprojects are currently a stub') + with mlog.nested(): + build_dir = os.path.join(self.environment.get_scratch_dir(), 'cmake_subp_{}'.format(dirname)) + new_build = self.build.copy() + subi = CMakeInterpreter(new_build, subdir, build_dir, new_build.environment, self.backend) + subi.run() + + return None def get_option_internal(self, optname): for opts in chain( diff --git a/setup.py b/setup.py index aab740e..7a7edef 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ entries = {'console_scripts': ['meson=mesonbuild.mesonmain:main']} packages = ['mesonbuild', 'mesonbuild.ast', 'mesonbuild.backend', + 'mesonbuild.cmake', 'mesonbuild.compilers', 'mesonbuild.dependencies', 'mesonbuild.modules', diff --git a/test cases/cmake/1 basic/main.cpp b/test cases/cmake/1 basic/main.cpp new file mode 100644 index 0000000..315c0f7 --- /dev/null +++ b/test cases/cmake/1 basic/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build new file mode 100644 index 0000000..bf2d178 --- /dev/null +++ b/test cases/cmake/1 basic/meson.build @@ -0,0 +1,6 @@ +project('cmakeSubTest', ['cpp']) + +subproject('cmMod') + +exe1 = executable('main', ['main.cpp']) +test('test1', exe1) diff --git a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..f27f636 --- /dev/null +++ b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set (CMAKE_CXX_STANDARD 14) + +add_library(cmModLib SHARED cmMod.cpp) diff --git a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp new file mode 100644 index 0000000..d3141d5 --- /dev/null +++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp @@ -0,0 +1,11 @@ +#include "cmMod.hpp" + +using namespace std; + +cmModClass::cmModClass(string foo) { + str = foo + " World"; +} + +string cmModClass::getStr() const { + return str; +} diff --git a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp new file mode 100644 index 0000000..29938dc --- /dev/null +++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +class cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo); + + std::string getStr() const; +}; -- cgit v1.1 From ef18afc48b85cbcb10086558fff50886931103e3 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Thu, 21 Feb 2019 23:44:11 +0100 Subject: cmake: configure works --- mesonbuild/cmake/client.py | 4 +-- mesonbuild/cmake/interpreter.py | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 219acae..b98bf46 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -232,14 +232,14 @@ class CMakeClient: if not cmake_exe.found(): raise CMakeException('Unable to find CMake') - mlog.log('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_vers)) + mlog.debug('Starting CMake server with CMake', mlog.bold(' '.join(cmake_exe.get_command())), 'version', mlog.cyan(cmake_vers)) 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.log('Shutting down the CMake server') + mlog.debug('Shutting down the CMake server') # Close the pipes to exit self.proc.stdin.close() diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 03fceca..e12c15a 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -21,6 +21,8 @@ from .. import mlog from ..build import Build from ..environment import Environment from ..backend.backends import Backend +from ..dependencies.base import CMakeDependency, ExternalProgram +from subprocess import Popen, PIPE, STDOUT import os CMAKE_BACKEND_GENERATOR_MAP = { @@ -31,6 +33,16 @@ CMAKE_BACKEND_GENERATOR_MAP = { 'vs2017': 'Visual Studio 15 2017', } +CMAKE_LANGUAGE_MAP = { + 'c': 'C', + 'cpp': 'CXX', + 'cuda': 'CUDA', + 'cs': 'CSharp', + 'java': 'Java', + 'fortran': 'Fortran', + 'swift': 'Swift', +} + class CMakeInterpreter: def __init__(self, build: Build, src_dir: str, build_dir: str, env: Environment, backend: Backend): assert(hasattr(backend, 'name')) @@ -42,7 +54,54 @@ class CMakeInterpreter: self.client = CMakeClient(self.env) os.makedirs(self.build_dir, exist_ok=True) + def configure(self) -> None: + # Find CMake + cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env) + if cmake_exe is None or cmake_exe is False: + raise CMakeException('Unable to find CMake') + assert(isinstance(cmake_exe, ExternalProgram)) + if not cmake_exe.found(): + raise CMakeException('Unable to find CMake') + + generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] + cmake_args = cmake_exe.get_command() + + # Map meson compiler to CMake variables + for lang, comp in self.build.compilers.items(): + if lang not in CMAKE_LANGUAGE_MAP: + continue + cmake_lang = CMAKE_LANGUAGE_MAP[lang] + exelist = comp.get_exelist() + if len(exelist) == 1: + cmake_args += ['-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[0])] + elif len(exelist) == 2: + cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]), + '-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])] + cmake_args += ['-G', generator] + + # Run CMake + mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_vers)) + with mlog.nested(): + mlog.log(mlog.bold('Running:'), ' '.join(cmake_args)) + proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=STDOUT, cwd=self.build_dir) + + # Print CMake log in realtime + while True: + line = proc.stdout.readline() + if not line: + break + mlog.log(line.decode('utf-8').strip('\n')) + + # Wait for CMake to finish + proc.communicate() + + h = mlog.green('SUCCEEDED') if proc.returncode == 0 else mlog.red('FAILED') + mlog.log('CMake configuration', h) + if proc.returncode != 0: + raise CMakeException('Failed to configure the CMake subproject') + def run(self) -> None: + self.configure() with self.client.connect(): generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] self.client.do_handshake(self.src_dir, self.build_dir, generator, 1) -- cgit v1.1 From 9aae603d1b5c660afefb3e208c284f880173cf9b Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 22 Feb 2019 11:46:18 +0100 Subject: cmake: Handle a few more stub messages --- mesonbuild/cmake/client.py | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index b98bf46..2fbb89a 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -30,7 +30,10 @@ 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'], } CMAKE_REPLY_TYPES = { @@ -66,6 +69,14 @@ class ReplyBase(MessageBase): super().__init__('reply', cookie) self.in_reply_to = in_reply_to +class SignalBase(MessageBase): + def __init__(self, cookie: str, signal_name: str): + 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): @@ -76,6 +87,21 @@ class Error(MessageBase): 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): + super().__init__('message', cookie) + self.message = message + + def log(self) -> None: + mlog.log(mlog.bold('CMake:'), self.message) + +class Progress(MessageBase): + def __init__(self, cookie: str): + super().__init__('progress', cookie) + + def log(self) -> None: + pass + class MessageHello(MessageBase): def __init__(self, supported_protocol_versions: List[dict]): super().__init__('hello', '') @@ -122,13 +148,16 @@ class CMakeClient: self.env = env self.proc = None self.type_map = { - 'hello': self.resolve_type_hello, - 'error': self.resolve_type_error, + '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']) } self.reply_map = { - 'handshake': self.resolve_reply_handshake, + 'handshake': lambda data: ReplyHandShake(data['cookie']), } def readMessageRaw(self) -> dict: @@ -175,7 +204,7 @@ class CMakeClient: self.writeMessage(request) while True: reply = self.readMessage() - if reply.cookie == request.cookie: + if reply.cookie == request.cookie and reply.type in ['reply', 'error']: return reply reply.log() @@ -194,12 +223,6 @@ class CMakeClient: raise CMakeException('Failed to perform the handshake with the CMake server') mlog.log('CMake server handshake:', mlog.green('OK')) - def resolve_type_error(self, data: dict) -> Error: - return Error(data['cookie'], data['errorMessage']) - - def resolve_type_hello(self, data: dict) -> MessageHello: - return MessageHello(data['supportedProtocolVersions']) - def resolve_type_reply(self, data: dict) -> ReplyBase: reply_type = data['inReplyTo'] func = self.reply_map.get(reply_type, None) @@ -210,9 +233,6 @@ class CMakeClient: raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type)) return func(data) - def resolve_reply_handshake(self, data: dict) -> ReplyHandShake: - return ReplyHandShake(data['cookie']) - @contextmanager def connect(self): self.startup() -- cgit v1.1 From 2039cb708bddc1bd8f3b936485e57278e4578a3d Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 22 Feb 2019 14:05:22 +0100 Subject: cmake: get CMake inputs --- mesonbuild/cmake/client.py | 70 +++++++++++++++++++++++++++++++++++++++-- mesonbuild/cmake/interpreter.py | 12 ++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 2fbb89a..037a3b7 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -38,6 +38,8 @@ CMAKE_MESSAGE_TYPES = { CMAKE_REPLY_TYPES = { 'handshake': [], + 'configure': [], + 'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'] } # Base CMake server message classes @@ -93,7 +95,8 @@ class Message(MessageBase): self.message = message def log(self) -> None: - mlog.log(mlog.bold('CMake:'), self.message) + #mlog.log(mlog.bold('CMake:'), self.message) + pass class Progress(MessageBase): def __init__(self, cookie: str): @@ -137,12 +140,55 @@ class RequestHandShake(RequestBase): 'protocolVersion': vers } +class RequestConfigure(RequestBase): + def __init__(self, args: Optional[List[str]] = None): + super().__init__('configure') + self.args = args + + def to_dict(self) -> dict: + res = super().to_dict() + if self.args: + res['cacheArguments'] = self.args + return res + +class RequestCMakeInputs(RequestBase): + def __init__(self): + super().__init__('cmakeInputs') + # Reply classes class ReplyHandShake(ReplyBase): def __init__(self, cookie: str): super().__init__(cookie, 'handshake') +class ReplyConfigure(ReplyBase): + def __init__(self, cookie: str): + super().__init__(cookie, 'configure') + +class CMakeBuildFile: + def __init__(self, file: str, is_cmake: bool, is_temp: bool): + self.file = file + self.is_cmake = is_cmake + self.is_temp = is_temp + + def __repr__(self): + return '<{}: {}; cmake={}; temp={}>'.format(self.__class__.__name__, self.file, self.is_cmake, self.is_temp) + +class ReplyCMakeInputs(ReplyBase): + def __init__(self, cookie: str, cmake_root: str, src_dir: str, build_files: List[CMakeBuildFile]): + 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)) + mlog.log('Source dir: ', mlog.bold(self.src_dir)) + 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 CMakeClient: def __init__(self, env: Environment): self.env = env @@ -158,6 +204,8 @@ class CMakeClient: self.reply_map = { 'handshake': lambda data: ReplyHandShake(data['cookie']), + 'configure': lambda data: ReplyConfigure(data['cookie']), + 'cmakeInputs': self.resolve_reply_cmakeInputs, } def readMessageRaw(self) -> dict: @@ -209,6 +257,15 @@ class CMakeClient: reply.log() + def query_checked(self, request: RequestBase, message: str) -> ReplyBase: + 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: str, build_dir: str, generator: str, vers_major: int, vers_minor: Optional[int] = None) -> None: # CMake prints the hello message on startup msg = self.readMessage() @@ -219,9 +276,9 @@ class CMakeClient: reply = self.query(request) if not isinstance(reply, ReplyHandShake): reply.log() - mlog.log('CMake server handshake:', mlog.red('FAILED')) + mlog.log('CMake server handshake', mlog.red('FAILED')) raise CMakeException('Failed to perform the handshake with the CMake server') - mlog.log('CMake server handshake:', mlog.green('OK')) + mlog.log('CMake server handshake', mlog.green('SUCCEEDED')) def resolve_type_reply(self, data: dict) -> ReplyBase: reply_type = data['inReplyTo'] @@ -233,6 +290,13 @@ 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: + files = [] + for i in data['buildFiles']: + for j in i['sources']: + files += [CMakeBuildFile(j, i['isCMake'], i['isTemporary'])] + return ReplyCMakeInputs(data['cookie'], data['cmakeRootDirectory'], data['sourceDirectory'], files) + @contextmanager def connect(self): self.startup() diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index e12c15a..ff7c1dc 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -16,7 +16,7 @@ # or an interpreter-based tool. from .common import CMakeException -from .client import CMakeClient +from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, ReplyCMakeInputs from .. import mlog from ..build import Build from ..environment import Environment @@ -101,7 +101,17 @@ class CMakeInterpreter: raise CMakeException('Failed to configure the CMake subproject') def run(self) -> None: + # Run configure the old way becuse doing it + # with the server doesn't work for some reason self.configure() + with self.client.connect(): generator = CMAKE_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') + + # Get CMake build system files + bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files') + bs_reply.log() -- cgit v1.1 From 5089eb356b2cc1d68fdc8b06946e2676dd48d576 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 22 Feb 2019 17:18:31 +0100 Subject: cmake: extract the codemodel --- mesonbuild/cmake/client.py | 171 ++++++++++++++++++++- mesonbuild/cmake/interpreter.py | 12 +- test cases/cmake/2 advanced/main.cpp | 10 ++ test cases/cmake/2 advanced/meson.build | 6 + .../2 advanced/subprojects/cmMod/CMakeLists.txt | 16 ++ .../2 advanced/subprojects/cmMod/lib/cmMod.cpp | 11 ++ .../2 advanced/subprojects/cmMod/lib/cmMod.hpp | 12 ++ .../cmake/2 advanced/subprojects/cmMod/main.cpp | 10 ++ 8 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 test cases/cmake/2 advanced/main.cpp create mode 100644 test cases/cmake/2 advanced/meson.build create mode 100644 test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt create mode 100644 test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp create mode 100644 test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp create mode 100644 test cases/cmake/2 advanced/subprojects/cmMod/main.cpp diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 037a3b7..ae572f9 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -39,7 +39,9 @@ CMAKE_MESSAGE_TYPES = { CMAKE_REPLY_TYPES = { 'handshake': [], 'configure': [], - 'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'] + 'compute': [], + 'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'], + 'codemodel': ['configurations'] } # Base CMake server message classes @@ -151,10 +153,18 @@ class RequestConfigure(RequestBase): res['cacheArguments'] = self.args return res +class RequestCompute(RequestBase): + def __init__(self): + super().__init__('compute') + class RequestCMakeInputs(RequestBase): def __init__(self): super().__init__('cmakeInputs') +class RequestCodeModel(RequestBase): + def __init__(self): + super().__init__('codemodel') + # Reply classes class ReplyHandShake(ReplyBase): @@ -165,6 +175,10 @@ class ReplyConfigure(ReplyBase): def __init__(self, cookie: str): super().__init__(cookie, 'configure') +class ReplyCompute(ReplyBase): + def __init__(self, cookie: str): + super().__init__(cookie, 'compute') + class CMakeBuildFile: def __init__(self, file: str, is_cmake: bool, is_temp: bool): self.file = file @@ -189,6 +203,150 @@ class ReplyCMakeInputs(ReplyBase): for i in self.build_files: mlog.log(str(i)) +def _flags_to_list(raw: str) -> List[str]: + res = [] + curr = '' + escape = False + in_string = False + for i in raw: + if escape: + curr += i + escape = False + elif i == '\\': + escape = True + elif i == '"' or i == "'": + in_string = not in_string + elif i == ' ' or i == '\n': + if in_string: + curr += i + else: + res += [curr] + curr = '' + else: + curr += i + res += [curr] + res = list(filter(lambda x: len(x) > 0, res)) + return res + +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', []) + + # Fix the include directories + tmp = [] + for i in self.includes: + if isinstance(i, dict) and 'path' in i: + tmp += [i['path']] + elif isinstance(i, str): + tmp += [i] + self.includes = tmp + + 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('is_generated =', mlog.bold('true' if self.is_generated else 'false')) + mlog.log('language =', mlog.bold(self.language)) + mlog.log('sources:') + for i in self.sources: + with mlog.nested(): + 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 = [] + + for i in data.get('fileGroups', []): + self.files += [CMakeFileGroup(i)] + + def log(self) -> None: + mlog.log('artifacts =', mlog.bold(', '.join(self.artifacts))) + mlog.log('src_dir =', mlog.bold(self.src_dir)) + mlog.log('build_dir =', mlog.bold(self.build_dir)) + mlog.log('name =', mlog.bold(self.name)) + mlog.log('full_name =', mlog.bold(self.full_name)) + mlog.log('install =', mlog.bold('true' if self.install else 'false')) + mlog.log('install_paths =', mlog.bold(', '.join(self.install_paths))) + mlog.log('link_lang =', mlog.bold(self.link_lang)) + mlog.log('link_libraries =', mlog.bold(', '.join(self.link_libraries))) + mlog.log('link_flags =', mlog.bold(', '.join(self.link_flags))) + mlog.log('link_lang_flags =', mlog.bold(', '.join(self.link_lang_flags))) + mlog.log('link_path =', mlog.bold(self.link_path)) + mlog.log('type =', mlog.bold(self.type)) + mlog.log('is_generator_provided =', mlog.bold('true' if self.is_generator_provided else 'false')) + for idx, i in enumerate(self.files): + mlog.log('Files {}:'.format(idx)) + with mlog.nested(): + 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 = [] + + for i in data.get('targets', []): + self.targets += [CMakeTarget(i)] + + def log(self) -> None: + mlog.log('src_dir =', mlog.bold(self.src_dir)) + mlog.log('build_dir =', mlog.bold(self.build_dir)) + mlog.log('name =', mlog.bold(self.name)) + for idx, i in enumerate(self.targets): + mlog.log('Target {}:'.format(idx)) + with mlog.nested(): + i.log() + +class CMakeConfiguration: + def __init__(self, data: dict): + self.name = data.get('name', '') + self.projects = [] + for i in data.get('projects', []): + self.projects += [CMakeProject(i)] + + def log(self) -> None: + mlog.log('name =', mlog.bold(self.name)) + for idx, i in enumerate(self.projects): + mlog.log('Project {}:'.format(idx)) + with mlog.nested(): + i.log() + +class ReplyCodeModel(ReplyBase): + def __init__(self, data: dict): + 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('Configuration {}:'.format(idx)) + with mlog.nested(): + i.log() + +# Main client class + class CMakeClient: def __init__(self, env: Environment): self.env = env @@ -205,7 +363,9 @@ class CMakeClient: 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), } def readMessageRaw(self) -> dict: @@ -260,7 +420,7 @@ class CMakeClient: def query_checked(self, request: RequestBase, message: str) -> ReplyBase: reply = self.query(request) h = mlog.green('SUCCEEDED') if reply.type == 'reply' else mlog.red('FAILED') - mlog.log(message, h) + mlog.log(message + ':', h) if reply.type != 'reply': reply.log() raise CMakeException('CMake server query failed') @@ -273,12 +433,7 @@ class CMakeClient: raise CMakeException('Recieved an unexpected message from the CMake server') request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor) - reply = self.query(request) - if not isinstance(reply, ReplyHandShake): - reply.log() - mlog.log('CMake server handshake', mlog.red('FAILED')) - raise CMakeException('Failed to perform the handshake with the CMake server') - mlog.log('CMake server handshake', mlog.green('SUCCEEDED')) + self.query_checked(request, 'CMake server handshake') def resolve_type_reply(self, data: dict) -> ReplyBase: reply_type = data['inReplyTo'] diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index ff7c1dc..d717c59 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -16,7 +16,7 @@ # or an interpreter-based tool. from .common import CMakeException -from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, ReplyCMakeInputs +from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel from .. import mlog from ..build import Build from ..environment import Environment @@ -96,7 +96,7 @@ class CMakeInterpreter: proc.communicate() h = mlog.green('SUCCEEDED') if proc.returncode == 0 else mlog.red('FAILED') - mlog.log('CMake configuration', h) + mlog.log('CMake configuration:', h) if proc.returncode != 0: raise CMakeException('Failed to configure the CMake subproject') @@ -112,6 +112,12 @@ class CMakeInterpreter: # 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') - bs_reply.log() + + # Now get the CMake code model + cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model') + cm_reply.log() diff --git a/test cases/cmake/2 advanced/main.cpp b/test cases/cmake/2 advanced/main.cpp new file mode 100644 index 0000000..315c0f7 --- /dev/null +++ b/test cases/cmake/2 advanced/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build new file mode 100644 index 0000000..eb5dab1 --- /dev/null +++ b/test cases/cmake/2 advanced/meson.build @@ -0,0 +1,6 @@ +project('cmakeSubTest_advanced', ['cpp']) + +subproject('cmMod') + +exe1 = executable('main', ['main.cpp']) +test('test1', exe1) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..b4cf698 --- /dev/null +++ b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set(CMAKE_CXX_STANDARD 14) + +find_package(ZLIB REQUIRED) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib) + +add_library(cmModLib SHARED lib/cmMod.cpp) +add_executable(testEXE main.cpp) + +target_link_libraries(cmModLib ZLIB::ZLIB) +target_link_libraries(testEXE cmModLib) + +install(TARGETS cmModLib testEXE LIBRARY DESTINATION lib RUNTIME DESTINATION bin) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp new file mode 100644 index 0000000..d3141d5 --- /dev/null +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp @@ -0,0 +1,11 @@ +#include "cmMod.hpp" + +using namespace std; + +cmModClass::cmModClass(string foo) { + str = foo + " World"; +} + +string cmModClass::getStr() const { + return str; +} diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp new file mode 100644 index 0000000..29938dc --- /dev/null +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +class cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo); + + std::string getStr() const; +}; diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp new file mode 100644 index 0000000..cd21042 --- /dev/null +++ b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp @@ -0,0 +1,10 @@ +#include +#include "lib/cmMod.hpp" + +using namespace std; + +int main() { + cmModClass obj("Hello (LIB TEST)"); + cout << obj.getStr() << endl; + return 0; +} -- cgit v1.1 From e55236bde495cb7c638a010c2de2dcadc31273e0 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 22 Feb 2019 22:10:49 +0100 Subject: cmake: basic AST generation --- mesonbuild/cmake/interpreter.py | 203 ++++++++++++++++++++++++++++++++++++++-- mesonbuild/interpreter.py | 31 ++++-- 2 files changed, 218 insertions(+), 16 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index d717c59..3011720 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -16,14 +16,16 @@ # or an interpreter-based tool. from .common import CMakeException -from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel +from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, CMakeTarget from .. import mlog from ..build import Build from ..environment import Environment +from ..mparser import Token, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, StringNode, IdNode from ..backend.backends import Backend from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT -import os +from typing import Dict +import os, re CMAKE_BACKEND_GENERATOR_MAP = { 'ninja': 'Ninja', @@ -43,16 +45,135 @@ CMAKE_LANGUAGE_MAP = { 'swift': 'Swift', } +class ConverterTarget: + lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()} + + def __init__(self, target: CMakeTarget): + self.artifacts = target.artifacts + self.src_dir = target.src_dir + self.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 + + if target.install_paths: + self.install_dir = target.install_paths[0] + + self.languages = [] + self.sources = [] + self.generated = [] + self.includes = [] + self.link_with = [] + self.compile_opts = {} + + # Project default override options (c_std, cpp_std, etc.) + self.override_options = [] + + for i in target.files: + # Determine the meson language + lang = ConverterTarget.lang_cmake_to_meson.get(i.language.lower(), 'c') + if lang not in self.languages: + self.languages += [lang] + if lang not in self.compile_opts: + self.compile_opts[lang] = [] + + # Add arguments, but avoid duplicates + args = i.flags + args += ['-D{}'.format(x) for x in i.defines] + self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]] + + # Handle include directories + self.includes += [x for x in i.includes if x not in self.includes] + + # Add sources to the right array + if i.is_generated: + self.generated += i.sources + else: + self.sources += i.sources + + def __repr__(self) -> str: + return '<{}: {}>'.format(self.__class__.__name__, self.name) + + std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') + + def postprocess(self, output_target_map) -> None: + # Detect setting the C and C++ standard + for i in ['c', 'cpp']: + if not i in self.compile_opts: + continue + + temp = [] + for j in self.compile_opts[i]: + m = ConverterTarget.std_regex.match(j) + if m: + self.override_options += ['{}_std={}'.format(i, m.group(2))] + else: + temp += [j] + + self.compile_opts[i] = temp + + # Fix link libraries + temp = [] + for i in self.link_libraries: + # Let meson handle this arcane magic + if ',-rpath,' in i: + continue + if i in output_target_map: + self.link_with += [output_target_map[i]] + continue + + temp += [i] + self.link_libraries = temp + + # Make paths relative + def rel_path(x: str) -> str: + if os.path.isabs(x) and os.path.commonpath([x, self.src_dir]) == self.src_dir: + return os.path.relpath(x, self.src_dir) + return x + + self.includes = [rel_path(x) for x in self.includes] + self.sources = [rel_path(x) for x in self.sources] + self.generated = [rel_path(x) for x in self.generated] + + def log(self) -> None: + mlog.log('Target', mlog.bold(self.name)) + mlog.log(' -- full_name: ', mlog.bold(self.full_name)) + mlog.log(' -- type: ', mlog.bold(self.type)) + mlog.log(' -- install: ', mlog.bold('true' if self.install else 'false')) + mlog.log(' -- install_dir: ', mlog.bold(self.install_dir)) + mlog.log(' -- link_libraries: ', mlog.bold(str(self.link_libraries))) + mlog.log(' -- link_with: ', mlog.bold(str(self.link_with))) + mlog.log(' -- link_flags: ', mlog.bold(str(self.link_flags))) + mlog.log(' -- languages: ', mlog.bold(str(self.languages))) + mlog.log(' -- includes: ', mlog.bold(str(self.includes))) + mlog.log(' -- sources: ', mlog.bold(str(self.sources))) + mlog.log(' -- generated: ', mlog.bold(str(self.generated))) + mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options))) + mlog.log(' -- options:') + for key, val in self.compile_opts.items(): + mlog.log(' -', key, '=', mlog.bold(str(val))) + class CMakeInterpreter: - def __init__(self, build: Build, src_dir: str, build_dir: str, env: Environment, backend: Backend): + def __init__(self, build: Build, subdir: str, src_dir: str, build_dir: str, env: Environment, backend: Backend): assert(hasattr(backend, 'name')) self.build = build + self.subdir = subdir self.src_dir = src_dir self.build_dir = build_dir self.env = env self.backend_name = backend.name self.client = CMakeClient(self.env) - os.makedirs(self.build_dir, exist_ok=True) + + # Raw CMake results + self.bs_files = [] + self.codemodel = None + + # Analysed data + self.project_name = '' + self.languages = [] def configure(self) -> None: # Find CMake @@ -80,9 +201,12 @@ class CMakeInterpreter: cmake_args += ['-G', generator] # Run CMake - mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_vers)) + mlog.log() with mlog.nested(): + mlog.log('Configuring the build directory with', mlog.bold('CMake'), 'version', mlog.cyan(cmake_vers)) mlog.log(mlog.bold('Running:'), ' '.join(cmake_args)) + mlog.log() + os.makedirs(self.build_dir, exist_ok=True) proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=STDOUT, cwd=self.build_dir) # Print CMake log in realtime @@ -95,12 +219,13 @@ class CMakeInterpreter: # Wait for CMake to finish proc.communicate() + mlog.log() h = mlog.green('SUCCEEDED') if proc.returncode == 0 else mlog.red('FAILED') mlog.log('CMake configuration:', h) if proc.returncode != 0: raise CMakeException('Failed to configure the CMake subproject') - def run(self) -> None: + def initialise(self) -> None: # Run configure the old way becuse doing it # with the server doesn't work for some reason self.configure() @@ -120,4 +245,68 @@ class CMakeInterpreter: # Now get the CMake code model cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model') - cm_reply.log() + + 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 = [os.path.relpath(os.path.join(src_dir, x), self.env.get_source_dir()) for x in self.bs_files] + self.codemodel = cm_reply + + def analyse(self) -> None: + if self.codemodel is None: + raise CMakeException('CMakeInterpreter was not initialized') + + # Clear analyser data + self.project_name = '' + self.languages = [] + + # Find all targets + targets = [] + for i in self.codemodel.configs: + for j in i.projects: + if not self.project_name: + self.project_name = j.name + for k in j.targets: + targets += [ConverterTarget(k)] + + output_target_map = {x.full_name: x for x in targets} + + for i in targets: + i.postprocess(output_target_map) + self.languages += [x for x in i.languages if x not in self.languages] + + mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(targets))), 'build targets.') + + def pretend_to_be_meson(self) -> CodeBlockNode: + if not self.project_name: + raise CMakeException('CMakeInterpreter was not analysed') + + def token(tid: str = 'string', val='') -> Token: + return Token(tid, self.subdir, 0, 0, 0, None, val) + + def string(value: str) -> StringNode: + return StringNode(token(val=value)) + + def id(value: str) -> IdNode: + return IdNode(token(val=value)) + + def array(elements: list) -> ArrayNode: + args = ArgumentNode(token()) + for i in elements: + if isinstance(i, str): + i = string(i) + args.arguments += [i] + return ArrayNode(args, 0, 0) + + def function(name: str, args=[]) -> FunctionNode: + args_n = ArgumentNode(token()) + for i in args: + if isinstance(i, str): + i = string(i) + args_n.arguments += [i] + func_n = FunctionNode(self.subdir, 0, 0, name, args_n) + return func_n + + root_cb = CodeBlockNode(token()) + root_cb.lines += [function('project', [self.project_name, array(self.languages)])] + + return root_cb diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 05636db..b2b25fe 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2038,7 +2038,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, class Interpreter(InterpreterBase): def __init__(self, build, backend=None, subproject='', subdir='', subproject_dir='subprojects', - modules = None, default_project_options=None, mock=False): + modules = None, default_project_options=None, mock=False, ast=None): super().__init__(build.environment.get_source_dir(), subdir) self.an_unpicklable_object = mesonlib.an_unpicklable_object self.build = build @@ -2055,9 +2055,12 @@ class Interpreter(InterpreterBase): self.subproject_directory_name = subdir.split(os.path.sep)[-1] self.subproject_dir = subproject_dir self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') - if not mock: + if not mock and ast is None: self.load_root_meson_file() self.sanity_check_ast() + elif ast is not None: + self.ast = ast + self.sanity_check_ast() self.builtin.update({'meson': MesonMain(build, self)}) self.generators = [] self.visited_subdirs = {} @@ -2494,7 +2497,7 @@ external dependencies (including libraries) must go to "dependencies".''') if method == 'meson': return self.do_subproject_meson(dirname, subdir, default_options, required, kwargs) elif method == 'cmake': - return self.do_subproject_cmake(dirname, subdir_abs, required, kwargs) + return self.do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs) else: raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) # Invalid code is always an error @@ -2510,11 +2513,11 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(dirname) raise e - def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs): + def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None): with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, - self.modules, default_options) + self.modules, default_options, ast=ast) subi.subprojects = self.subprojects subi.subproject_stack = self.subproject_stack + [dirname] @@ -2538,14 +2541,24 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] - def do_subproject_cmake(self, dirname, subdir, required, kwargs): + def do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): with mlog.nested(): build_dir = os.path.join(self.environment.get_scratch_dir(), 'cmake_subp_{}'.format(dirname)) new_build = self.build.copy() - subi = CMakeInterpreter(new_build, subdir, build_dir, new_build.environment, self.backend) - subi.run() + cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, build_dir, new_build.environment, self.backend) + cm_int.initialise() + cm_int.analyse() + + # Generate a meson ast and execute it with the normal do_subproject_meson + mlog.log() + with mlog.nested(): + mlog.log('Processing generated meson AST') + mlog.log() + ast = cm_int.pretend_to_be_meson() + result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast) - return None + mlog.log() + return result def get_option_internal(self, optname): for opts in chain( -- cgit v1.1 From be6a9191e16aa3ced9cae49eeeee2f21fe47cb63 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 11:28:04 +0100 Subject: cmake: First working version --- mesonbuild/cmake/interpreter.py | 139 ++++++++++++++++++++++++++++++++++------ mesonbuild/interpreter.py | 21 ++++-- 2 files changed, 135 insertions(+), 25 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 3011720..b296ed4 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -20,7 +20,7 @@ from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCo from .. import mlog from ..build import Build from ..environment import Environment -from ..mparser import Token, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, StringNode, IdNode +from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode from ..backend.backends import Backend from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT @@ -45,6 +45,13 @@ CMAKE_LANGUAGE_MAP = { 'swift': 'Swift', } +CMAKE_TGT_TYPE_MAP = { + 'STATIC_LIBRARY': 'static_library', + 'MODULE_LIBRARY': 'shared_module', + 'SHARED_LIBRARY': 'shared_library', + 'EXECUTABLE': 'executable', +} + class ConverterTarget: lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()} @@ -68,6 +75,7 @@ class ConverterTarget: self.includes = [] self.link_with = [] self.compile_opts = {} + self.pie = False # Project default override options (c_std, cpp_std, etc.) self.override_options = [] @@ -99,7 +107,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') - def postprocess(self, output_target_map) -> None: + def postprocess(self, output_target_map: dict, root_src_dir: str) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: if not i in self.compile_opts: @@ -110,6 +118,8 @@ class ConverterTarget: m = ConverterTarget.std_regex.match(j) if m: self.override_options += ['{}_std={}'.format(i, m.group(2))] + elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']: + self.pie = True else: temp += [j] @@ -130,14 +140,23 @@ class ConverterTarget: # Make paths relative def rel_path(x: str) -> str: - if os.path.isabs(x) and os.path.commonpath([x, self.src_dir]) == self.src_dir: - return os.path.relpath(x, self.src_dir) + if not os.path.isabs(x): + x = os.path.normpath(os.path.join(self.src_dir, x)) + if os.path.isabs(x) and os.path.commonpath([x, root_src_dir]) == root_src_dir: + return os.path.relpath(x, root_src_dir) return x self.includes = [rel_path(x) for x in self.includes] self.sources = [rel_path(x) for x in self.sources] self.generated = [rel_path(x) for x in self.generated] + # Make sure '.' is always in the include directories + if '.' not in self.includes: + self.includes += ['.'] + + def meson_func(self) -> str: + return CMAKE_TGT_TYPE_MAP.get(self.type.upper()) + def log(self) -> None: mlog.log('Target', mlog.bold(self.name)) mlog.log(' -- full_name: ', mlog.bold(self.full_name)) @@ -151,6 +170,7 @@ class ConverterTarget: mlog.log(' -- includes: ', mlog.bold(str(self.includes))) mlog.log(' -- sources: ', mlog.bold(str(self.sources))) mlog.log(' -- generated: ', mlog.bold(str(self.generated))) + mlog.log(' -- pie: ', mlog.bold('true' if self.pie else 'false')) mlog.log(' -- override_opts: ', mlog.bold(str(self.override_options))) mlog.log(' -- options:') for key, val in self.compile_opts.items(): @@ -174,6 +194,7 @@ class CMakeInterpreter: # Analysed data self.project_name = '' self.languages = [] + self.targets = [] def configure(self) -> None: # Find CMake @@ -258,23 +279,23 @@ class CMakeInterpreter: # Clear analyser data self.project_name = '' self.languages = [] + self.targets = [] # Find all targets - targets = [] for i in self.codemodel.configs: for j in i.projects: if not self.project_name: self.project_name = j.name for k in j.targets: - targets += [ConverterTarget(k)] + self.targets += [ConverterTarget(k)] - output_target_map = {x.full_name: x for x in targets} + output_target_map = {x.full_name: x for x in self.targets} - for i in targets: - i.postprocess(output_target_map) + for i in self.targets: + i.postprocess(output_target_map, self.src_dir) self.languages += [x for x in i.languages if x not in self.languages] - mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(targets))), 'build targets.') + mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets))), 'build targets.') def pretend_to_be_meson(self) -> CodeBlockNode: if not self.project_name: @@ -289,24 +310,100 @@ class CMakeInterpreter: def id(value: str) -> IdNode: return IdNode(token(val=value)) - def array(elements: list) -> ArrayNode: + def nodeify(value): + if isinstance(value, str): + return string(value) + elif isinstance(value, bool): + return BooleanNode(token(), value) + elif isinstance(value, list): + return array(value) + return value + + def array(elements) -> ArrayNode: args = ArgumentNode(token()) - for i in elements: - if isinstance(i, str): - i = string(i) - args.arguments += [i] + if not isinstance(elements, list): + elements = [args] + args.arguments += [nodeify(x) for x in elements] return ArrayNode(args, 0, 0) - def function(name: str, args=[]) -> FunctionNode: + def function(name: str, args=[], kwargs={}) -> FunctionNode: args_n = ArgumentNode(token()) - for i in args: - if isinstance(i, str): - i = string(i) - args_n.arguments += [i] + if not isinstance(args, list): + args = [args] + args_n.arguments = [nodeify(x) for x in args] + args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()} func_n = FunctionNode(self.subdir, 0, 0, name, args_n) return func_n + def assign(var_name: str, value: BaseNode) -> AssignmentNode: + return AssignmentNode(self.subdir, 0, 0, var_name, value) + + # Generate the root code block and the project function call root_cb = CodeBlockNode(token()) - root_cb.lines += [function('project', [self.project_name, array(self.languages)])] + root_cb.lines += [function('project', [self.project_name] + self.languages)] + + processed = {} + def process_target(tgt: ConverterTarget): + # First handle inter dependencies + link_with = [] + for i in tgt.link_with: + assert(isinstance(i, ConverterTarget)) + if i.name not in processed: + process_target(i) + link_with += [id(processed[i.name]['tgt'])] + + # Determine the meson function to use for the build target + tgt_func = tgt.meson_func() + if not tgt_func: + raise CMakeException('Unknown target type "{}"'.format(tgt.type)) + + # Determine the variable names + base_name = tgt.name + inc_var = '{}_inc'.format(base_name) + src_var = '{}_src'.format(base_name) + dep_var = '{}_dep'.format(base_name) + tgt_var = base_name + + # Generate target kwargs + tgt_kwargs = { + 'link_args': tgt.link_flags + tgt.link_libraries, + 'link_with': link_with, + 'include_directories': id(inc_var), + 'install': tgt.install, + 'install_dir': tgt.install_dir, + 'override_options': tgt.override_options, + } + + # Handle compiler args + for key, val in tgt.compile_opts.items(): + tgt_kwargs['{}_args'.format(key)] = val + + # Handle -fPCI, etc + if tgt_func == 'executable': + tgt_kwargs['pie'] = tgt.pie + elif tgt_func == 'static_library': + tgt_kwargs['pic'] = tgt.pie + + # declare_dependency kwargs + dep_kwargs = { + 'link_args': tgt.link_flags + tgt.link_libraries, + 'link_with': id(tgt_var), + 'include_directories': id(inc_var), + } + + # Generate the function nodes + inc_node = assign(inc_var, function('include_directories', tgt.includes)) + src_node = assign(src_var, function('files', tgt.sources + tgt.generated)) + tgt_node = assign(tgt_var, function(tgt_func, [base_name, id(src_var)], tgt_kwargs)) + dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) + + # Add the nodes to the ast + root_cb.lines += [inc_node, src_node, tgt_node, dep_node] + processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var} + + # Now generate the target function calls + for i in self.targets: + if i.name not in processed: + process_target(i) return root_cb diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index b2b25fe..bee990a 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2513,7 +2513,7 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(dirname) raise e - def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None): + def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None): with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, @@ -2536,7 +2536,10 @@ external dependencies (including libraries) must go to "dependencies".''') self.subprojects.update(subi.subprojects) self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname) # Duplicates are possible when subproject uses files from project root - self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) + if build_def_files: + self.build_def_files += list(set(self.build_def_files + build_def_files)) + else: + self.build_def_files += list(set(self.build_def_files + subi.build_def_files)) self.build.merge(subi.build) self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] @@ -2550,12 +2553,22 @@ external dependencies (including libraries) must go to "dependencies".''') cm_int.analyse() # Generate a meson ast and execute it with the normal do_subproject_meson + ast = cm_int.pretend_to_be_meson() + mlog.log() with mlog.nested(): mlog.log('Processing generated meson AST') mlog.log() - ast = cm_int.pretend_to_be_meson() - result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast) + mlog.log('=== BEGIN meson.build ===') + from .ast import AstIndentationGenerator, AstPrinter + printer = AstPrinter() + ast.accept(AstIndentationGenerator()) + ast.accept(printer) + printer.post_process() + mlog.log(printer.result) + mlog.log('=== END meson.build ===') + mlog.log() + result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) mlog.log() return result -- cgit v1.1 From 2b44b4be9f0e3896ca7f7a24c92a841939332596 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 11:50:32 +0100 Subject: cmake: Fixed install prefix --- mesonbuild/cmake/interpreter.py | 14 ++++++++++---- mesonbuild/interpreter.py | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index b296ed4..e070a68 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -107,7 +107,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') - def postprocess(self, output_target_map: dict, root_src_dir: str) -> None: + def postprocess(self, output_target_map: dict, root_src_dir: str, install_prefix: str) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: if not i in self.compile_opts: @@ -154,6 +154,11 @@ class ConverterTarget: if '.' not in self.includes: self.includes += ['.'] + # make install dir relative to the install prefix + if self.install_dir and os.path.isabs(self.install_dir): + if os.path.commonpath([self.install_dir, install_prefix]) == install_prefix: + self.install_dir = os.path.relpath(self.install_dir, install_prefix) + def meson_func(self) -> str: return CMAKE_TGT_TYPE_MAP.get(self.type.upper()) @@ -177,12 +182,13 @@ class ConverterTarget: mlog.log(' -', key, '=', mlog.bold(str(val))) class CMakeInterpreter: - def __init__(self, build: Build, subdir: str, src_dir: str, build_dir: str, env: Environment, backend: Backend): + def __init__(self, build: Build, subdir: str, src_dir: str, build_dir: str, install_prefix: str, env: Environment, backend: Backend): assert(hasattr(backend, 'name')) self.build = build self.subdir = subdir self.src_dir = src_dir self.build_dir = build_dir + self.install_prefix = install_prefix self.env = env self.backend_name = backend.name self.client = CMakeClient(self.env) @@ -219,7 +225,7 @@ class CMakeInterpreter: elif len(exelist) == 2: cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]), '-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])] - cmake_args += ['-G', generator] + cmake_args += ['-G', generator, '-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] # Run CMake mlog.log() @@ -292,7 +298,7 @@ class CMakeInterpreter: output_target_map = {x.full_name: x for x in self.targets} for i in self.targets: - i.postprocess(output_target_map, self.src_dir) + i.postprocess(output_target_map, self.src_dir, self.install_prefix) self.languages += [x for x in i.languages if x not in self.languages] mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets))), 'build targets.') diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index bee990a..182326e 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2548,7 +2548,8 @@ external dependencies (including libraries) must go to "dependencies".''') with mlog.nested(): build_dir = os.path.join(self.environment.get_scratch_dir(), 'cmake_subp_{}'.format(dirname)) new_build = self.build.copy() - cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, build_dir, new_build.environment, self.backend) + prefix = self.coredata.builtins['prefix'].value + cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, build_dir, prefix, new_build.environment, self.backend) cm_int.initialise() cm_int.analyse() -- cgit v1.1 From bf81aac465bbfe9545393c18328804776defa0e0 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 11:50:46 +0100 Subject: cmake: Added project tests --- run_project_tests.py | 1 + test cases/cmake/1 basic/meson.build | 5 +++-- test cases/cmake/2 advanced/installed_files.txt | 2 ++ test cases/cmake/2 advanced/meson.build | 15 +++++++++++++-- .../cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp | 3 ++- test cases/cmake/2 advanced/subprojects/cmMod/main.cpp | 3 ++- 6 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 test cases/cmake/2 advanced/installed_files.txt diff --git a/run_project_tests.py b/run_project_tests.py index a3aa07d..c3187ff 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -553,6 +553,7 @@ def skip_csharp(backend): def detect_tests_to_run(): # Name, subdirectory, skip condition. all_tests = [ + ('cmake', 'cmake', not shutil.which('cmake')), ('common', 'common', False), ('warning-meson', 'warning', False), ('failing-meson', 'failing', False), diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build index bf2d178..de54cce 100644 --- a/test cases/cmake/1 basic/meson.build +++ b/test cases/cmake/1 basic/meson.build @@ -1,6 +1,7 @@ project('cmakeSubTest', ['cpp']) -subproject('cmMod') +sub_pro = subproject('cmMod', method: 'cmake') +sub_dep = sub_pro.get_variable('cmModLib_dep') -exe1 = executable('main', ['main.cpp']) +exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) test('test1', exe1) diff --git a/test cases/cmake/2 advanced/installed_files.txt b/test cases/cmake/2 advanced/installed_files.txt new file mode 100644 index 0000000..40235a5 --- /dev/null +++ b/test cases/cmake/2 advanced/installed_files.txt @@ -0,0 +1,2 @@ +usr/lib/libcmModLib.so +usr/bin/testEXE \ No newline at end of file diff --git a/test cases/cmake/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build index eb5dab1..b3acc97 100644 --- a/test cases/cmake/2 advanced/meson.build +++ b/test cases/cmake/2 advanced/meson.build @@ -1,6 +1,17 @@ project('cmakeSubTest_advanced', ['cpp']) -subproject('cmMod') +# Test the "normal" subproject call +sub_pro = subproject('cmMod') +sub_dep = sub_pro.get_variable('cmModLib_dep') -exe1 = executable('main', ['main.cpp']) +# Test the dependency shortcut +shortcut_dep = dependency('ttabadbneiobevn', fallback: ['cmMod', 'cmModLib_dep']) + +# Build some files +exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep]) +exe2 = executable('main2', ['main.cpp'], dependencies: [shortcut_dep]) test('test1', exe1) +test('test2', exe2) + +# Test if we can also extract executables +test('test3', sub_pro.get_variable('testEXE')) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp index d3141d5..cb20844 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp @@ -1,9 +1,10 @@ #include "cmMod.hpp" +#include using namespace std; cmModClass::cmModClass(string foo) { - str = foo + " World"; + str = foo + " World " + zlibVersion(); } string cmModClass::getStr() const { diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp index cd21042..a1b1637 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp +++ b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp @@ -1,10 +1,11 @@ #include +#include #include "lib/cmMod.hpp" using namespace std; int main() { cmModClass obj("Hello (LIB TEST)"); - cout << obj.getStr() << endl; + cout << obj.getStr() << " ZLIB: " << zlibVersion() << endl; return 0; } -- cgit v1.1 From 316f4f49485ed51e29942364f11692638d47cfc1 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 11:58:23 +0100 Subject: cmake: Always add the CMake build dir to the include dirs --- mesonbuild/cmake/interpreter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index e070a68..ae42ae7 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -58,6 +58,7 @@ class ConverterTarget: def __init__(self, target: CMakeTarget): self.artifacts = target.artifacts self.src_dir = target.src_dir + self.build_dir = target.build_dir self.name = target.name self.full_name = target.full_name self.type = target.type @@ -146,7 +147,7 @@ class ConverterTarget: return os.path.relpath(x, root_src_dir) return x - self.includes = [rel_path(x) for x in self.includes] + self.includes = [rel_path(x) for x in set(self.includes + [self.build_dir])] self.sources = [rel_path(x) for x in self.sources] self.generated = [rel_path(x) for x in self.generated] -- cgit v1.1 From d9481f23599d8eab5ef4bceb9cde23a14ef4f066 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 12:18:33 +0100 Subject: cmake: Added Code generation test --- test cases/cmake/3 code gen/main.cpp | 8 ++++++++ test cases/cmake/3 code gen/meson.build | 18 ++++++++++++++++++ .../3 code gen/subprojects/cmCodeGen/CMakeLists.txt | 5 +++++ .../cmake/3 code gen/subprojects/cmCodeGen/main.cpp | 21 +++++++++++++++++++++ test cases/cmake/3 code gen/test.hpp | 5 +++++ 5 files changed, 57 insertions(+) create mode 100644 test cases/cmake/3 code gen/main.cpp create mode 100644 test cases/cmake/3 code gen/meson.build create mode 100644 test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt create mode 100644 test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp create mode 100644 test cases/cmake/3 code gen/test.hpp diff --git a/test cases/cmake/3 code gen/main.cpp b/test cases/cmake/3 code gen/main.cpp new file mode 100644 index 0000000..3ddbf63 --- /dev/null +++ b/test cases/cmake/3 code gen/main.cpp @@ -0,0 +1,8 @@ +#include +#include "test.hpp" + +using namespace std; + +int main() { + cout << getStr() << endl; +} diff --git a/test cases/cmake/3 code gen/meson.build b/test cases/cmake/3 code gen/meson.build new file mode 100644 index 0000000..daa93ce --- /dev/null +++ b/test cases/cmake/3 code gen/meson.build @@ -0,0 +1,18 @@ +project('cmake_code_gen', ['cpp']) + +# Subproject with the "code generator" +sub_pro = subproject('cmCodeGen') +sub_exe = sub_pro.get_variable('genA') + +# Generate the source +generated = custom_target( + 'cmake-generated', + input: [], + output: ['test.cpp'], + command: [sub_exe, '@OUTPUT@'] +) + +# Build the exe +exe1 = executable('main1', ['main.cpp', generated]) + +test('test1', exe1) \ No newline at end of file diff --git a/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt b/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt new file mode 100644 index 0000000..268743c --- /dev/null +++ b/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.7) + +set(CMAKE_CXX_STANDARD 14) + +add_executable(genA main.cpp) diff --git a/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp b/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp new file mode 100644 index 0000000..5b7fed2 --- /dev/null +++ b/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +using namespace std; + +int main(int argc, const char *argv[]) { + if(argc < 2) { + cerr << argv[0] << " requires an output file!" << endl; + return 1; + } + ofstream out(argv[1]); + out << R"( +#include "test.hpp" + +std::string getStr() { + return "Hello World"; +} +)"; + + return 0; +} diff --git a/test cases/cmake/3 code gen/test.hpp b/test cases/cmake/3 code gen/test.hpp new file mode 100644 index 0000000..8e25a0a --- /dev/null +++ b/test cases/cmake/3 code gen/test.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getStr(); -- cgit v1.1 From f73b36c2f342ff9bef5ebc9ca49fa14f54b81d85 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 12:23:44 +0100 Subject: cmake: Added test for configure_file --- test cases/cmake/2 advanced/main.cpp | 5 +++++ test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt | 3 +++ test cases/cmake/2 advanced/subprojects/cmMod/config.h.in | 3 +++ test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp | 5 +++++ 4 files changed, 16 insertions(+) create mode 100644 test cases/cmake/2 advanced/subprojects/cmMod/config.h.in diff --git a/test cases/cmake/2 advanced/main.cpp b/test cases/cmake/2 advanced/main.cpp index 315c0f7..6cc4c0c 100644 --- a/test cases/cmake/2 advanced/main.cpp +++ b/test cases/cmake/2 advanced/main.cpp @@ -1,5 +1,10 @@ #include #include +#include "config.h" + +#if CONFIG_OPT != 42 +#error "Invalid value of CONFIG_OPT" +#endif using namespace std; diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt index b4cf698..3f33e4d 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt @@ -7,6 +7,9 @@ find_package(ZLIB REQUIRED) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib) +set(CONFIG_OPT 42) +configure_file("config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) + add_library(cmModLib SHARED lib/cmMod.cpp) add_executable(testEXE main.cpp) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/config.h.in b/test cases/cmake/2 advanced/subprojects/cmMod/config.h.in new file mode 100644 index 0000000..f538ac9 --- /dev/null +++ b/test cases/cmake/2 advanced/subprojects/cmMod/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define CONFIG_OPT @CONFIG_OPT@ diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp index cb20844..027296e 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp @@ -1,5 +1,10 @@ #include "cmMod.hpp" #include +#include "config.h" + +#if CONFIG_OPT != 42 +#error "Invalid value of CONFIG_OPT" +#endif using namespace std; -- cgit v1.1 From 1041c71eb0370041492055fff5c7a2bffd306b58 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 15:02:33 +0100 Subject: cmake: support object libraries --- mesonbuild/cmake/interpreter.py | 43 ++++++++++++++++++++-- mesonbuild/interpreter.py | 11 ++++-- test cases/cmake/4 object library/main.cpp | 9 +++++ test cases/cmake/4 object library/meson.build | 14 +++++++ .../subprojects/cmObjLib/CMakeLists.txt | 10 +++++ .../4 object library/subprojects/cmObjLib/libA.cpp | 5 +++ .../4 object library/subprojects/cmObjLib/libA.hpp | 5 +++ .../4 object library/subprojects/cmObjLib/libB.cpp | 6 +++ .../4 object library/subprojects/cmObjLib/libB.hpp | 5 +++ 9 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 test cases/cmake/4 object library/main.cpp create mode 100644 test cases/cmake/4 object library/meson.build create mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt create mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp create mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp create mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp create mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index ae42ae7..7073e94 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -20,11 +20,11 @@ from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCo from .. import mlog from ..build import Build from ..environment import Environment -from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode +from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode, MethodNode from ..backend.backends import Backend from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT -from typing import Dict +from typing import Dict, List import os, re CMAKE_BACKEND_GENERATOR_MAP = { @@ -50,6 +50,7 @@ CMAKE_TGT_TYPE_MAP = { 'MODULE_LIBRARY': 'shared_module', 'SHARED_LIBRARY': 'shared_library', 'EXECUTABLE': 'executable', + 'OBJECT_LIBRARY': 'static_library', } class ConverterTarget: @@ -75,6 +76,7 @@ class ConverterTarget: self.generated = [] self.includes = [] self.link_with = [] + self.object_libs = [] self.compile_opts = {} self.pie = False @@ -160,6 +162,17 @@ class ConverterTarget: if os.path.commonpath([self.install_dir, install_prefix]) == install_prefix: self.install_dir = os.path.relpath(self.install_dir, install_prefix) + def process_object_libs(self, obj_target_list: List['ConverterTarget']): + # Try to detect the object library(s) from the generated input sources + temp = [os.path.basename(x) for x in self.generated if x.endswith('.o')] + self.generated = [x for x in self.generated if not x.endswith('.o')] + for i in obj_target_list: + out_objects = [os.path.basename(x + '.o') for x in i.sources + i.generated] + for j in out_objects: + if j in temp: + self.object_libs += [i] + break + def meson_func(self) -> str: return CMAKE_TGT_TYPE_MAP.get(self.type.upper()) @@ -171,6 +184,7 @@ class ConverterTarget: mlog.log(' -- install_dir: ', mlog.bold(self.install_dir)) mlog.log(' -- link_libraries: ', mlog.bold(str(self.link_libraries))) mlog.log(' -- link_with: ', mlog.bold(str(self.link_with))) + mlog.log(' -- object_libs: ', mlog.bold(str(self.object_libs))) mlog.log(' -- link_flags: ', mlog.bold(str(self.link_flags))) mlog.log(' -- languages: ', mlog.bold(str(self.languages))) mlog.log(' -- includes: ', mlog.bold(str(self.includes))) @@ -297,11 +311,19 @@ class CMakeInterpreter: self.targets += [ConverterTarget(k)] output_target_map = {x.full_name: x for x in self.targets} + object_libs = [] + # First pass: Basic target cleanup for i in self.targets: i.postprocess(output_target_map, self.src_dir, self.install_prefix) + if i.type == 'OBJECT_LIBRARY': + object_libs += [i] self.languages += [x for x in i.languages if x not in self.languages] + # Second pass: Detect object library dependencies + for i in self.targets: + i.process_object_libs(object_libs) + mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets))), 'build targets.') def pretend_to_be_meson(self) -> CodeBlockNode: @@ -342,6 +364,14 @@ class CMakeInterpreter: func_n = FunctionNode(self.subdir, 0, 0, name, args_n) return func_n + def method(obj: BaseNode, name: str, args=[], kwargs={}) -> MethodNode: + args_n = ArgumentNode(token()) + if not isinstance(args, list): + args = [args] + args_n.arguments = [nodeify(x) for x in args] + args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()} + return MethodNode(self.subdir, 0, 0, obj, name, args_n) + def assign(var_name: str, value: BaseNode) -> AssignmentNode: return AssignmentNode(self.subdir, 0, 0, var_name, value) @@ -351,13 +381,19 @@ class CMakeInterpreter: processed = {} def process_target(tgt: ConverterTarget): - # First handle inter dependencies + # First handle inter target dependencies link_with = [] + objec_libs = [] for i in tgt.link_with: assert(isinstance(i, ConverterTarget)) if i.name not in processed: process_target(i) link_with += [id(processed[i.name]['tgt'])] + for i in tgt.object_libs: + assert(isinstance(i, ConverterTarget)) + if i.name not in processed: + process_target(i) + objec_libs += [processed[i.name]['tgt']] # Determine the meson function to use for the build target tgt_func = tgt.meson_func() @@ -379,6 +415,7 @@ class CMakeInterpreter: 'install': tgt.install, 'install_dir': tgt.install_dir, 'override_options': tgt.override_options, + 'objects': [method(id(x), 'extract_all_objects') for x in objec_libs], } # Handle compiler args diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 182326e..6d268c6 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2560,15 +2560,18 @@ external dependencies (including libraries) must go to "dependencies".''') with mlog.nested(): mlog.log('Processing generated meson AST') mlog.log() - mlog.log('=== BEGIN meson.build ===') + + # Debug print the generated meson file + mlog.debug('=== BEGIN meson.build ===') from .ast import AstIndentationGenerator, AstPrinter printer = AstPrinter() ast.accept(AstIndentationGenerator()) ast.accept(printer) printer.post_process() - mlog.log(printer.result) - mlog.log('=== END meson.build ===') - mlog.log() + mlog.debug(printer.result) + mlog.debug('=== END meson.build ===') + mlog.debug() + result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) mlog.log() diff --git a/test cases/cmake/4 object library/main.cpp b/test cases/cmake/4 object library/main.cpp new file mode 100644 index 0000000..f383608 --- /dev/null +++ b/test cases/cmake/4 object library/main.cpp @@ -0,0 +1,9 @@ +#include +#include "libA.hpp" +#include "libB.hpp" + +using namespace std; + +int main() { + cout << getLibStr() << " -- " << getZlibVers() << endl; +} diff --git a/test cases/cmake/4 object library/meson.build b/test cases/cmake/4 object library/meson.build new file mode 100644 index 0000000..05cd7f7 --- /dev/null +++ b/test cases/cmake/4 object library/meson.build @@ -0,0 +1,14 @@ +project('cmake_object_lib_test', ['cpp']) + +sub_pro = subproject('cmObjLib') +sub_sha = sub_pro.get_variable('lib_sha_dep') +sub_sta = sub_pro.get_variable('lib_sta_dep') + +# Required for the static library +zlib_dep = dependency('zlib') + +exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) +exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta, zlib_dep]) + +test('test1', exe_sha) +test('test1', exe_sta) diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt b/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt new file mode 100644 index 0000000..ee9be47 --- /dev/null +++ b/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.7) + +find_package(ZLIB REQUIRED) + +add_library(lib_obj OBJECT libA.cpp libB.cpp) +add_library(lib_sha SHARED $) +add_library(lib_sta STATIC $) + +target_link_libraries(lib_sha ZLIB::ZLIB) +target_link_libraries(lib_sta ZLIB::ZLIB) diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp new file mode 100644 index 0000000..3736b2c --- /dev/null +++ b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp @@ -0,0 +1,5 @@ +#include "libA.hpp" + +std::string getLibStr() { + return "Hello World"; +} diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp new file mode 100644 index 0000000..58c9413 --- /dev/null +++ b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getLibStr(); diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp new file mode 100644 index 0000000..b359c29 --- /dev/null +++ b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp @@ -0,0 +1,6 @@ +#include "libB.hpp" +#include + +std::string getZlibVers() { + return zlibVersion(); +} diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp new file mode 100644 index 0000000..71db6b7 --- /dev/null +++ b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getZlibVers(); -- cgit v1.1 From d114d8446eac10ad27f4b09c02e0049890a4b2a0 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 17:05:11 +0100 Subject: cmake: Minor fixes --- mesonbuild/cmake/interpreter.py | 37 ++++++++++++++++++++++++------------- mesonbuild/interpreter.py | 11 +++++------ 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 7073e94..1962e06 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -53,10 +53,13 @@ CMAKE_TGT_TYPE_MAP = { 'OBJECT_LIBRARY': 'static_library', } +CMAKE_TGT_SKIP = ['UTILITY'] + class ConverterTarget: lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()} - def __init__(self, target: CMakeTarget): + 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 @@ -110,7 +113,7 @@ class ConverterTarget: std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') - def postprocess(self, output_target_map: dict, root_src_dir: str, install_prefix: str) -> None: + def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: if not i in self.compile_opts: @@ -134,9 +137,11 @@ class ConverterTarget: # Let meson handle this arcane magic if ',-rpath,' in i: continue - if i in output_target_map: - self.link_with += [output_target_map[i]] - continue + if not os.path.isabs(i): + basename = os.path.basename(i) + if basename in output_target_map: + self.link_with += [output_target_map[basename]] + continue temp += [i] self.link_libraries = temp @@ -147,11 +152,14 @@ class ConverterTarget: x = os.path.normpath(os.path.join(self.src_dir, x)) if os.path.isabs(x) and os.path.commonpath([x, root_src_dir]) == root_src_dir: return os.path.relpath(x, root_src_dir) + if os.path.isabs(x) and os.path.commonpath([x, self.env.get_build_dir()]) == self.env.get_build_dir(): + return os.path.relpath(x, os.path.join(self.env.get_build_dir(), subdir)) return x - self.includes = [rel_path(x) for x in set(self.includes + [self.build_dir])] - self.sources = [rel_path(x) for x in self.sources] - self.generated = [rel_path(x) for x in self.generated] + build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir)) + self.includes = list(set([rel_path(x) for x in set(self.includes)] + [build_dir_rel])) + self.sources = [rel_path(x) for x in self.sources if not x.endswith('.rule')] + self.generated = [rel_path(x) for x in self.generated if not x.endswith('.rule')] # Make sure '.' is always in the include directories if '.' not in self.includes: @@ -197,12 +205,13 @@ class ConverterTarget: mlog.log(' -', key, '=', mlog.bold(str(val))) class CMakeInterpreter: - def __init__(self, build: Build, subdir: str, src_dir: str, build_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 self.src_dir = src_dir - self.build_dir = build_dir + self.build_dir_rel = os.path.join(subdir, '__CMake_build') + self.build_dir = os.path.join(env.get_build_dir(), self.build_dir_rel) self.install_prefix = install_prefix self.env = env self.backend_name = backend.name @@ -308,14 +317,15 @@ class CMakeInterpreter: if not self.project_name: self.project_name = j.name for k in j.targets: - self.targets += [ConverterTarget(k)] + if k.type not in CMAKE_TGT_SKIP: + self.targets += [ConverterTarget(k, self.env)] output_target_map = {x.full_name: x for x in self.targets} object_libs = [] # First pass: Basic target cleanup for i in self.targets: - i.postprocess(output_target_map, self.src_dir, self.install_prefix) + i.postprocess(output_target_map, self.src_dir, self.subdir, self.install_prefix) if i.type == 'OBJECT_LIBRARY': object_libs += [i] self.languages += [x for x in i.languages if x not in self.languages] @@ -401,7 +411,8 @@ class CMakeInterpreter: raise CMakeException('Unknown target type "{}"'.format(tgt.type)) # Determine the variable names - base_name = tgt.name + base_name = str(tgt.name) + base_name = base_name.replace('-', '_') inc_var = '{}_inc'.format(base_name) src_var = '{}_src'.format(base_name) dep_var = '{}_dep'.format(base_name) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 6d268c6..6df7ae1 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2546,10 +2546,9 @@ external dependencies (including libraries) must go to "dependencies".''') def do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): with mlog.nested(): - build_dir = os.path.join(self.environment.get_scratch_dir(), 'cmake_subp_{}'.format(dirname)) new_build = self.build.copy() prefix = self.coredata.builtins['prefix'].value - cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, build_dir, prefix, new_build.environment, self.backend) + cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend) cm_int.initialise() cm_int.analyse() @@ -2562,15 +2561,15 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log() # Debug print the generated meson file - mlog.debug('=== BEGIN meson.build ===') + mlog.log('=== BEGIN meson.build ===') from .ast import AstIndentationGenerator, AstPrinter printer = AstPrinter() ast.accept(AstIndentationGenerator()) ast.accept(printer) printer.post_process() - mlog.debug(printer.result) - mlog.debug('=== END meson.build ===') - mlog.debug() + mlog.log(printer.result) + mlog.log('=== END meson.build ===') + mlog.log() result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) -- cgit v1.1 From 3d7c50d1092bb6f00842f73248650e0af1266050 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 17:42:45 +0100 Subject: cmake: Added option for additional CMake args --- mesonbuild/cmake/interpreter.py | 11 +++++++---- mesonbuild/interpreter.py | 13 +++++++------ test cases/cmake/5 cmake options/meson.build | 3 +++ .../cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt | 5 +++++ 4 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 test cases/cmake/5 cmake options/meson.build create mode 100644 test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 1962e06..c0c0a44 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -226,7 +226,7 @@ class CMakeInterpreter: self.languages = [] self.targets = [] - def configure(self) -> None: + def configure(self, extra_cmake_options: List[str]) -> None: # Find CMake cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env) if cmake_exe is None or cmake_exe is False: @@ -249,7 +249,9 @@ class CMakeInterpreter: elif len(exelist) == 2: cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]), '-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])] - cmake_args += ['-G', generator, '-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] + cmake_args += ['-G', generator] + cmake_args += ['-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] + cmake_args += extra_cmake_options # Run CMake mlog.log() @@ -276,10 +278,10 @@ class CMakeInterpreter: if proc.returncode != 0: raise CMakeException('Failed to configure the CMake subproject') - def initialise(self) -> None: + def initialise(self, extra_cmake_options: List[str]) -> None: # Run configure the old way becuse doing it # with the server doesn't work for some reason - self.configure() + self.configure(extra_cmake_options) with self.client.connect(): generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] @@ -300,6 +302,7 @@ class CMakeInterpreter: 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 = [os.path.relpath(os.path.join(src_dir, x), self.env.get_source_dir()) for x in self.bs_files] + self.bs_files = list(set(self.bs_files)) self.codemodel = cm_reply def analyse(self) -> None: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 6df7ae1..efed755 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2028,7 +2028,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'both_libraries': known_library_kwargs, 'library': known_library_kwargs, 'subdir': {'if_found'}, - 'subproject': {'version', 'default_options', 'required', 'method'}, + 'subproject': {'version', 'default_options', 'required', 'method', 'cmake_options'}, 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite', 'protocol'}, 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, @@ -2548,8 +2548,9 @@ external dependencies (including libraries) must go to "dependencies".''') with mlog.nested(): new_build = self.build.copy() prefix = self.coredata.builtins['prefix'].value + cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', [])) cm_int = CMakeInterpreter(new_build, subdir, subdir_abs, prefix, new_build.environment, self.backend) - cm_int.initialise() + cm_int.initialise(cmake_options) cm_int.analyse() # Generate a meson ast and execute it with the normal do_subproject_meson @@ -2561,15 +2562,15 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log() # Debug print the generated meson file - mlog.log('=== BEGIN meson.build ===') + mlog.debug('=== BEGIN meson.build ===') from .ast import AstIndentationGenerator, AstPrinter printer = AstPrinter() ast.accept(AstIndentationGenerator()) ast.accept(printer) printer.post_process() - mlog.log(printer.result) - mlog.log('=== END meson.build ===') - mlog.log() + mlog.debug(printer.result) + mlog.debug('=== END meson.build ===') + mlog.debug() result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) diff --git a/test cases/cmake/5 cmake options/meson.build b/test cases/cmake/5 cmake options/meson.build new file mode 100644 index 0000000..3b58515 --- /dev/null +++ b/test cases/cmake/5 cmake options/meson.build @@ -0,0 +1,3 @@ +project('cmake_set_opt', ['c']) + +subproject('cmOpts', method: 'cmake', cmake_options: '-DSOME_CMAKE_VAR=something') diff --git a/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt b/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt new file mode 100644 index 0000000..62b5990 --- /dev/null +++ b/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.7) + +if(NOT "${SOME_CMAKE_VAR}" STREQUAL "something") + message(FATAL_ERROR "Setting the CMake var failed") +endif() -- cgit v1.1 From 6cb904de7b771f25580a17eff52c32ad0602452f Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sat, 23 Feb 2019 17:56:48 +0100 Subject: cmake: Added docs --- docs/markdown/Reference-manual.md | 5 ++ docs/markdown/Subprojects.md | 94 ++++++++++++++++++++--------- docs/markdown/snippets/cmake_subprojects.md | 34 +++++++++++ 3 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 docs/markdown/snippets/cmake_subprojects.md diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index f59d627..5662708 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1405,6 +1405,11 @@ arguments: - `version` keyword argument that works just like the one in `dependency`. It specifies what version the subproject should be, as an example `>=1.0.1` + - `method` *(added 0.50.0)* Specifies the configuration method of the + subproject. Possible values are `meson`, `cmake` and `auto`. With + `auto` meson will always prefer a `meson.build` in the subproject + over other methods. The default value of `method` is `auto`. + - `cmake_options` *(added 0.50.0)* List of additional CMake options - `required` *(added 0.48.0)* By default, `required` is `true` and Meson will abort if the subproject could not be setup. You can set this to `false` and then use the `.found()` method on the [returned diff --git a/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md index 2546441..21cb2cb 100644 --- a/docs/markdown/Subprojects.md +++ b/docs/markdown/Subprojects.md @@ -14,17 +14,16 @@ Meson tries to solve this problem by making it extremely easy to provide both at the same time. The way this is done is that Meson allows you to take any other Meson project and make it a part of your build without (in the best case) any changes to its Meson setup. It -becomes a transparent part of the project. +becomes a transparent part of the project. -It should be noted that this only works for subprojects that are built -with Meson. It can not be used with any other build system. The reason -is the simple fact that there is no possible way to do this reliably -with mixed build systems. +It should be noted that this is only guaranteed to work for subprojects +that are built with Meson. Using CMake based subprojects is not guaranteed +to work for all projects. ## A subproject example -Usually dependencies consist of some header files plus a library to link against. -To declare this internal dependency use `declare_dependency` function. +Usually dependencies consist of some header files plus a library to link against. +To declare this internal dependency use `declare_dependency` function. As an example, suppose we have a simple project that provides a shared library. Its `meson.build` would look like this. @@ -33,22 +32,22 @@ library. Its `meson.build` would look like this. project('libsimple', 'c') inc = include_directories('include') -libsimple = shared_library('simple', - 'simple.c', - include_directories : inc, +libsimple = shared_library('simple', + 'simple.c', + include_directories : inc, install : true) -libsimple_dep = declare_dependency(include_directories : inc, +libsimple_dep = declare_dependency(include_directories : inc, link_with : libsimple) ``` ### Naming convention for dependency variables -Ideally the dependency variable name should be of `_dep` form. +Ideally the dependency variable name should be of `_dep` form. This way one can just use it without even looking inside build definitions of that subproject. -In cases where there are multiple dependencies need to be declared, the default one -should be named as `_dep` (e.g. `gtest_dep`), and others can have +In cases where there are multiple dependencies need to be declared, the default one +should be named as `_dep` (e.g. `gtest_dep`), and others can have `___dep` form (e.g. `gtest_main_dep` - gtest with main function). There may be exceptions to these rules where common sense should be applied. @@ -65,16 +64,16 @@ as a subproject, use the `is_subproject` function. ## Using a subproject -All subprojects must be inside `subprojects` directory. -The `subprojects` directory must be at the top level of your project. -Subproject declaration must be in your top level `meson.build`. +All subprojects must be inside `subprojects` directory. +The `subprojects` directory must be at the top level of your project. +Subproject declaration must be in your top level `meson.build`. ### A simple example Let's use `libsimple` as a subproject. -At the top level of your project create `subprojects` directory. -Then copy `libsimple` into `subprojects` directory. +At the top level of your project create `subprojects` directory. +Then copy `libsimple` into `subprojects` directory. Your project's `meson.build` should look like this. @@ -84,9 +83,9 @@ project('my_project', 'cpp') libsimple_proj = subproject('libsimple') libsimple_dep = libsimple_proj.get_variable('libsimple_dep') -executable('my_project', - 'my_project.cpp', - dependencies : libsimple_dep, +executable('my_project', + 'my_project.cpp', + dependencies : libsimple_dep, install : true) ``` @@ -102,7 +101,7 @@ embed any sources. Some distros have a rule forbidding embedded dependencies so your project must be buildable without them or otherwise the packager will hate you. -Here's how you would use system libraries and fall back to embedding sources +Here's how you would use system libraries and fall back to embedding sources if the dependency is not available. ```meson @@ -115,9 +114,9 @@ if not libsimple_dep.found() libsimple_dep = libsimple_proj.get_variable('libsimple_dep') endif -executable('my_project', - 'my_project.cpp', - dependencies : libsimple_dep, +executable('my_project', + 'my_project.cpp', + dependencies : libsimple_dep, install : true) ``` @@ -141,14 +140,14 @@ project('my_project', 'cpp') libsimple_dep = dependency('libsimple', fallback : ['libsimple', 'libsimple_dep']) -executable('my_project', - 'my_project.cpp', - dependencies : libsimple_dep, +executable('my_project', + 'my_project.cpp', + dependencies : libsimple_dep, install : true) ``` With this setup when libsimple is provided by the system, we use it. When -that is not the case we use the embedded version (the one from subprojects). +that is not the case we use the embedded version (the one from subprojects). Note that `libsimple_dep` can point to an external or an internal dependency but you don't have to worry about their differences. Meson will take care @@ -161,6 +160,41 @@ in the top level `subprojects` directory. Recursive use of subprojects is not allowed, though, so you can't have subproject `a` that uses subproject `b` and have `b` also use `a`. +## CMake subprojects + +Meson is also able to use CMake subprojects directly. Using CMake +subprojects is almost identical to using the "normal" meson subprojects: + +```meson +sub_proj = subproject('libsimple_cmake', method : 'cmake') +``` + +The `method` key is optional if the subproject only has a `CMakeList.txt`. +Without specifying a method meson will always first try to find and use a +`meson.build` in the subproject. + +Project specific CMake options can be added with the `cmake_options` key. + +The returned `sub_proj` supports the same options as a "normal" subproject. +Meson automatically detects build targets, which can be retrieved with +`get_variable`. Meson also generates a dependency object for each target. + +These variable names are generated based on the CMake target name. + +```cmake +add_library(cm_exe SHARED ${SOURCES}) +``` + +For `cm_exe`, meson will then define the following variables: + +- `cm_exe` The raw library target (similar to `cm_exe = library('cm_exe', ...)` in meson) +- `cm_exe_dep` The dependency object for the target (similar to `declare_dependency()` in meson) +- `cm_exe_inc` A meson include directory object, containing all include irectories of the target. + +It should be noted that not all projects are guaranteed to work. The +safest approach would still be to create a `meson.build` for the +subprojects in question. + ## Obtaining subprojects Meson ships with a dependency system to automatically obtain diff --git a/docs/markdown/snippets/cmake_subprojects.md b/docs/markdown/snippets/cmake_subprojects.md new file mode 100644 index 0000000..94f68a1 --- /dev/null +++ b/docs/markdown/snippets/cmake_subprojects.md @@ -0,0 +1,34 @@ +## CMake subprojects + +Meson can now directly consume CMake based subprojects. Using CMake +subprojects is almost identical to using the "normal" meson subprojects: + +```meson +sub_proj = subproject('libsimple_cmake', method : 'cmake') +``` + +The `method` key is optional if the subproject only has a `CMakeList.txt`. +Without specifying a method meson will always first try to find and use a +`meson.build` in the subproject. + +Project specific CMake options can be added with the new `cmake_options` key. + +The returned `sub_proj` supports the same options as a "normal" subproject. +Meson automatically detects build targets, which can be retrieved with +`get_variable`. Meson also generates a dependency object for each target. + +These variable names are generated based on the CMake target name. + +```cmake +add_library(cm_exe SHARED ${SOURCES}) +``` + +For `cm_exe`, meson will then define the following variables: + +- `cm_exe` The raw library target (similar to `cm_exe = library('cm_exe', ...)` in meson) +- `cm_exe_dep` The dependency object for the target (similar to `declare_dependency()` in meson) +- `cm_exe_inc` A meson include directory object, containing all include irectories of the target. + +It should be noted that not all projects are guaranteed to work. The +safest approach would still be to create a `meson.build` for the +subprojects in question. -- cgit v1.1 From 4279255401a2fe8b24c20f49f4e5be0f8a6d56db Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:08:09 +0200 Subject: cmake: use newer CMake version for cygwin --- azure-pipelines.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3d04ffc..5ec62f1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -88,6 +88,7 @@ jobs: gccx64ninja: {} variables: CYGWIN_ROOT: $(System.Workfolder)\cygwin + CYGWIN_CMAKE_LINK: http://cygwin.mirror.constant.com/x86_64/release/cmake/cmake-3.13.1-1.tar.xz CYGWIN_MIRROR: http://cygwin.mirror.constant.com steps: - script: | @@ -95,21 +96,29 @@ jobs: displayName: Install Cygwin - script: | %CYGWIN_ROOT%\cygwinsetup.exe -qnNdO -R "%CYGWIN_ROOT%" -s "%CYGWIN_MIRROR%" -g -P ^ - cmake,^ gcc-fortran,^ gcc-objc++,^ gcc-objc,^ git,^ gobject-introspection,^ + libarchive13,^ libboost-devel,^ libglib2.0-devel,^ libgtk3-devel,^ + libjsoncpp19,^ + librhash0,^ + libuv1,^ ninja,^ python35-pip,^ vala,^ + wget,^ zlib-devel displayName: Install Dependencies - script: | + %CYGWIN_ROOT%\bin\bash.exe -cl "wget %CYGWIN_CMAKE_LINK% -O cmake.tar.xz" + %CYGWIN_ROOT%\bin\bash.exe -cl "tar -xf cmake.tar.xz -C /" + displayName: Manually install CMake 3.13.1 + - script: | set BOOST_ROOT= set PATH=%CYGWIN_ROOT%\bin;%SYSTEMROOT%\system32 cp /usr/bin/python3.5 /usr/bin/python3 -- cgit v1.1 From 7c4f46b65c6d9d8bf71c0eca3c75e5f8b5f73617 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:14:03 +0200 Subject: cmake: Fixed test cases for windows --- test cases/cmake/1 basic/meson.build | 2 +- .../cmake/1 basic/subprojects/cmMod/CMakeLists.txt | 4 ++++ .../cmake/1 basic/subprojects/cmMod/cmMod.hpp | 3 ++- test cases/cmake/2 advanced/meson.build | 7 ++++++- .../2 advanced/subprojects/cmMod/CMakeLists.txt | 3 +++ .../2 advanced/subprojects/cmMod/lib/cmMod.hpp | 3 ++- test cases/cmake/3 advanced no dep/main.cpp | 15 ++++++++++++++ test cases/cmake/3 advanced no dep/meson.build | 17 ++++++++++++++++ .../subprojects/cmMod/CMakeLists.txt | 19 ++++++++++++++++++ .../subprojects/cmMod/config.h.in | 3 +++ .../subprojects/cmMod/lib/cmMod.cpp | 16 +++++++++++++++ .../subprojects/cmMod/lib/cmMod.hpp | 13 ++++++++++++ .../3 advanced no dep/subprojects/cmMod/main.cpp | 10 ++++++++++ test cases/cmake/3 code gen/main.cpp | 8 -------- test cases/cmake/3 code gen/meson.build | 18 ----------------- .../subprojects/cmCodeGen/CMakeLists.txt | 5 ----- .../3 code gen/subprojects/cmCodeGen/main.cpp | 21 -------------------- test cases/cmake/3 code gen/test.hpp | 5 ----- test cases/cmake/4 code gen/main.cpp | 8 ++++++++ test cases/cmake/4 code gen/meson.build | 18 +++++++++++++++++ .../subprojects/cmCodeGen/CMakeLists.txt | 5 +++++ .../4 code gen/subprojects/cmCodeGen/main.cpp | 21 ++++++++++++++++++++ test cases/cmake/4 code gen/test.hpp | 5 +++++ test cases/cmake/4 object library/main.cpp | 9 --------- test cases/cmake/4 object library/meson.build | 14 ------------- .../subprojects/cmObjLib/CMakeLists.txt | 10 ---------- .../4 object library/subprojects/cmObjLib/libA.cpp | 5 ----- .../4 object library/subprojects/cmObjLib/libA.hpp | 5 ----- .../4 object library/subprojects/cmObjLib/libB.cpp | 6 ------ .../4 object library/subprojects/cmObjLib/libB.hpp | 5 ----- test cases/cmake/5 cmake options/meson.build | 3 --- .../subprojects/cmOpts/CMakeLists.txt | 5 ----- test cases/cmake/5 object library/main.cpp | 9 +++++++++ test cases/cmake/5 object library/meson.build | 23 ++++++++++++++++++++++ .../subprojects/cmObjLib/CMakeLists.txt | 10 ++++++++++ .../5 object library/subprojects/cmObjLib/libA.cpp | 5 +++++ .../5 object library/subprojects/cmObjLib/libA.hpp | 5 +++++ .../5 object library/subprojects/cmObjLib/libB.cpp | 6 ++++++ .../5 object library/subprojects/cmObjLib/libB.hpp | 5 +++++ test cases/cmake/6 object library no dep/main.cpp | 9 +++++++++ .../cmake/6 object library no dep/meson.build | 15 ++++++++++++++ .../subprojects/cmObjLib/CMakeLists.txt | 5 +++++ .../subprojects/cmObjLib/libA.cpp | 5 +++++ .../subprojects/cmObjLib/libA.hpp | 5 +++++ .../subprojects/cmObjLib/libB.cpp | 5 +++++ .../subprojects/cmObjLib/libB.hpp | 5 +++++ test cases/cmake/7 cmake options/meson.build | 3 +++ .../subprojects/cmOpts/CMakeLists.txt | 5 +++++ 48 files changed, 288 insertions(+), 123 deletions(-) create mode 100644 test cases/cmake/3 advanced no dep/main.cpp create mode 100644 test cases/cmake/3 advanced no dep/meson.build create mode 100644 test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt create mode 100644 test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in create mode 100644 test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp create mode 100644 test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp create mode 100644 test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp delete mode 100644 test cases/cmake/3 code gen/main.cpp delete mode 100644 test cases/cmake/3 code gen/meson.build delete mode 100644 test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt delete mode 100644 test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp delete mode 100644 test cases/cmake/3 code gen/test.hpp create mode 100644 test cases/cmake/4 code gen/main.cpp create mode 100644 test cases/cmake/4 code gen/meson.build create mode 100644 test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt create mode 100644 test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp create mode 100644 test cases/cmake/4 code gen/test.hpp delete mode 100644 test cases/cmake/4 object library/main.cpp delete mode 100644 test cases/cmake/4 object library/meson.build delete mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt delete mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp delete mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp delete mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp delete mode 100644 test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp delete mode 100644 test cases/cmake/5 cmake options/meson.build delete mode 100644 test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt create mode 100644 test cases/cmake/5 object library/main.cpp create mode 100644 test cases/cmake/5 object library/meson.build create mode 100644 test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt create mode 100644 test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp create mode 100644 test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp create mode 100644 test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp create mode 100644 test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp create mode 100644 test cases/cmake/6 object library no dep/main.cpp create mode 100644 test cases/cmake/6 object library no dep/meson.build create mode 100644 test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt create mode 100644 test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp create mode 100644 test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp create mode 100644 test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp create mode 100644 test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp create mode 100644 test cases/cmake/7 cmake options/meson.build create mode 100644 test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build index de54cce..eaa1e7a 100644 --- a/test cases/cmake/1 basic/meson.build +++ b/test cases/cmake/1 basic/meson.build @@ -1,4 +1,4 @@ -project('cmakeSubTest', ['cpp']) +project('cmakeSubTest', ['c', 'cpp']) sub_pro = subproject('cmMod', method: 'cmake') sub_dep = sub_pro.get_variable('cmModLib_dep') diff --git a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt index f27f636..7a9538b 100644 --- a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt @@ -3,4 +3,8 @@ cmake_minimum_required(VERSION 3.5) project(cmMod) set (CMAKE_CXX_STANDARD 14) +add_definitions("-DDO_NOTHING_JUST_A_FLAG=1") + add_library(cmModLib SHARED cmMod.cpp) +include(GenerateExportHeader) +generate_export_header(cmModLib) diff --git a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp index 29938dc..52f576b 100644 --- a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp +++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include "cmmodlib_export.h" -class cmModClass { +class CMMODLIB_EXPORT cmModClass { private: std::string str; public: diff --git a/test cases/cmake/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build index b3acc97..5f949f4 100644 --- a/test cases/cmake/2 advanced/meson.build +++ b/test cases/cmake/2 advanced/meson.build @@ -1,4 +1,9 @@ -project('cmakeSubTest_advanced', ['cpp']) +project('cmakeSubTest_advanced', ['c', 'cpp']) + +dep_test = dependency('ZLIB', method: 'cmake', required: false) +if not dep_test.found() + error('MESON_SKIP_TEST: zlib is not installed') +endif # Test the "normal" subproject call sub_pro = subproject('cmMod') diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt index 3f33e4d..14908a3 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt @@ -11,6 +11,9 @@ set(CONFIG_OPT 42) configure_file("config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) add_library(cmModLib SHARED lib/cmMod.cpp) +include(GenerateExportHeader) +generate_export_header(cmModLib) + add_executable(testEXE main.cpp) target_link_libraries(cmModLib ZLIB::ZLIB) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp index 29938dc..52f576b 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp @@ -1,8 +1,9 @@ #pragma once #include +#include "cmmodlib_export.h" -class cmModClass { +class CMMODLIB_EXPORT cmModClass { private: std::string str; public: diff --git a/test cases/cmake/3 advanced no dep/main.cpp b/test cases/cmake/3 advanced no dep/main.cpp new file mode 100644 index 0000000..6cc4c0c --- /dev/null +++ b/test cases/cmake/3 advanced no dep/main.cpp @@ -0,0 +1,15 @@ +#include +#include +#include "config.h" + +#if CONFIG_OPT != 42 +#error "Invalid value of CONFIG_OPT" +#endif + +using namespace std; + +int main() { + cmModClass obj("Hello"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/3 advanced no dep/meson.build b/test cases/cmake/3 advanced no dep/meson.build new file mode 100644 index 0000000..b4469f3 --- /dev/null +++ b/test cases/cmake/3 advanced no dep/meson.build @@ -0,0 +1,17 @@ +project('cmakeSubTest_advanced', ['c', 'cpp']) + +# Test the "normal" subproject call +sub_pro = subproject('cmMod') +sub_dep = sub_pro.get_variable('cmModLib_dep') + +# Test the dependency shortcut +shortcut_dep = dependency('ttabadbneiobevn', fallback: ['cmMod', 'cmModLib_dep']) + +# Build some files +exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep]) +exe2 = executable('main2', ['main.cpp'], dependencies: [shortcut_dep]) +test('test1', exe1) +test('test2', exe2) + +# Test if we can also extract executables +test('test3', sub_pro.get_variable('testEXE')) diff --git a/test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt new file mode 100644 index 0000000..57f0a64 --- /dev/null +++ b/test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmMod) +set(CMAKE_CXX_STANDARD 14) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/lib) + +set(CONFIG_OPT 42) +configure_file("config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) + +add_library(cmModLib SHARED lib/cmMod.cpp) +include(GenerateExportHeader) +generate_export_header(cmModLib) + +add_executable(testEXE main.cpp) + +target_link_libraries(testEXE cmModLib) + +install(TARGETS cmModLib testEXE LIBRARY DESTINATION lib RUNTIME DESTINATION bin) diff --git a/test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in b/test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in new file mode 100644 index 0000000..f538ac9 --- /dev/null +++ b/test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define CONFIG_OPT @CONFIG_OPT@ diff --git a/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp b/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp new file mode 100644 index 0000000..741e8df --- /dev/null +++ b/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp @@ -0,0 +1,16 @@ +#include "cmMod.hpp" +#include "config.h" + +#if CONFIG_OPT != 42 +#error "Invalid value of CONFIG_OPT" +#endif + +using namespace std; + +cmModClass::cmModClass(string foo) { + str = foo + " World"; +} + +string cmModClass::getStr() const { + return str; +} diff --git a/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp b/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp new file mode 100644 index 0000000..52f576b --- /dev/null +++ b/test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "cmmodlib_export.h" + +class CMMODLIB_EXPORT cmModClass { + private: + std::string str; + public: + cmModClass(std::string foo); + + std::string getStr() const; +}; diff --git a/test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp b/test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp new file mode 100644 index 0000000..cd21042 --- /dev/null +++ b/test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp @@ -0,0 +1,10 @@ +#include +#include "lib/cmMod.hpp" + +using namespace std; + +int main() { + cmModClass obj("Hello (LIB TEST)"); + cout << obj.getStr() << endl; + return 0; +} diff --git a/test cases/cmake/3 code gen/main.cpp b/test cases/cmake/3 code gen/main.cpp deleted file mode 100644 index 3ddbf63..0000000 --- a/test cases/cmake/3 code gen/main.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include "test.hpp" - -using namespace std; - -int main() { - cout << getStr() << endl; -} diff --git a/test cases/cmake/3 code gen/meson.build b/test cases/cmake/3 code gen/meson.build deleted file mode 100644 index daa93ce..0000000 --- a/test cases/cmake/3 code gen/meson.build +++ /dev/null @@ -1,18 +0,0 @@ -project('cmake_code_gen', ['cpp']) - -# Subproject with the "code generator" -sub_pro = subproject('cmCodeGen') -sub_exe = sub_pro.get_variable('genA') - -# Generate the source -generated = custom_target( - 'cmake-generated', - input: [], - output: ['test.cpp'], - command: [sub_exe, '@OUTPUT@'] -) - -# Build the exe -exe1 = executable('main1', ['main.cpp', generated]) - -test('test1', exe1) \ No newline at end of file diff --git a/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt b/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt deleted file mode 100644 index 268743c..0000000 --- a/test cases/cmake/3 code gen/subprojects/cmCodeGen/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.7) - -set(CMAKE_CXX_STANDARD 14) - -add_executable(genA main.cpp) diff --git a/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp b/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp deleted file mode 100644 index 5b7fed2..0000000 --- a/test cases/cmake/3 code gen/subprojects/cmCodeGen/main.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -using namespace std; - -int main(int argc, const char *argv[]) { - if(argc < 2) { - cerr << argv[0] << " requires an output file!" << endl; - return 1; - } - ofstream out(argv[1]); - out << R"( -#include "test.hpp" - -std::string getStr() { - return "Hello World"; -} -)"; - - return 0; -} diff --git a/test cases/cmake/3 code gen/test.hpp b/test cases/cmake/3 code gen/test.hpp deleted file mode 100644 index 8e25a0a..0000000 --- a/test cases/cmake/3 code gen/test.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -std::string getStr(); diff --git a/test cases/cmake/4 code gen/main.cpp b/test cases/cmake/4 code gen/main.cpp new file mode 100644 index 0000000..3ddbf63 --- /dev/null +++ b/test cases/cmake/4 code gen/main.cpp @@ -0,0 +1,8 @@ +#include +#include "test.hpp" + +using namespace std; + +int main() { + cout << getStr() << endl; +} diff --git a/test cases/cmake/4 code gen/meson.build b/test cases/cmake/4 code gen/meson.build new file mode 100644 index 0000000..a592dfb --- /dev/null +++ b/test cases/cmake/4 code gen/meson.build @@ -0,0 +1,18 @@ +project('cmake_code_gen', ['c', 'cpp']) + +# Subproject with the "code generator" +sub_pro = subproject('cmCodeGen') +sub_exe = sub_pro.get_variable('genA') + +# Generate the source +generated = custom_target( + 'cmake-generated', + input: [], + output: ['test.cpp'], + command: [sub_exe, '@OUTPUT@'] +) + +# Build the exe +exe1 = executable('main1', ['main.cpp', generated]) + +test('test1', exe1) diff --git a/test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt b/test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt new file mode 100644 index 0000000..268743c --- /dev/null +++ b/test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.7) + +set(CMAKE_CXX_STANDARD 14) + +add_executable(genA main.cpp) diff --git a/test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp b/test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp new file mode 100644 index 0000000..5b7fed2 --- /dev/null +++ b/test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp @@ -0,0 +1,21 @@ +#include +#include + +using namespace std; + +int main(int argc, const char *argv[]) { + if(argc < 2) { + cerr << argv[0] << " requires an output file!" << endl; + return 1; + } + ofstream out(argv[1]); + out << R"( +#include "test.hpp" + +std::string getStr() { + return "Hello World"; +} +)"; + + return 0; +} diff --git a/test cases/cmake/4 code gen/test.hpp b/test cases/cmake/4 code gen/test.hpp new file mode 100644 index 0000000..8e25a0a --- /dev/null +++ b/test cases/cmake/4 code gen/test.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getStr(); diff --git a/test cases/cmake/4 object library/main.cpp b/test cases/cmake/4 object library/main.cpp deleted file mode 100644 index f383608..0000000 --- a/test cases/cmake/4 object library/main.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include "libA.hpp" -#include "libB.hpp" - -using namespace std; - -int main() { - cout << getLibStr() << " -- " << getZlibVers() << endl; -} diff --git a/test cases/cmake/4 object library/meson.build b/test cases/cmake/4 object library/meson.build deleted file mode 100644 index 05cd7f7..0000000 --- a/test cases/cmake/4 object library/meson.build +++ /dev/null @@ -1,14 +0,0 @@ -project('cmake_object_lib_test', ['cpp']) - -sub_pro = subproject('cmObjLib') -sub_sha = sub_pro.get_variable('lib_sha_dep') -sub_sta = sub_pro.get_variable('lib_sta_dep') - -# Required for the static library -zlib_dep = dependency('zlib') - -exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) -exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta, zlib_dep]) - -test('test1', exe_sha) -test('test1', exe_sta) diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt b/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt deleted file mode 100644 index ee9be47..0000000 --- a/test cases/cmake/4 object library/subprojects/cmObjLib/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -cmake_minimum_required(VERSION 3.7) - -find_package(ZLIB REQUIRED) - -add_library(lib_obj OBJECT libA.cpp libB.cpp) -add_library(lib_sha SHARED $) -add_library(lib_sta STATIC $) - -target_link_libraries(lib_sha ZLIB::ZLIB) -target_link_libraries(lib_sta ZLIB::ZLIB) diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp deleted file mode 100644 index 3736b2c..0000000 --- a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "libA.hpp" - -std::string getLibStr() { - return "Hello World"; -} diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp deleted file mode 100644 index 58c9413..0000000 --- a/test cases/cmake/4 object library/subprojects/cmObjLib/libA.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -std::string getLibStr(); diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp deleted file mode 100644 index b359c29..0000000 --- a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "libB.hpp" -#include - -std::string getZlibVers() { - return zlibVersion(); -} diff --git a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp b/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp deleted file mode 100644 index 71db6b7..0000000 --- a/test cases/cmake/4 object library/subprojects/cmObjLib/libB.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -std::string getZlibVers(); diff --git a/test cases/cmake/5 cmake options/meson.build b/test cases/cmake/5 cmake options/meson.build deleted file mode 100644 index 3b58515..0000000 --- a/test cases/cmake/5 cmake options/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -project('cmake_set_opt', ['c']) - -subproject('cmOpts', method: 'cmake', cmake_options: '-DSOME_CMAKE_VAR=something') diff --git a/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt b/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt deleted file mode 100644 index 62b5990..0000000 --- a/test cases/cmake/5 cmake options/subprojects/cmOpts/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.7) - -if(NOT "${SOME_CMAKE_VAR}" STREQUAL "something") - message(FATAL_ERROR "Setting the CMake var failed") -endif() diff --git a/test cases/cmake/5 object library/main.cpp b/test cases/cmake/5 object library/main.cpp new file mode 100644 index 0000000..f383608 --- /dev/null +++ b/test cases/cmake/5 object library/main.cpp @@ -0,0 +1,9 @@ +#include +#include "libA.hpp" +#include "libB.hpp" + +using namespace std; + +int main() { + cout << getLibStr() << " -- " << getZlibVers() << endl; +} diff --git a/test cases/cmake/5 object library/meson.build b/test cases/cmake/5 object library/meson.build new file mode 100644 index 0000000..76e16be --- /dev/null +++ b/test cases/cmake/5 object library/meson.build @@ -0,0 +1,23 @@ +project('cmake_object_lib_test', ['c', 'cpp']) + +dep_test = dependency('ZLIB', method: 'cmake', required: false) +if not dep_test.found() + error('MESON_SKIP_TEST: zlib is not installed') +endif + +if build_machine.system() == 'windows' + error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems') +endif + +sub_pro = subproject('cmObjLib') +sub_sha = sub_pro.get_variable('lib_sha_dep') +sub_sta = sub_pro.get_variable('lib_sta_dep') + +# Required for the static library +zlib_dep = dependency('zlib') + +exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) +exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta, zlib_dep]) + +test('test1', exe_sha) +test('test1', exe_sta) diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt b/test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt new file mode 100644 index 0000000..ee9be47 --- /dev/null +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.7) + +find_package(ZLIB REQUIRED) + +add_library(lib_obj OBJECT libA.cpp libB.cpp) +add_library(lib_sha SHARED $) +add_library(lib_sta STATIC $) + +target_link_libraries(lib_sha ZLIB::ZLIB) +target_link_libraries(lib_sta ZLIB::ZLIB) diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp new file mode 100644 index 0000000..3736b2c --- /dev/null +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp @@ -0,0 +1,5 @@ +#include "libA.hpp" + +std::string getLibStr() { + return "Hello World"; +} diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp new file mode 100644 index 0000000..58c9413 --- /dev/null +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getLibStr(); diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp new file mode 100644 index 0000000..b359c29 --- /dev/null +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp @@ -0,0 +1,6 @@ +#include "libB.hpp" +#include + +std::string getZlibVers() { + return zlibVersion(); +} diff --git a/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp new file mode 100644 index 0000000..71db6b7 --- /dev/null +++ b/test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getZlibVers(); diff --git a/test cases/cmake/6 object library no dep/main.cpp b/test cases/cmake/6 object library no dep/main.cpp new file mode 100644 index 0000000..f383608 --- /dev/null +++ b/test cases/cmake/6 object library no dep/main.cpp @@ -0,0 +1,9 @@ +#include +#include "libA.hpp" +#include "libB.hpp" + +using namespace std; + +int main() { + cout << getLibStr() << " -- " << getZlibVers() << endl; +} diff --git a/test cases/cmake/6 object library no dep/meson.build b/test cases/cmake/6 object library no dep/meson.build new file mode 100644 index 0000000..8e1eaf9 --- /dev/null +++ b/test cases/cmake/6 object library no dep/meson.build @@ -0,0 +1,15 @@ +project('cmake_object_lib_test', ['c', 'cpp']) + +if build_machine.system() == 'windows' + error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems') +endif + +sub_pro = subproject('cmObjLib') +sub_sha = sub_pro.get_variable('lib_sha_dep') +sub_sta = sub_pro.get_variable('lib_sta_dep') + +exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) +exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta]) + +test('test1', exe_sha) +test('test1', exe_sta) diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt new file mode 100644 index 0000000..08c6a18 --- /dev/null +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.7) + +add_library(lib_obj OBJECT libA.cpp libB.cpp) +add_library(lib_sha SHARED $) +add_library(lib_sta STATIC $) diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp new file mode 100644 index 0000000..3736b2c --- /dev/null +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp @@ -0,0 +1,5 @@ +#include "libA.hpp" + +std::string getLibStr() { + return "Hello World"; +} diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp new file mode 100644 index 0000000..58c9413 --- /dev/null +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getLibStr(); diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp new file mode 100644 index 0000000..187d10f --- /dev/null +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp @@ -0,0 +1,5 @@ +#include "libB.hpp" + +std::string getZlibVers() { + return "STUB"; +} diff --git a/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp new file mode 100644 index 0000000..71db6b7 --- /dev/null +++ b/test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string getZlibVers(); diff --git a/test cases/cmake/7 cmake options/meson.build b/test cases/cmake/7 cmake options/meson.build new file mode 100644 index 0000000..55813fb --- /dev/null +++ b/test cases/cmake/7 cmake options/meson.build @@ -0,0 +1,3 @@ +project('cmake_set_opt', ['c', 'cpp']) + +subproject('cmOpts', method: 'cmake', cmake_options: '-DSOME_CMAKE_VAR=something') diff --git a/test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt b/test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt new file mode 100644 index 0000000..62b5990 --- /dev/null +++ b/test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.7) + +if(NOT "${SOME_CMAKE_VAR}" STREQUAL "something") + message(FATAL_ERROR "Setting the CMake var failed") +endif() -- cgit v1.1 From cb57847c30ce49d03e2b98f8466b11acf3cf21fd Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:14:50 +0200 Subject: cmake: fixed installed_files.txt --- run_project_tests.py | 5 +++++ test cases/cmake/2 advanced/installed_files.txt | 6 ++++-- test cases/cmake/3 advanced no dep/installed_files.txt | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test cases/cmake/3 advanced no dep/installed_files.txt diff --git a/run_project_tests.py b/run_project_tests.py index c3187ff..f4b4a96 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -161,6 +161,11 @@ def platform_fix_name(fname, compiler, env): if not mesonlib.for_cygwin(env.is_cross_build(), env): return None + if fname.startswith('?!cygwin:'): + fname = fname[9:] + if mesonlib.for_cygwin(env.is_cross_build(), env): + return None + if fname.endswith('?so'): if mesonlib.for_windows(env.is_cross_build(), env) and canonical_compiler == 'msvc': fname = re.sub(r'lib/([^/]*)\?so$', r'bin/\1.dll', fname) diff --git a/test cases/cmake/2 advanced/installed_files.txt b/test cases/cmake/2 advanced/installed_files.txt index 40235a5..8cbdcd7 100644 --- a/test cases/cmake/2 advanced/installed_files.txt +++ b/test cases/cmake/2 advanced/installed_files.txt @@ -1,2 +1,4 @@ -usr/lib/libcmModLib.so -usr/bin/testEXE \ No newline at end of file +usr/?lib/libcmModLib?so +?cygwin:usr/lib/libcmModLib?implib +?!cygwin:usr/bin/libcmModLib?implib +usr/bin/testEXE?exe \ No newline at end of file diff --git a/test cases/cmake/3 advanced no dep/installed_files.txt b/test cases/cmake/3 advanced no dep/installed_files.txt new file mode 100644 index 0000000..e16e27d --- /dev/null +++ b/test cases/cmake/3 advanced no dep/installed_files.txt @@ -0,0 +1,6 @@ +usr/?lib/libcmModLib?so +?cygwin:usr/lib/libcmModLib?implib +?!cygwin:usr/bin/libcmModLib?implib +?msvc:usr/bin/cmModLib.pdb +?msvc:usr/bin/testEXE.pdb +usr/bin/testEXE?exe \ No newline at end of file -- cgit v1.1 From 493e505d58f02d7ea63a8a41978adb0ecb314091 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:18:47 +0200 Subject: cmake: better logging --- ci/azure-steps.yml | 5 +++++ run_project_tests.py | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index abbed65..77f61fe 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -155,6 +155,11 @@ steps: python --version echo "" + echo "Locating cl, rc:" + where.exe cl + where.exe rc + + echo "" echo "=== Start running tests ===" # Starting from VS2019 Powershell(?) will fail the test run # if it prints anything to stderr. Python's test runner diff --git a/run_project_tests.py b/run_project_tests.py index f4b4a96..268e254 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -227,6 +227,10 @@ def validate_install(srcdir, installdir, compiler, env): for fname in found: if fname not in expected: ret_msg += 'Extra file {0} found.\n'.format(fname) + if ret_msg != '': + ret_msg += '\nInstall dir contents:\n' + for i in found: + ret_msg += ' - {}'.format(i) return ret_msg def log_text_file(logfile, testdir, stdo, stde): @@ -670,6 +674,12 @@ def _run_tests(all_tests, log_name_base, failfast, extra_args): # print the meson log if available since it's a superset # of stdout and often has very useful information. failing_logs.append(result.mlog) + elif under_ci: + # Always print the complete meson log when running in + # a CI. This helps debugging issues that only occur in + # a hard to reproduce environment + failing_logs.append(result.mlog) + failing_logs.append(result.stdo) else: failing_logs.append(result.stdo) failing_logs.append(result.stde) -- cgit v1.1 From 5c603a900b004458a9a6816cb663316954c51a74 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:23:59 +0200 Subject: cmake: Some minor style changes --- mesonbuild/cmake/interpreter.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index c0c0a44..f0a971f 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -24,10 +24,10 @@ from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, A from ..backend.backends import Backend from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT -from typing import Dict, List +from typing import List import os, re -CMAKE_BACKEND_GENERATOR_MAP = { +backend_generator_map = { 'ninja': 'Ninja', 'xcode': 'Xcode', 'vs2010': 'Visual Studio 10 2010', @@ -35,7 +35,7 @@ CMAKE_BACKEND_GENERATOR_MAP = { 'vs2017': 'Visual Studio 15 2017', } -CMAKE_LANGUAGE_MAP = { +language_map = { 'c': 'C', 'cpp': 'CXX', 'cuda': 'CUDA', @@ -45,7 +45,7 @@ CMAKE_LANGUAGE_MAP = { 'swift': 'Swift', } -CMAKE_TGT_TYPE_MAP = { +target_type_map = { 'STATIC_LIBRARY': 'static_library', 'MODULE_LIBRARY': 'shared_module', 'SHARED_LIBRARY': 'shared_library', @@ -53,10 +53,12 @@ CMAKE_TGT_TYPE_MAP = { 'OBJECT_LIBRARY': 'static_library', } -CMAKE_TGT_SKIP = ['UTILITY'] +skip_targets = ['UTILITY'] + +skip_input_extensions = ['.rule'] class ConverterTarget: - lang_cmake_to_meson = {val.lower(): key for key, val in CMAKE_LANGUAGE_MAP.items()} + lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} def __init__(self, target: CMakeTarget, env: Environment): self.env = env @@ -116,7 +118,7 @@ class ConverterTarget: def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None: # Detect setting the C and C++ standard for i in ['c', 'cpp']: - if not i in self.compile_opts: + if i not in self.compile_opts: continue temp = [] @@ -158,8 +160,12 @@ class ConverterTarget: build_dir_rel = os.path.relpath(self.build_dir, os.path.join(self.env.get_build_dir(), subdir)) self.includes = list(set([rel_path(x) for x in set(self.includes)] + [build_dir_rel])) - self.sources = [rel_path(x) for x in self.sources if not x.endswith('.rule')] - self.generated = [rel_path(x) for x in self.generated if not x.endswith('.rule')] + self.sources = [rel_path(x) for x in self.sources] + self.generated = [rel_path(x) for x in self.generated] + + # Filter out CMake rule files + self.sources = [x for x in self.sources if not any([x.endswith(y) for y in skip_input_extensions])] + self.generated = [x for x in self.generated if not any([x.endswith(y) for y in skip_input_extensions])] # Make sure '.' is always in the include directories if '.' not in self.includes: @@ -182,7 +188,7 @@ class ConverterTarget: break def meson_func(self) -> str: - return CMAKE_TGT_TYPE_MAP.get(self.type.upper()) + return target_type_map.get(self.type.upper()) def log(self) -> None: mlog.log('Target', mlog.bold(self.name)) @@ -235,14 +241,14 @@ class CMakeInterpreter: if not cmake_exe.found(): raise CMakeException('Unable to find CMake') - generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] + generator = backend_generator_map[self.backend_name] cmake_args = cmake_exe.get_command() # Map meson compiler to CMake variables for lang, comp in self.build.compilers.items(): - if lang not in CMAKE_LANGUAGE_MAP: + if lang not in language_map: continue - cmake_lang = CMAKE_LANGUAGE_MAP[lang] + cmake_lang = language_map[lang] exelist = comp.get_exelist() if len(exelist) == 1: cmake_args += ['-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[0])] @@ -284,7 +290,7 @@ class CMakeInterpreter: self.configure(extra_cmake_options) with self.client.connect(): - generator = CMAKE_BACKEND_GENERATOR_MAP[self.backend_name] + 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 @@ -300,7 +306,7 @@ class CMakeInterpreter: cm_reply = self.client.query_checked(RequestCodeModel(), 'Querying the CMake code model') src_dir = bs_reply.src_dir - self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] + self.bs_files = [x.file for x in bs_reply.build_files if not x.is_cmake and not x.is_temp] self.bs_files = [os.path.relpath(os.path.join(src_dir, x), self.env.get_source_dir()) for x in self.bs_files] self.bs_files = list(set(self.bs_files)) self.codemodel = cm_reply @@ -320,7 +326,7 @@ class CMakeInterpreter: if not self.project_name: self.project_name = j.name for k in j.targets: - if k.type not in CMAKE_TGT_SKIP: + if k.type not in skip_targets: self.targets += [ConverterTarget(k, self.env)] output_target_map = {x.full_name: x for x in self.targets} @@ -391,8 +397,8 @@ class CMakeInterpreter: # Generate the root code block and the project function call root_cb = CodeBlockNode(token()) root_cb.lines += [function('project', [self.project_name] + self.languages)] - processed = {} + def process_target(tgt: ConverterTarget): # First handle inter target dependencies link_with = [] -- cgit v1.1 From 1f3a57598e344eacaff1052403bd48ab9dd57ed5 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:26:15 +0200 Subject: cmake: general windows and old CMake fixes --- mesonbuild/cmake/client.py | 18 ++++++++++++++---- mesonbuild/cmake/interpreter.py | 28 ++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index ae572f9..4375eb9 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -23,6 +23,7 @@ from contextlib import contextmanager from subprocess import Popen, PIPE, TimeoutExpired from typing import List, Optional import json +import os CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==[' CMAKE_SERVER_END_STR = ']== "CMake Server" ==]' @@ -134,10 +135,15 @@ class RequestHandShake(RequestBase): 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 + src_list = os.path.normpath(self.src_dir).split(os.sep) + bld_list = os.path.normpath(self.build_dir).split(os.sep) + return { **super().to_dict(), - 'sourceDirectory': self.src_dir, - 'buildDirectory': self.build_dir, + 'sourceDirectory': '/'.join(src_list), + 'buildDirectory': '/'.join(bld_list), 'generator': self.generator, 'protocolVersion': vers } @@ -204,19 +210,23 @@ class ReplyCMakeInputs(ReplyBase): mlog.log(str(i)) def _flags_to_list(raw: str) -> List[str]: + # Convert a raw commandline string into a list of strings res = [] curr = '' escape = False in_string = False for i in raw: if escape: + # If the current char is not a quote, the '\' is probably important + if i not in ['"', "'"]: + curr += '\\' curr += i escape = False elif i == '\\': escape = True - elif i == '"' or i == "'": + elif i in ['"', "'"]: in_string = not in_string - elif i == ' ' or i == '\n': + elif i in [' ', '\n']: if in_string: curr += i else: diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index f0a971f..43ddc83 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -22,6 +22,7 @@ from ..build import Build from ..environment import Environment from ..mparser import Token, BaseNode, CodeBlockNode, FunctionNode, ArrayNode, ArgumentNode, AssignmentNode, BooleanNode, StringNode, IdNode, MethodNode from ..backend.backends import Backend +from ..compilers.compilers import obj_suffixes from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT from typing import List @@ -113,7 +114,7 @@ class ConverterTarget: def __repr__(self) -> str: return '<{}: {}>'.format(self.__class__.__name__, self.name) - std_regex = re.compile(r'([-]{1,2}std=|/std:v?)(.*)') + std_regex = re.compile(r'([-]{1,2}std=|/std:v?|[-]{1,2}std:)(.*)') def postprocess(self, output_target_map: dict, root_src_dir: str, subdir: str, install_prefix: str) -> None: # Detect setting the C and C++ standard @@ -133,6 +134,10 @@ class ConverterTarget: self.compile_opts[i] = temp + # Make sure to force enable -fPIC for OBJECT libraries + if self.type.upper() == 'OBJECT_LIBRARY': + self.pie = True + # Fix link libraries temp = [] for i in self.link_libraries: @@ -178,20 +183,26 @@ class ConverterTarget: def process_object_libs(self, obj_target_list: List['ConverterTarget']): # Try to detect the object library(s) from the generated input sources - temp = [os.path.basename(x) for x in self.generated if x.endswith('.o')] - self.generated = [x for x in self.generated if not x.endswith('.o')] + temp = [os.path.basename(x) for x in self.generated] + temp = [x for x in temp if any([x.endswith('.' + y) for y in obj_suffixes])] + temp = [os.path.splitext(x)[0] for x in temp] + # Temp now stores the source filenames of the object files for i in obj_target_list: - out_objects = [os.path.basename(x + '.o') for x in i.sources + i.generated] - for j in out_objects: + source_files = [os.path.basename(x) for x in i.sources + i.generated] + for j in source_files: if j in temp: self.object_libs += [i] break + # Filter out object files from the sources + self.generated = [x for x in self.generated if not any([x.endswith('.' + y) for y in obj_suffixes])] + def meson_func(self) -> str: return target_type_map.get(self.type.upper()) def log(self) -> None: mlog.log('Target', mlog.bold(self.name)) + mlog.log(' -- artifacts: ', mlog.bold(str(self.artifacts))) mlog.log(' -- full_name: ', mlog.bold(self.full_name)) mlog.log(' -- type: ', mlog.bold(self.type)) mlog.log(' -- install: ', mlog.bold('true' if self.install else 'false')) @@ -266,7 +277,9 @@ class CMakeInterpreter: mlog.log(mlog.bold('Running:'), ' '.join(cmake_args)) mlog.log() os.makedirs(self.build_dir, exist_ok=True) - proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=STDOUT, cwd=self.build_dir) + os_env = os.environ.copy() + os_env['LC_ALL'] = 'C' + proc = Popen(cmake_args + [self.src_dir], stdout=PIPE, stderr=STDOUT, cwd=self.build_dir, env=os_env) # Print CMake log in realtime while True: @@ -330,6 +343,9 @@ class CMakeInterpreter: self.targets += [ConverterTarget(k, self.env)] output_target_map = {x.full_name: x for x in self.targets} + for i in self.targets: + for j in i.artifacts: + output_target_map[os.path.basename(j)] = i object_libs = [] # First pass: Basic target cleanup -- cgit v1.1 From 29a26c9dbd4aec55fedac9f885f8558ca41fe341 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:31:45 +0200 Subject: cmake: blacklist some `cl` compiler and linker flags --- mesonbuild/cmake/interpreter.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 43ddc83..e1ba7eb 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -58,6 +58,33 @@ skip_targets = ['UTILITY'] skip_input_extensions = ['.rule'] +blacklist_compiler_flags = [ + '/W1', '/W2', '/W3', '/W4', '/Wall', + '/O1', '/O2', '/Ob', '/Od', '/Og', '/Oi', '/Os', '/Ot', '/Ox', '/Oy', '/Ob0', + '/RTC1', '/RTCc', '/RTCs', '/RTCu' +] + +blacklist_link_flags = [ + '/machine:x64', '/machine:x86', '/machine:arm', '/machine:ebc', + '/debug', '/debug:fastlink', '/debug:full', '/debug:none', + '/incremental', +] + +blacklist_clang_cl_link_flags = ['/GR', '/EHsc', '/MDd', '/Zi', '/RTC1'] + +blacklist_link_libs = [ + 'kernel32.lib', + 'user32.lib', + 'gdi32.lib', + 'winspool.lib', + 'shell32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'comdlg32.lib', + 'advapi32.lib' +] + class ConverterTarget: lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} @@ -129,6 +156,8 @@ class ConverterTarget: self.override_options += ['{}_std={}'.format(i, m.group(2))] elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']: self.pie = True + elif j in blacklist_compiler_flags: + pass else: temp += [j] @@ -181,6 +210,17 @@ class ConverterTarget: if os.path.commonpath([self.install_dir, install_prefix]) == install_prefix: self.install_dir = os.path.relpath(self.install_dir, install_prefix) + # Remove blacklisted options and libs + def check_flag(flag: str) -> bool: + if flag.lower() in blacklist_link_flags or flag in blacklist_compiler_flags + blacklist_clang_cl_link_flags: + return False + if flag.startswith('/D'): + return False + return True + + self.link_libraries = [x for x in self.link_libraries if x.lower() not in blacklist_link_libs] + self.link_flags = [x for x in self.link_flags if check_flag(x)] + def process_object_libs(self, obj_target_list: List['ConverterTarget']): # Try to detect the object library(s) from the generated input sources temp = [os.path.basename(x) for x in self.generated] -- cgit v1.1 From 9b22147dcb0c31cc374ebf6b059f225530401a51 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:32:33 +0200 Subject: cmake: set the linker for clang-cl for CMake --- mesonbuild/cmake/interpreter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index e1ba7eb..da14a06 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -306,6 +306,8 @@ class CMakeInterpreter: elif len(exelist) == 2: cmake_args += ['-DCMAKE_{}_COMPILER_LAUNCHER={}'.format(cmake_lang, exelist[0]), '-DCMAKE_{}_COMPILER={}'.format(cmake_lang, exelist[1])] + if hasattr(comp, 'get_linker_exelist') and comp.get_id() == 'clang-cl': + cmake_args += ['-DCMAKE_LINKER={}'.format(comp.get_linker_exelist()[0])] cmake_args += ['-G', generator] cmake_args += ['-DCMAKE_INSTALL_PREFIX={}'.format(self.install_prefix)] cmake_args += extra_cmake_options -- cgit v1.1 From 35936316eb87f2930a3873da71eaa1c9f11e3186 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:34:00 +0200 Subject: cmake: rebase fixes --- docs/markdown/Reference-manual.md | 8 ++++---- mesonbuild/cmake/interpreter.py | 5 +++-- mesonbuild/interpreter.py | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 5662708..68c8f9d 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1082,7 +1082,7 @@ res2 = foo / bar ``` Builds a library that is either static, shared or both depending on -the value of `default_library` +the value of `default_library` user [option](https://mesonbuild.com/Builtin-options.html). You should use this instead of [`shared_library`](#shared_library), [`static_library`](#static_library) or @@ -1405,11 +1405,11 @@ arguments: - `version` keyword argument that works just like the one in `dependency`. It specifies what version the subproject should be, as an example `>=1.0.1` - - `method` *(added 0.50.0)* Specifies the configuration method of the + - `method` *(added 0.51.0)* Specifies the configuration method of the subproject. Possible values are `meson`, `cmake` and `auto`. With `auto` meson will always prefer a `meson.build` in the subproject over other methods. The default value of `method` is `auto`. - - `cmake_options` *(added 0.50.0)* List of additional CMake options + - `cmake_options` *(added 0.51.0)* List of additional CMake options - `required` *(added 0.48.0)* By default, `required` is `true` and Meson will abort if the subproject could not be setup. You can set this to `false` and then use the `.found()` method on the [returned @@ -2180,7 +2180,7 @@ an external dependency with the following methods: dep3 will add `['-Werror=foo', '-Werror=bar']` to the compiler args of any target it is added to, but libfoo will not be added to the link_args. - + *Note*: A bug present until 0.50.1 results in the above behavior not working correctly. diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index da14a06..488c99c 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -34,6 +34,7 @@ backend_generator_map = { 'vs2010': 'Visual Studio 10 2010', 'vs2015': 'Visual Studio 15 2017', 'vs2017': 'Visual Studio 15 2017', + 'vs2019': 'Visual Studio 16 2019', } language_map = { @@ -430,7 +431,7 @@ class CMakeInterpreter: if not isinstance(elements, list): elements = [args] args.arguments += [nodeify(x) for x in elements] - return ArrayNode(args, 0, 0) + return ArrayNode(args, 0, 0, 0, 0) def function(name: str, args=[], kwargs={}) -> FunctionNode: args_n = ArgumentNode(token()) @@ -438,7 +439,7 @@ class CMakeInterpreter: args = [args] args_n.arguments = [nodeify(x) for x in args] args_n.kwargs = {k: nodeify(v) for k, v in kwargs.items()} - func_n = FunctionNode(self.subdir, 0, 0, name, args_n) + func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n) return func_n def method(obj: BaseNode, name: str, args=[], kwargs={}) -> MethodNode: diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index efed755..0e50202 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2416,7 +2416,7 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) - @FeatureNewKwargs('subproject', '0.50.0', ['method']) + @FeatureNewKwargs('subproject', '0.51.0', ['method', 'cmake_options']) @permittedKwargs(permitted_kwargs['subproject']) @stringArgs def func_subproject(self, nodes, args, kwargs): @@ -2537,9 +2537,9 @@ external dependencies (including libraries) must go to "dependencies".''') self.subprojects[dirname] = SubprojectHolder(subi, self.subproject_dir, dirname) # Duplicates are possible when subproject uses files from project root if build_def_files: - self.build_def_files += list(set(self.build_def_files + build_def_files)) + self.build_def_files = list(set(self.build_def_files + build_def_files)) else: - self.build_def_files += list(set(self.build_def_files + subi.build_def_files)) + self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) self.build.merge(subi.build) self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] -- cgit v1.1 From 703054903b8e269e19b69f48fd58b472ecb13883 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Fri, 19 Apr 2019 10:34:21 +0200 Subject: cmake: disable the CMake tests for msvc2015 --- run_project_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_project_tests.py b/run_project_tests.py index 268e254..f6d83b5 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -562,7 +562,7 @@ def skip_csharp(backend): def detect_tests_to_run(): # Name, subdirectory, skip condition. all_tests = [ - ('cmake', 'cmake', not shutil.which('cmake')), + ('cmake', 'cmake', not shutil.which('cmake') or (os.environ.get('compiler') == 'msvc2015' and under_ci)), ('common', 'common', False), ('warning-meson', 'warning', False), ('failing-meson', 'failing', False), -- cgit v1.1 From af6448ced5955f8fb43aa667ce8065d50c104fba Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Sun, 5 May 2019 21:58:25 +0200 Subject: cmake: Make flake8 happy --- mesonbuild/cmake/client.py | 18 +++++++++--------- mesonbuild/cmake/interpreter.py | 26 +++++++++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 4375eb9..4581d6d 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -48,8 +48,8 @@ CMAKE_REPLY_TYPES = { # Base CMake server message classes class MessageBase: - def __init__(self, type: str, cookie: str): - self.type = type + def __init__(self, msg_type: str, cookie: str): + self.type = msg_type self.cookie = cookie def to_dict(self) -> dict: @@ -61,8 +61,8 @@ class MessageBase: class RequestBase(MessageBase): cookie_counter = 0 - def __init__(self, type: str): - super().__init__(type, self.gen_cookie()) + def __init__(self, msg_type: str): + super().__init__(msg_type, self.gen_cookie()) @staticmethod def gen_cookie(): @@ -404,13 +404,13 @@ class CMakeClient: raw_data = self.readMessageRaw() if 'type' not in raw_data: raise CMakeException('The "type" attribute is missing from the message') - type = raw_data['type'] - func = self.type_map.get(type, None) + msg_type = raw_data['type'] + func = self.type_map.get(msg_type, None) if not func: - raise CMakeException('Recieved unknown message type "{}"'.format(type)) - for i in CMAKE_MESSAGE_TYPES[type]: + raise CMakeException('Recieved unknown message type "{}"'.format(msg_type)) + for i in CMAKE_MESSAGE_TYPES[msg_type]: if i not in raw_data: - raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, type)) + raise CMakeException('Key "{}" is missing from CMake server message type {}'.format(i, msg_type)) return func(raw_data) def writeMessage(self, msg: MessageBase) -> None: diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 488c99c..7782555 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -414,7 +414,7 @@ class CMakeInterpreter: def string(value: str) -> StringNode: return StringNode(token(val=value)) - def id(value: str) -> IdNode: + def id_node(value: str) -> IdNode: return IdNode(token(val=value)) def nodeify(value): @@ -433,7 +433,11 @@ class CMakeInterpreter: args.arguments += [nodeify(x) for x in elements] return ArrayNode(args, 0, 0, 0, 0) - def function(name: str, args=[], kwargs={}) -> FunctionNode: + def function(name: str, args=None, kwargs=None) -> FunctionNode: + if args is None: + args = [] + if kwargs is None: + kwargs = {} args_n = ArgumentNode(token()) if not isinstance(args, list): args = [args] @@ -442,7 +446,11 @@ class CMakeInterpreter: func_n = FunctionNode(self.subdir, 0, 0, 0, 0, name, args_n) return func_n - def method(obj: BaseNode, name: str, args=[], kwargs={}) -> MethodNode: + def method(obj: BaseNode, name: str, args=None, kwargs=None) -> MethodNode: + if args is None: + args = [] + if kwargs is None: + kwargs = {} args_n = ArgumentNode(token()) if not isinstance(args, list): args = [args] @@ -466,7 +474,7 @@ class CMakeInterpreter: assert(isinstance(i, ConverterTarget)) if i.name not in processed: process_target(i) - link_with += [id(processed[i.name]['tgt'])] + link_with += [id_node(processed[i.name]['tgt'])] for i in tgt.object_libs: assert(isinstance(i, ConverterTarget)) if i.name not in processed: @@ -490,11 +498,11 @@ class CMakeInterpreter: tgt_kwargs = { 'link_args': tgt.link_flags + tgt.link_libraries, 'link_with': link_with, - 'include_directories': id(inc_var), + 'include_directories': id_node(inc_var), 'install': tgt.install, 'install_dir': tgt.install_dir, 'override_options': tgt.override_options, - 'objects': [method(id(x), 'extract_all_objects') for x in objec_libs], + 'objects': [method(id_node(x), 'extract_all_objects') for x in objec_libs], } # Handle compiler args @@ -510,14 +518,14 @@ class CMakeInterpreter: # declare_dependency kwargs dep_kwargs = { 'link_args': tgt.link_flags + tgt.link_libraries, - 'link_with': id(tgt_var), - 'include_directories': id(inc_var), + 'link_with': id_node(tgt_var), + 'include_directories': id_node(inc_var), } # Generate the function nodes inc_node = assign(inc_var, function('include_directories', tgt.includes)) src_node = assign(src_var, function('files', tgt.sources + tgt.generated)) - tgt_node = assign(tgt_var, function(tgt_func, [base_name, id(src_var)], tgt_kwargs)) + tgt_node = assign(tgt_var, function(tgt_func, [base_name, id_node(src_var)], tgt_kwargs)) dep_node = assign(dep_var, function('declare_dependency', kwargs=dep_kwargs)) # Add the nodes to the ast -- cgit v1.1 From e02c1015750fb9a5a15a9d0930281cd78e012133 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Tue, 28 May 2019 16:56:45 +0200 Subject: cmake: moved subprojects into the CMake module --- mesonbuild/cmake/interpreter.py | 18 +++++++++-- mesonbuild/interpreter.py | 30 +++++++----------- mesonbuild/modules/cmake.py | 70 +++++++++++++++++++++++++++++++++++++++-- mesonbuild/msubprojects.py | 2 +- mesonbuild/wrap/wrap.py | 15 ++++++--- 5 files changed, 106 insertions(+), 29 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 7782555..406723f 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -25,7 +25,7 @@ from ..backend.backends import Backend from ..compilers.compilers import obj_suffixes from ..dependencies.base import CMakeDependency, ExternalProgram from subprocess import Popen, PIPE, STDOUT -from typing import List +from typing import List, Dict, Optional import os, re backend_generator_map = { @@ -284,6 +284,9 @@ class CMakeInterpreter: self.languages = [] self.targets = [] + # Generated meson data + self.generated_targets = {} + def configure(self, extra_cmake_options: List[str]) -> None: # Find CMake cmake_exe, cmake_vers, _ = CMakeDependency.find_cmake_binary(self.env) @@ -297,7 +300,7 @@ class CMakeInterpreter: cmake_args = cmake_exe.get_command() # Map meson compiler to CMake variables - for lang, comp in self.build.compilers.items(): + for lang, comp in self.env.coredata.compilers.items(): if lang not in language_map: continue cmake_lang = language_map[lang] @@ -530,11 +533,20 @@ class CMakeInterpreter: # Add the nodes to the ast root_cb.lines += [inc_node, src_node, tgt_node, dep_node] - processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var} + processed[tgt.name] = {'inc': inc_var, 'src': src_var, 'dep': dep_var, 'tgt': tgt_var, 'func': tgt_func} # Now generate the target function calls for i in self.targets: if i.name not in processed: process_target(i) + self.generated_targets = processed return root_cb + + def target_info(self, target: str) -> Optional[Dict[str, str]]: + if target in self.generated_targets: + return self.generated_targets[target] + return None + + def target_list(self) -> List[str]: + return list(self.generated_targets.keys()) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 0e50202..1ac4cd9 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2247,7 +2247,7 @@ class Interpreter(InterpreterBase): raise InterpreterException('Stdlib definition for %s should have exactly two elements.' % l) projname, depname = di - subproj = self.do_subproject(projname, {}) + subproj = self.do_subproject(projname, 'meson', {}) self.build.cross_stdlibs[l] = subproj.get_variable_method([depname], {}) except KeyError: pass @@ -2416,20 +2416,19 @@ external dependencies (including libraries) must go to "dependencies".''') raise InterpreterException('Tried to call option() in build description file. All options must be in the option file.') @FeatureNewKwargs('subproject', '0.38.0', ['default_options']) - @FeatureNewKwargs('subproject', '0.51.0', ['method', 'cmake_options']) @permittedKwargs(permitted_kwargs['subproject']) @stringArgs def func_subproject(self, nodes, args, kwargs): if len(args) != 1: raise InterpreterException('Subproject takes exactly one argument') dirname = args[0] - return self.do_subproject(dirname, kwargs) + return self.do_subproject(dirname, 'meson', kwargs) def disabled_subproject(self, dirname): self.subprojects[dirname] = SubprojectHolder(None, self.subproject_dir, dirname) return self.subprojects[dirname] - def do_subproject(self, dirname, kwargs): + def do_subproject(self, dirname: str, method: str, kwargs): disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: mlog.log('Subproject', mlog.bold(dirname), ':', 'skipped: feature', mlog.bold(feature), 'disabled') @@ -2437,7 +2436,6 @@ external dependencies (including libraries) must go to "dependencies".''') default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) default_options = coredata.create_options_dict(default_options) - method = kwargs.get('method', 'auto') if dirname == '': raise InterpreterException('Subproject dir name must not be empty.') if dirname[0] == '.': @@ -2463,7 +2461,7 @@ external dependencies (including libraries) must go to "dependencies".''') subproject_dir_abs = os.path.join(self.environment.get_source_dir(), self.subproject_dir) r = wrap.Resolver(subproject_dir_abs, self.coredata.get_builtin_option('wrap_mode')) try: - resolved = r.resolve(dirname) + resolved = r.resolve(dirname, method) except wrap.WrapException as e: subprojdir = os.path.join(self.subproject_dir, r.directory) if isinstance(e, wrap.WrapNotFoundException): @@ -2483,21 +2481,14 @@ external dependencies (including libraries) must go to "dependencies".''') os.makedirs(os.path.join(self.build.environment.get_build_dir(), subdir), exist_ok=True) self.global_args_frozen = True - # Determine the method to use when 'auto' is given - if method == 'auto': - if os.path.exists(os.path.join(subdir_abs, environment.build_filename)): - method = 'meson' - elif os.path.exists(os.path.join(subdir_abs, 'CMakeLists.txt')): - method = 'cmake' - mlog.log() with mlog.nested(): mlog.log('Executing subproject', mlog.bold(dirname), 'method', mlog.bold(method), '\n') try: if method == 'meson': - return self.do_subproject_meson(dirname, subdir, default_options, required, kwargs) + return self._do_subproject_meson(dirname, subdir, default_options, required, kwargs) elif method == 'cmake': - return self.do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs) + return self._do_subproject_cmake(dirname, subdir, subdir_abs, default_options, required, kwargs) else: raise InterpreterException('The method {} is invalid for the subproject {}'.format(method, dirname)) # Invalid code is always an error @@ -2513,7 +2504,7 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(dirname) raise e - def do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None): + def _do_subproject_meson(self, dirname, subdir, default_options, required, kwargs, ast=None, build_def_files=None): with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, dirname, subdir, self.subproject_dir, @@ -2544,7 +2535,7 @@ external dependencies (including libraries) must go to "dependencies".''') self.build.subprojects[dirname] = subi.project_version return self.subprojects[dirname] - def do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): + def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, required, kwargs): with mlog.nested(): new_build = self.build.copy() prefix = self.coredata.builtins['prefix'].value @@ -2572,7 +2563,8 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.debug('=== END meson.build ===') mlog.debug() - result = self.do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) + result = self._do_subproject_meson(dirname, subdir, default_options, required, kwargs, ast, cm_int.bs_files) + result.cm_interpreter = cm_int mlog.log() return result @@ -3164,7 +3156,7 @@ external dependencies (including libraries) must go to "dependencies".''') 'default_options': kwargs.get('default_options', []), 'required': kwargs.get('required', True), } - self.do_subproject(dirname, sp_kwargs) + self.do_subproject(dirname, 'meson', sp_kwargs) return self.get_subproject_dep(display_name, dirname, varname, kwargs) @FeatureNewKwargs('executable', '0.42.0', ['implib']) diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index d72ceca..8ce5aef 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -18,8 +18,8 @@ import shutil from . import ExtensionModule, ModuleReturnValue from .. import build, dependencies, mesonlib, mlog -from ..interpreterbase import permittedKwargs -from ..interpreter import ConfigurationDataHolder +from ..interpreterbase import permittedKwargs, FeatureNew, stringArgs, InterpreterObject, ObjectHolder +from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion'] @@ -43,6 +43,62 @@ unset(_realOrig) unset(_realCurr) ''' +class CMakeSubprojectHolder(InterpreterObject, ObjectHolder): + def __init__(self, subp, pv): + assert(isinstance(subp, SubprojectHolder)) + assert(hasattr(subp, 'cm_interpreter')) + InterpreterObject.__init__(self) + ObjectHolder.__init__(self, subp, pv) + self.methods.update({'get_variable': self.get_variable, + 'dependency': self.dependency, + 'include_directories': self.include_directories, + 'target': self.target, + 'target_type': self.target_type, + 'target_list': self.target_list, + }) + + def _args_to_info(self, args): + if len(args) != 1: + raise InterpreterException('Exactly one argument is required.') + + tgt = args[0] + res = self.held_object.cm_interpreter.target_info(tgt) + if res is None: + raise InterpreterException('The CMake target {} does not exist'.format(tgt)) + + # Make sure that all keys are present (if not this is a bug) + assert(all([x in res for x in ['inc', 'src', 'dep', 'tgt', 'func']])) + return res + + @permittedKwargs({}) + def get_variable(self, args, kwargs): + return self.held_object.get_variable_method(args, kwargs) + + @permittedKwargs({}) + def dependency(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['dep']], kwargs) + + @permittedKwargs({}) + def include_directories(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['inc']], kwargs) + + @permittedKwargs({}) + def target(self, args, kwargs): + info = self._args_to_info(args) + return self.get_variable([info['tgt']], kwargs) + + @permittedKwargs({}) + def target_type(self, args, kwargs): + info = self._args_to_info(args) + return info['func'] + + @permittedKwargs({}) + def target_list(self, args, kwargs): + if len(args) > 0: + raise InterpreterException('target_list does not take any parameters.') + return self.held_object.cm_interpreter.target_list() class CmakeModule(ExtensionModule): cmake_detected = False @@ -51,6 +107,7 @@ class CmakeModule(ExtensionModule): def __init__(self, interpreter): super().__init__(interpreter) self.snippets.add('configure_package_config_file') + self.snippets.add('subproject') def detect_voidp_size(self, env): compilers = env.coredata.compilers @@ -210,5 +267,14 @@ class CmakeModule(ExtensionModule): return res + @FeatureNew('subproject', '0.51.0') + @permittedKwargs({'cmake_options'}) + @stringArgs + def subproject(self, interpreter, state, args, kwargs): + if len(args) != 1: + raise InterpreterException('Subproject takes exactly one argument') + dirname = args[0] + return CMakeSubprojectHolder(interpreter.do_subproject(dirname, 'cmake', kwargs), dirname) + def initialize(*args, **kwargs): return CmakeModule(*args, **kwargs) diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 4162af6..bc6a6ce 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -163,7 +163,7 @@ def download(wrap, repo_dir, options): return try: r = Resolver(os.path.dirname(repo_dir)) - r.resolve(wrap.name) + r.resolve(wrap.name, 'meson') mlog.log(' -> done') except WrapException as e: mlog.log(' ->', mlog.red(str(e))) diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index 47db985..55f86bc 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -111,7 +111,7 @@ class Resolver: self.subdir_root = subdir_root self.cachedir = os.path.join(self.subdir_root, 'packagecache') - def resolve(self, packagename): + def resolve(self, packagename: str, method: str): self.packagename = packagename self.directory = packagename # We always have to load the wrap file, if it exists, because it could @@ -125,8 +125,13 @@ class Resolver: meson_file = os.path.join(self.dirname, 'meson.build') cmake_file = os.path.join(self.dirname, 'CMakeLists.txt') + if method not in ['meson', 'cmake']: + raise WrapException('Only the methods "meson" and "cmake" are supported') + # The directory is there and has meson.build? Great, use it. - if os.path.exists(meson_file) or os.path.exists(cmake_file): + if method == 'meson' and os.path.exists(meson_file): + return self.directory + if method == 'cmake' and os.path.exists(cmake_file): return self.directory # Check if the subproject is a git submodule @@ -154,9 +159,11 @@ class Resolver: else: raise WrapException('Unknown wrap type {!r}'.format(self.wrap.type)) - # A meson.build file is required in the directory - if not os.path.exists(meson_file): + # A meson.build or CMakeLists.txt file is required in the directory + if method == 'meson' and not os.path.exists(meson_file): raise WrapException('Subproject exists but has no meson.build file') + if method == 'cmake' and not os.path.exists(cmake_file): + raise WrapException('Subproject exists but has no CMakeLists.txt file') return self.directory -- cgit v1.1 From 2f998cd8d5ab7f794b86fdf9d34630b645b8a72b Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Tue, 28 May 2019 16:57:10 +0200 Subject: cmake: Updated test cases --- test cases/cmake/1 basic/meson.build | 9 +++++++-- test cases/cmake/2 advanced/meson.build | 14 ++++++-------- test cases/cmake/3 advanced no dep/meson.build | 14 ++++++-------- test cases/cmake/4 code gen/meson.build | 6 ++++-- test cases/cmake/5 object library/meson.build | 8 +++++--- test cases/cmake/6 object library no dep/meson.build | 8 +++++--- test cases/cmake/7 cmake options/meson.build | 2 +- 7 files changed, 34 insertions(+), 27 deletions(-) diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build index eaa1e7a..a23063d 100644 --- a/test cases/cmake/1 basic/meson.build +++ b/test cases/cmake/1 basic/meson.build @@ -1,7 +1,12 @@ project('cmakeSubTest', ['c', 'cpp']) -sub_pro = subproject('cmMod', method: 'cmake') -sub_dep = sub_pro.get_variable('cmModLib_dep') +cm = import('cmake') + +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') + +assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target') +assert(sub_pro.target_type('cmModLib') == 'shared_library', 'Target type should be shared_library') exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep]) test('test1', exe1) diff --git a/test cases/cmake/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build index 5f949f4..385a49b 100644 --- a/test cases/cmake/2 advanced/meson.build +++ b/test cases/cmake/2 advanced/meson.build @@ -5,18 +5,16 @@ if not dep_test.found() error('MESON_SKIP_TEST: zlib is not installed') endif -# Test the "normal" subproject call -sub_pro = subproject('cmMod') -sub_dep = sub_pro.get_variable('cmModLib_dep') +cm = import('cmake') -# Test the dependency shortcut -shortcut_dep = dependency('ttabadbneiobevn', fallback: ['cmMod', 'cmModLib_dep']) +# Test the "normal" subproject call +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') # Build some files exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep]) -exe2 = executable('main2', ['main.cpp'], dependencies: [shortcut_dep]) test('test1', exe1) -test('test2', exe2) # Test if we can also extract executables -test('test3', sub_pro.get_variable('testEXE')) +assert(sub_pro.target_type('testEXE') == 'executable', 'The type must be executable for obvious reasons') +test('test2', sub_pro.target('testEXE')) diff --git a/test cases/cmake/3 advanced no dep/meson.build b/test cases/cmake/3 advanced no dep/meson.build index b4469f3..c10dbf5 100644 --- a/test cases/cmake/3 advanced no dep/meson.build +++ b/test cases/cmake/3 advanced no dep/meson.build @@ -1,17 +1,15 @@ project('cmakeSubTest_advanced', ['c', 'cpp']) -# Test the "normal" subproject call -sub_pro = subproject('cmMod') -sub_dep = sub_pro.get_variable('cmModLib_dep') +cm = import('cmake') -# Test the dependency shortcut -shortcut_dep = dependency('ttabadbneiobevn', fallback: ['cmMod', 'cmModLib_dep']) +# Test the "normal" subproject call +sub_pro = cm.subproject('cmMod') +sub_dep = sub_pro.dependency('cmModLib') # Build some files exe1 = executable('main1', ['main.cpp'], dependencies: [sub_dep]) -exe2 = executable('main2', ['main.cpp'], dependencies: [shortcut_dep]) test('test1', exe1) -test('test2', exe2) # Test if we can also extract executables -test('test3', sub_pro.get_variable('testEXE')) +assert(sub_pro.target_type('testEXE') == 'executable', 'The type must be executable for obvious reasons') +test('test2', sub_pro.target('testEXE')) diff --git a/test cases/cmake/4 code gen/meson.build b/test cases/cmake/4 code gen/meson.build index a592dfb..592f903 100644 --- a/test cases/cmake/4 code gen/meson.build +++ b/test cases/cmake/4 code gen/meson.build @@ -1,8 +1,10 @@ project('cmake_code_gen', ['c', 'cpp']) +cm = import('cmake') + # Subproject with the "code generator" -sub_pro = subproject('cmCodeGen') -sub_exe = sub_pro.get_variable('genA') +sub_pro = cm.subproject('cmCodeGen') +sub_exe = sub_pro.target('genA') # Generate the source generated = custom_target( diff --git a/test cases/cmake/5 object library/meson.build b/test cases/cmake/5 object library/meson.build index 76e16be..268c2be 100644 --- a/test cases/cmake/5 object library/meson.build +++ b/test cases/cmake/5 object library/meson.build @@ -9,9 +9,11 @@ if build_machine.system() == 'windows' error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems') endif -sub_pro = subproject('cmObjLib') -sub_sha = sub_pro.get_variable('lib_sha_dep') -sub_sta = sub_pro.get_variable('lib_sta_dep') +cm = import('cmake') + +sub_pro = cm.subproject('cmObjLib') +sub_sha = sub_pro.dependency('lib_sha') +sub_sta = sub_pro.dependency('lib_sta') # Required for the static library zlib_dep = dependency('zlib') diff --git a/test cases/cmake/6 object library no dep/meson.build b/test cases/cmake/6 object library no dep/meson.build index 8e1eaf9..7494fee 100644 --- a/test cases/cmake/6 object library no dep/meson.build +++ b/test cases/cmake/6 object library no dep/meson.build @@ -4,9 +4,11 @@ if build_machine.system() == 'windows' error('MESON_SKIP_TEST: Windows is not supported because of symbol export problems') endif -sub_pro = subproject('cmObjLib') -sub_sha = sub_pro.get_variable('lib_sha_dep') -sub_sta = sub_pro.get_variable('lib_sta_dep') +cm = import('cmake') + +sub_pro = cm.subproject('cmObjLib') +sub_sha = sub_pro.dependency('lib_sha') +sub_sta = sub_pro.dependency('lib_sta') exe_sha = executable('shared', ['main.cpp'], dependencies: [sub_sha]) exe_sta = executable('static', ['main.cpp'], dependencies: [sub_sta]) diff --git a/test cases/cmake/7 cmake options/meson.build b/test cases/cmake/7 cmake options/meson.build index 55813fb..8bb6d1d 100644 --- a/test cases/cmake/7 cmake options/meson.build +++ b/test cases/cmake/7 cmake options/meson.build @@ -1,3 +1,3 @@ project('cmake_set_opt', ['c', 'cpp']) -subproject('cmOpts', method: 'cmake', cmake_options: '-DSOME_CMAKE_VAR=something') +import('cmake').subproject('cmOpts', cmake_options: '-DSOME_CMAKE_VAR=something') -- cgit v1.1 From 438ee6b01da9cc19d578242ceb1615ff3cdec920 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Tue, 28 May 2019 17:51:51 +0200 Subject: cmake: updated docs --- docs/markdown/CMake-module.md | 87 +++++++++++++++++++++++++++++ docs/markdown/Reference-manual.md | 5 -- docs/markdown/Subprojects.md | 42 ++------------ docs/markdown/snippets/cmake_subprojects.md | 38 ++++++------- 4 files changed, 109 insertions(+), 63 deletions(-) diff --git a/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md index 4cc97cf..3b65253 100644 --- a/docs/markdown/CMake-module.md +++ b/docs/markdown/CMake-module.md @@ -1,6 +1,8 @@ # CMake module This module provides helper tools for generating cmake package files. +It also supports the usage of CMake based subprojects, similar to +the normal [meson subprojects](Subprojects.md). ## Usage @@ -10,6 +12,91 @@ following functions will then be available as methods on the object with the name `cmake`. You can, of course, replace the name `cmake` with anything else. +## CMake subprojects + +Using CMake subprojects is similar to using the "normal" meson +subprojects. They also have to be located in the `subprojects` +directory. + +Example: + +```cmake +add_library(cm_lib SHARED ${SOURCES}) +``` + +```meson +cmake = import('cmake') + +# Configure the CMake project +sub_proj = cmake.subproject('libsimple_cmake') + +# Fetch the dependency object +cm_lib = sub_proj.dependency('cm_lib') + +executable(exe1, ['sources'], dependencies: [cm_lib]) +``` + +The `subproject` method is almost identical to the normal meson +`subproject` function. The only difference is that a CMake project +instead of a meson prokect is configured. + +Also, project specific CMake options can be added with the `cmake_options` key. + +The returned `sub_proj` supports the same options as a "normal" subproject. +Meson automatically detects CMake build targets, which can be accessed with +the methods listed [below](#subproject-object). + +It is usually enough to just use the dependency object returned by the +`dependency()` method in the build targets. This is almost identical to +using `declare_dependency()` object from a normal meson subproject. + +It is also possible to use executables defined in the CMake project as code +generators with the `target()` method: + +```cmake +add_executable(cm_exe ${EXE_SRC}) +``` + +```meson +cmake = import('cmake') + +# Subproject with the "code generator" +sub_pro = cmake.subproject('cmCodeGen') + +# Fetch the code generator exe +sub_exe = sub_pro.target('cm_exe') + +# Use the code generator +generated = custom_target( + 'cmake-generated', + input: [], + output: ['test.cpp'], + command: [sub_exe, '@OUTPUT@'] +) +``` + +It should be noted that not all projects are guaranteed to work. The +safest approach would still be to create a `meson.build` for the +subprojects in question. + +### `subproject` object + +This is object is returned by the `subproject` function described above +and supports the following methods: + + - `dependency(target)` returns a dependency object for any CMake target. + - `include_directories(target)` returns a meson include_directories + for the specified target. Using this function is not neccessary if the + dependency object is used. + - `target(target)` returns the raw build target. + - `target_type(target)` returns the type of the target as a string + - `target_list()` returns a list of all target *names*. + - `get_variable(name)` fetches the specified variable from inside + the subproject. Usually `dependency()` or `target()` should be + prefered to extract build targets. + +## CMake configuration files + ### cmake.write_basic_package_version_file() This function is the equivalent of the corresponding [CMake function](https://cmake.org/cmake/help/v3.11/module/CMakePackageConfigHelpers.html#generating-a-package-version-file), diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 68c8f9d..0aa4253 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1405,11 +1405,6 @@ arguments: - `version` keyword argument that works just like the one in `dependency`. It specifies what version the subproject should be, as an example `>=1.0.1` - - `method` *(added 0.51.0)* Specifies the configuration method of the - subproject. Possible values are `meson`, `cmake` and `auto`. With - `auto` meson will always prefer a `meson.build` in the subproject - over other methods. The default value of `method` is `auto`. - - `cmake_options` *(added 0.51.0)* List of additional CMake options - `required` *(added 0.48.0)* By default, `required` is `true` and Meson will abort if the subproject could not be setup. You can set this to `false` and then use the `.found()` method on the [returned diff --git a/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md index 21cb2cb..fc845ff 100644 --- a/docs/markdown/Subprojects.md +++ b/docs/markdown/Subprojects.md @@ -17,8 +17,11 @@ build without (in the best case) any changes to its Meson setup. It becomes a transparent part of the project. It should be noted that this is only guaranteed to work for subprojects -that are built with Meson. Using CMake based subprojects is not guaranteed -to work for all projects. +that are built with Meson. The reason is the simple fact that there is +no possible way to do this reliably with mixed build systems. Because of +this, only meson subprojects are described here. +[CMake based subprojects](CMake-module.md#CMake-subprojects) are also +supported but not guaranteed to work. ## A subproject example @@ -160,41 +163,6 @@ in the top level `subprojects` directory. Recursive use of subprojects is not allowed, though, so you can't have subproject `a` that uses subproject `b` and have `b` also use `a`. -## CMake subprojects - -Meson is also able to use CMake subprojects directly. Using CMake -subprojects is almost identical to using the "normal" meson subprojects: - -```meson -sub_proj = subproject('libsimple_cmake', method : 'cmake') -``` - -The `method` key is optional if the subproject only has a `CMakeList.txt`. -Without specifying a method meson will always first try to find and use a -`meson.build` in the subproject. - -Project specific CMake options can be added with the `cmake_options` key. - -The returned `sub_proj` supports the same options as a "normal" subproject. -Meson automatically detects build targets, which can be retrieved with -`get_variable`. Meson also generates a dependency object for each target. - -These variable names are generated based on the CMake target name. - -```cmake -add_library(cm_exe SHARED ${SOURCES}) -``` - -For `cm_exe`, meson will then define the following variables: - -- `cm_exe` The raw library target (similar to `cm_exe = library('cm_exe', ...)` in meson) -- `cm_exe_dep` The dependency object for the target (similar to `declare_dependency()` in meson) -- `cm_exe_inc` A meson include directory object, containing all include irectories of the target. - -It should be noted that not all projects are guaranteed to work. The -safest approach would still be to create a `meson.build` for the -subprojects in question. - ## Obtaining subprojects Meson ships with a dependency system to automatically obtain diff --git a/docs/markdown/snippets/cmake_subprojects.md b/docs/markdown/snippets/cmake_subprojects.md index 94f68a1..07ff868 100644 --- a/docs/markdown/snippets/cmake_subprojects.md +++ b/docs/markdown/snippets/cmake_subprojects.md @@ -1,33 +1,29 @@ ## CMake subprojects -Meson can now directly consume CMake based subprojects. Using CMake -subprojects is almost identical to using the "normal" meson subprojects: +Meson can now directly consume CMake based subprojects with the +CMake module. -```meson -sub_proj = subproject('libsimple_cmake', method : 'cmake') -``` - -The `method` key is optional if the subproject only has a `CMakeList.txt`. -Without specifying a method meson will always first try to find and use a -`meson.build` in the subproject. - -Project specific CMake options can be added with the new `cmake_options` key. - -The returned `sub_proj` supports the same options as a "normal" subproject. -Meson automatically detects build targets, which can be retrieved with -`get_variable`. Meson also generates a dependency object for each target. +Using CMake subprojects is similar to using the "normal" meson +subprojects. They also have to be located in the `subprojects` +directory. -These variable names are generated based on the CMake target name. +Example: ```cmake -add_library(cm_exe SHARED ${SOURCES}) +add_library(cm_lib SHARED ${SOURCES}) ``` -For `cm_exe`, meson will then define the following variables: +```meson +cmake = import('cmake') + +# Configure the CMake project +sub_proj = cmake.subproject('libsimple_cmake') -- `cm_exe` The raw library target (similar to `cm_exe = library('cm_exe', ...)` in meson) -- `cm_exe_dep` The dependency object for the target (similar to `declare_dependency()` in meson) -- `cm_exe_inc` A meson include directory object, containing all include irectories of the target. +# Fetch the dependency object +cm_lib = sub_proj.dependency('cm_lib') + +executable(exe1, ['sources'], dependencies: [cm_lib]) +``` It should be noted that not all projects are guaranteed to work. The safest approach would still be to create a `meson.build` for the -- cgit v1.1 From 4abd2e71037658c691de23e7ca36787cd1aae12d Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 5 Jun 2019 10:26:41 +0200 Subject: cmake: doc: fixed typos --- docs/markdown/CMake-module.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md index 3b65253..94f4708 100644 --- a/docs/markdown/CMake-module.md +++ b/docs/markdown/CMake-module.md @@ -38,7 +38,7 @@ executable(exe1, ['sources'], dependencies: [cm_lib]) The `subproject` method is almost identical to the normal meson `subproject` function. The only difference is that a CMake project -instead of a meson prokect is configured. +instead of a meson project is configured. Also, project specific CMake options can be added with the `cmake_options` key. @@ -81,13 +81,13 @@ subprojects in question. ### `subproject` object -This is object is returned by the `subproject` function described above +This object is returned by the `subproject` function described above and supports the following methods: - `dependency(target)` returns a dependency object for any CMake target. - - `include_directories(target)` returns a meson include_directories - for the specified target. Using this function is not neccessary if the - dependency object is used. + - `include_directories(target)` returns a meson `include_directories()` + object for the specified target. Using this function is not neccessary + if the dependency object is used. - `target(target)` returns the raw build target. - `target_type(target)` returns the type of the target as a string - `target_list()` returns a list of all target *names*. -- cgit v1.1 From b1bef5ae0df94ec892e0dc1bf126fe68f2d31d5c Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Wed, 5 Jun 2019 22:44:49 +0200 Subject: cmake: removed legacy kwargs from interpreter dict --- mesonbuild/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 1ac4cd9..7c17e1c 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2028,7 +2028,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'both_libraries': known_library_kwargs, 'library': known_library_kwargs, 'subdir': {'if_found'}, - 'subproject': {'version', 'default_options', 'required', 'method', 'cmake_options'}, + 'subproject': {'version', 'default_options', 'required'}, 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite', 'protocol'}, 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, -- cgit v1.1 From 9a9ea1434ab4d204d73503a61d5c1a044ce07366 Mon Sep 17 00:00:00 2001 From: Daniel Mensinger Date: Thu, 6 Jun 2019 11:07:12 +0200 Subject: cmake: Fix cygwin failures --- mesonbuild/cmake/client.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mesonbuild/cmake/client.py b/mesonbuild/cmake/client.py index 4581d6d..f4b549b 100644 --- a/mesonbuild/cmake/client.py +++ b/mesonbuild/cmake/client.py @@ -498,6 +498,15 @@ class CMakeClient: try: self.proc.wait(timeout=2) except TimeoutExpired: - self.proc.terminate() + # 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 -- cgit v1.1