aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2019-06-06 21:55:55 +0300
committerGitHub <noreply@github.com>2019-06-06 21:55:55 +0300
commit266b297515f0e6d0e864564a2fc2f079e829a033 (patch)
tree79d0cacab38ea2919ee52786ac601aa7ba80fe10
parent7561926a70e1920c6ff8754ee1a66ab0bc3ff431 (diff)
parent9a9ea1434ab4d204d73503a61d5c1a044ce07366 (diff)
downloadmeson-266b297515f0e6d0e864564a2fc2f079e829a033.zip
meson-266b297515f0e6d0e864564a2fc2f079e829a033.tar.gz
meson-266b297515f0e6d0e864564a2fc2f079e829a033.tar.bz2
Merge pull request #4969 from mensinda/cmakeSubProject
CMake subprojects
-rw-r--r--azure-pipelines.yml11
-rw-r--r--ci/azure-steps.yml5
-rw-r--r--docs/markdown/CMake-module.md87
-rw-r--r--docs/markdown/Reference-manual.md4
-rw-r--r--docs/markdown/Subprojects.md62
-rw-r--r--docs/markdown/snippets/cmake_subprojects.md30
-rw-r--r--mesonbuild/cmake/__init__.py26
-rw-r--r--mesonbuild/cmake/client.py512
-rw-r--r--mesonbuild/cmake/common.py21
-rw-r--r--mesonbuild/cmake/interpreter.py552
-rw-r--r--mesonbuild/dependencies/base.py76
-rw-r--r--mesonbuild/interpreter.py89
-rw-r--r--mesonbuild/modules/cmake.py70
-rwxr-xr-xmesonbuild/msubprojects.py2
-rw-r--r--mesonbuild/wrap/wrap.py16
-rwxr-xr-xrun_project_tests.py16
-rw-r--r--setup.py1
-rw-r--r--test cases/cmake/1 basic/main.cpp10
-rw-r--r--test cases/cmake/1 basic/meson.build12
-rw-r--r--test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt10
-rw-r--r--test cases/cmake/1 basic/subprojects/cmMod/cmMod.cpp11
-rw-r--r--test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp13
-rw-r--r--test cases/cmake/2 advanced/installed_files.txt4
-rw-r--r--test cases/cmake/2 advanced/main.cpp15
-rw-r--r--test cases/cmake/2 advanced/meson.build20
-rw-r--r--test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt22
-rw-r--r--test cases/cmake/2 advanced/subprojects/cmMod/config.h.in3
-rw-r--r--test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp17
-rw-r--r--test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp13
-rw-r--r--test cases/cmake/2 advanced/subprojects/cmMod/main.cpp11
-rw-r--r--test cases/cmake/3 advanced no dep/installed_files.txt6
-rw-r--r--test cases/cmake/3 advanced no dep/main.cpp15
-rw-r--r--test cases/cmake/3 advanced no dep/meson.build15
-rw-r--r--test cases/cmake/3 advanced no dep/subprojects/cmMod/CMakeLists.txt19
-rw-r--r--test cases/cmake/3 advanced no dep/subprojects/cmMod/config.h.in3
-rw-r--r--test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.cpp16
-rw-r--r--test cases/cmake/3 advanced no dep/subprojects/cmMod/lib/cmMod.hpp13
-rw-r--r--test cases/cmake/3 advanced no dep/subprojects/cmMod/main.cpp10
-rw-r--r--test cases/cmake/4 code gen/main.cpp8
-rw-r--r--test cases/cmake/4 code gen/meson.build20
-rw-r--r--test cases/cmake/4 code gen/subprojects/cmCodeGen/CMakeLists.txt5
-rw-r--r--test cases/cmake/4 code gen/subprojects/cmCodeGen/main.cpp21
-rw-r--r--test cases/cmake/4 code gen/test.hpp5
-rw-r--r--test cases/cmake/5 object library/main.cpp9
-rw-r--r--test cases/cmake/5 object library/meson.build25
-rw-r--r--test cases/cmake/5 object library/subprojects/cmObjLib/CMakeLists.txt10
-rw-r--r--test cases/cmake/5 object library/subprojects/cmObjLib/libA.cpp5
-rw-r--r--test cases/cmake/5 object library/subprojects/cmObjLib/libA.hpp5
-rw-r--r--test cases/cmake/5 object library/subprojects/cmObjLib/libB.cpp6
-rw-r--r--test cases/cmake/5 object library/subprojects/cmObjLib/libB.hpp5
-rw-r--r--test cases/cmake/6 object library no dep/main.cpp9
-rw-r--r--test cases/cmake/6 object library no dep/meson.build17
-rw-r--r--test cases/cmake/6 object library no dep/subprojects/cmObjLib/CMakeLists.txt5
-rw-r--r--test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.cpp5
-rw-r--r--test cases/cmake/6 object library no dep/subprojects/cmObjLib/libA.hpp5
-rw-r--r--test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.cpp5
-rw-r--r--test cases/cmake/6 object library no dep/subprojects/cmObjLib/libB.hpp5
-rw-r--r--test cases/cmake/7 cmake options/meson.build3
-rw-r--r--test cases/cmake/7 cmake options/subprojects/cmOpts/CMakeLists.txt5
59 files changed, 1926 insertions, 95 deletions
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
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/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md
index 4cc97cf..94f4708 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 project 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 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()`
+ 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*.
+ - `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 f59d627..0aa4253 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
@@ -2175,7 +2175,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/docs/markdown/Subprojects.md b/docs/markdown/Subprojects.md
index 2546441..fc845ff 100644
--- a/docs/markdown/Subprojects.md
+++ b/docs/markdown/Subprojects.md
@@ -14,17 +14,19 @@ 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. 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
-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 +35,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 `<project_name>_dep` form.
+Ideally the dependency variable name should be of `<project_name>_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 `<project_name>_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 `<project_name>_dep` (e.g. `gtest_dep`), and others can have
`<project_name>_<other>_<name>_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 +67,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 +86,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 +104,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 +117,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 +143,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
diff --git a/docs/markdown/snippets/cmake_subprojects.md b/docs/markdown/snippets/cmake_subprojects.md
new file mode 100644
index 0000000..07ff868
--- /dev/null
+++ b/docs/markdown/snippets/cmake_subprojects.md
@@ -0,0 +1,30 @@
+## CMake subprojects
+
+Meson can now directly consume CMake based subprojects with the
+CMake module.
+
+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])
+```
+
+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.
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..f4b549b
--- /dev/null
+++ b/mesonbuild/cmake/client.py
@@ -0,0 +1,512 @@
+# 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
+import os
+
+CMAKE_SERVER_BEGIN_STR = '[== "CMake Server" ==['
+CMAKE_SERVER_END_STR = ']== "CMake Server" ==]'
+
+CMAKE_MESSAGE_TYPES = {
+ 'error': ['cookie', 'errorMessage'],
+ 'hello': ['supportedProtocolVersions'],
+ 'message': ['cookie', 'message'],
+ 'progress': ['cookie'],
+ 'reply': ['cookie', 'inReplyTo'],
+ 'signal': ['cookie', 'name'],
+}
+
+CMAKE_REPLY_TYPES = {
+ 'handshake': [],
+ 'configure': [],
+ 'compute': [],
+ 'cmakeInputs': ['buildFiles', 'cmakeRootDirectory', 'sourceDirectory'],
+ 'codemodel': ['configurations']
+}
+
+# Base CMake server message classes
+
+class MessageBase:
+ def __init__(self, msg_type: str, cookie: str):
+ self.type = msg_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, msg_type: str):
+ super().__init__(msg_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
+
+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):
+ 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 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)
+ pass
+
+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', '')
+ 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
+
+ # 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': '/'.join(src_list),
+ 'buildDirectory': '/'.join(bld_list),
+ 'generator': self.generator,
+ '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 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):
+ def __init__(self, cookie: str):
+ super().__init__(cookie, 'handshake')
+
+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
+ 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))
+
+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 in ['"', "'"]:
+ in_string = not in_string
+ elif i in [' ', '\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
+ self.proc = None
+ self.type_map = {
+ 'error': lambda data: Error(data['cookie'], data['errorMessage']),
+ 'hello': lambda data: MessageHello(data['supportedProtocolVersions']),
+ 'message': lambda data: Message(data['cookie'], data['message']),
+ 'progress': lambda data: Progress(data['cookie']),
+ 'reply': self.resolve_type_reply,
+ 'signal': lambda data: SignalBase(data['cookie'], data['name'])
+ }
+
+ 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:
+ 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')
+ msg_type = raw_data['type']
+ func = self.type_map.get(msg_type, None)
+ if not func:
+ 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, msg_type))
+ return func(raw_data)
+
+ def writeMessage(self, msg: MessageBase) -> None:
+ raw_data = '\n{}\n{}\n{}\n'.format(CMAKE_SERVER_BEGIN_STR, json.dumps(msg.to_dict(), indent=2), CMAKE_SERVER_END_STR)
+ self.proc.stdin.write(raw_data.encode('ascii'))
+ self.proc.stdin.flush()
+
+ def query(self, request: RequestBase) -> MessageBase:
+ self.writeMessage(request)
+ while True:
+ reply = self.readMessage()
+ if reply.cookie == request.cookie and reply.type in ['reply', 'error']:
+ return reply
+
+ reply.log()
+
+ def query_checked(self, request: RequestBase, message: str) -> 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()
+ if not isinstance(msg, MessageHello):
+ raise CMakeException('Recieved an unexpected message from the CMake server')
+
+ request = RequestHandShake(src_dir, build_dir, generator, vers_major, vers_minor)
+ self.query_checked(request, 'CMake server handshake')
+
+ def resolve_type_reply(self, data: 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_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()
+ 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.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.debug('Shutting down the CMake server')
+
+ # Close the pipes to exit
+ self.proc.stdin.close()
+ self.proc.stdout.close()
+
+ # Wait for CMake to finish
+ try:
+ self.proc.wait(timeout=2)
+ except TimeoutExpired:
+ # Terminate CMake if there is a timeout
+ # terminate() may throw a platform specific exception if the process has already
+ # terminated. This may be the case if there is a race condition (CMake exited after
+ # the timeout but before the terminate() call). Additionally, this behavior can
+ # also be triggered on cygwin if CMake crashes.
+ # See https://github.com/mesonbuild/meson/pull/4969#issuecomment-499413233
+ try:
+ self.proc.terminate()
+ except Exception:
+ pass
+
+ self.proc = None
diff --git a/mesonbuild/cmake/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..406723f
--- /dev/null
+++ b/mesonbuild/cmake/interpreter.py
@@ -0,0 +1,552 @@
+# 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, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel, CMakeTarget
+from .. import mlog
+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, Dict, Optional
+import os, re
+
+backend_generator_map = {
+ 'ninja': 'Ninja',
+ 'xcode': 'Xcode',
+ 'vs2010': 'Visual Studio 10 2010',
+ 'vs2015': 'Visual Studio 15 2017',
+ 'vs2017': 'Visual Studio 15 2017',
+ 'vs2019': 'Visual Studio 16 2019',
+}
+
+language_map = {
+ 'c': 'C',
+ 'cpp': 'CXX',
+ 'cuda': 'CUDA',
+ 'cs': 'CSharp',
+ 'java': 'Java',
+ 'fortran': 'Fortran',
+ 'swift': 'Swift',
+}
+
+target_type_map = {
+ 'STATIC_LIBRARY': 'static_library',
+ 'MODULE_LIBRARY': 'shared_module',
+ 'SHARED_LIBRARY': 'shared_library',
+ 'EXECUTABLE': 'executable',
+ 'OBJECT_LIBRARY': 'static_library',
+}
+
+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()}
+
+ def __init__(self, target: CMakeTarget, env: Environment):
+ self.env = env
+ self.artifacts = target.artifacts
+ self.src_dir = target.src_dir
+ self.build_dir = target.build_dir
+ self.name = target.name
+ self.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.object_libs = []
+ self.compile_opts = {}
+ self.pie = False
+
+ # 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?|[-]{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
+ for i in ['c', 'cpp']:
+ if i not 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))]
+ elif j in ['-fPIC', '-fpic', '-fPIE', '-fpie']:
+ self.pie = True
+ elif j in blacklist_compiler_flags:
+ pass
+ else:
+ temp += [j]
+
+ 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:
+ # Let meson handle this arcane magic
+ if ',-rpath,' in 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
+
+ # Make paths relative
+ def rel_path(x: str) -> str:
+ 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)
+ 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
+
+ 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]
+ 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:
+ 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)
+
+ # 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]
+ 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:
+ 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'))
+ 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)))
+ 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():
+ mlog.log(' -', key, '=', mlog.bold(str(val)))
+
+class CMakeInterpreter:
+ 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_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
+ self.client = CMakeClient(self.env)
+
+ # Raw CMake results
+ self.bs_files = []
+ self.codemodel = None
+
+ # Analysed data
+ self.project_name = ''
+ 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)
+ 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 = backend_generator_map[self.backend_name]
+ cmake_args = cmake_exe.get_command()
+
+ # Map meson compiler to CMake variables
+ for lang, comp in self.env.coredata.compilers.items():
+ if lang not in language_map:
+ continue
+ cmake_lang = 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])]
+ 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
+
+ # Run CMake
+ 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)
+ 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:
+ line = proc.stdout.readline()
+ if not line:
+ break
+ mlog.log(line.decode('utf-8').strip('\n'))
+
+ # 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 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(extra_cmake_options)
+
+ with self.client.connect():
+ generator = backend_generator_map[self.backend_name]
+ self.client.do_handshake(self.src_dir, self.build_dir, generator, 1)
+
+ # Do a second configure to initialise the server
+ self.client.query_checked(RequestConfigure(), 'CMake server configure')
+
+ # Generate the build system files
+ self.client.query_checked(RequestCompute(), 'Generating build system files')
+
+ # Get CMake build system files
+ bs_reply = self.client.query_checked(RequestCMakeInputs(), 'Querying build system files')
+
+ # Now get the CMake code model
+ 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 = [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:
+ if self.codemodel is None:
+ raise CMakeException('CMakeInterpreter was not initialized')
+
+ # Clear analyser data
+ self.project_name = ''
+ self.languages = []
+ self.targets = []
+
+ # Find all 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:
+ 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}
+ 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
+ for i in self.targets:
+ 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]
+
+ # 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:
+ 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_node(value: str) -> IdNode:
+ return IdNode(token(val=value))
+
+ 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())
+ if not isinstance(elements, list):
+ elements = [args]
+ args.arguments += [nodeify(x) for x in elements]
+ return ArrayNode(args, 0, 0, 0, 0)
+
+ 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]
+ 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, 0, 0, name, args_n)
+ return func_n
+
+ 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]
+ 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)
+
+ # 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 = []
+ objec_libs = []
+ for i in tgt.link_with:
+ assert(isinstance(i, ConverterTarget))
+ if i.name not in processed:
+ process_target(i)
+ link_with += [id_node(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()
+ if not tgt_func:
+ raise CMakeException('Unknown target type "{}"'.format(tgt.type))
+
+ # Determine the variable names
+ 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)
+ tgt_var = base_name
+
+ # Generate target kwargs
+ tgt_kwargs = {
+ 'link_args': tgt.link_flags + tgt.link_libraries,
+ 'link_with': link_with,
+ 'include_directories': id_node(inc_var),
+ 'install': tgt.install,
+ 'install_dir': tgt.install_dir,
+ 'override_options': tgt.override_options,
+ 'objects': [method(id_node(x), 'extract_all_objects') for x in objec_libs],
+ }
+
+ # 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_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_node(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, '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/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
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index a945f70..7c17e1c 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
@@ -2037,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
@@ -2054,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 = {}
@@ -2243,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
@@ -2418,13 +2422,13 @@ external dependencies (including libraries) must go to "dependencies".''')
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')
@@ -2457,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):
@@ -2473,22 +2477,20 @@ 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
+
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, 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
except InvalidCode:
raise
@@ -2502,6 +2504,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, 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,
+ self.modules, default_options, ast=ast)
+ 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:
@@ -2513,11 +2527,48 @@ 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]
+ 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
+ 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(cmake_options)
+ 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()
+
+ # 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.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)
+ result.cm_interpreter = cm_int
+
+ mlog.log()
+ return result
+
def get_option_internal(self, optname):
for opts in chain(
[self.coredata.base_options, compilers.base_options, self.coredata.builtins],
@@ -3105,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 3eb68a7..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
@@ -123,9 +123,15 @@ 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')
+
+ 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):
+ 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
@@ -153,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
diff --git a/run_project_tests.py b/run_project_tests.py
index a3aa07d..f6d83b5 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)
@@ -222,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):
@@ -553,6 +562,7 @@ def skip_csharp(backend):
def detect_tests_to_run():
# Name, subdirectory, skip condition.
all_tests = [
+ ('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),
@@ -664,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)
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 <iostream>
+#include <cmMod.hpp>
+
+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..a23063d
--- /dev/null
+++ b/test cases/cmake/1 basic/meson.build
@@ -0,0 +1,12 @@
+project('cmakeSubTest', ['c', 'cpp'])
+
+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/1 basic/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt
new file mode 100644
index 0000000..7a9538b
--- /dev/null
+++ b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt
@@ -0,0 +1,10 @@
+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.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..52f576b
--- /dev/null
+++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <string>
+#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/2 advanced/installed_files.txt b/test cases/cmake/2 advanced/installed_files.txt
new file mode 100644
index 0000000..8cbdcd7
--- /dev/null
+++ b/test cases/cmake/2 advanced/installed_files.txt
@@ -0,0 +1,4 @@
+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/2 advanced/main.cpp b/test cases/cmake/2 advanced/main.cpp
new file mode 100644
index 0000000..6cc4c0c
--- /dev/null
+++ b/test cases/cmake/2 advanced/main.cpp
@@ -0,0 +1,15 @@
+#include <iostream>
+#include <cmMod.hpp>
+#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/2 advanced/meson.build b/test cases/cmake/2 advanced/meson.build
new file mode 100644
index 0000000..385a49b
--- /dev/null
+++ b/test cases/cmake/2 advanced/meson.build
@@ -0,0 +1,20 @@
+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
+
+cm = import('cmake')
+
+# 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])
+test('test1', exe1)
+
+# Test if we can also extract executables
+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/2 advanced/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt
new file mode 100644
index 0000000..14908a3
--- /dev/null
+++ b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt
@@ -0,0 +1,22 @@
+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)
+
+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)
+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/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
new file mode 100644
index 0000000..027296e
--- /dev/null
+++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp
@@ -0,0 +1,17 @@
+#include "cmMod.hpp"
+#include <zlib.h>
+#include "config.h"
+
+#if CONFIG_OPT != 42
+#error "Invalid value of CONFIG_OPT"
+#endif
+
+using namespace std;
+
+cmModClass::cmModClass(string foo) {
+ str = foo + " World " + zlibVersion();
+}
+
+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..52f576b
--- /dev/null
+++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <string>
+#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/2 advanced/subprojects/cmMod/main.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp
new file mode 100644
index 0000000..a1b1637
--- /dev/null
+++ b/test cases/cmake/2 advanced/subprojects/cmMod/main.cpp
@@ -0,0 +1,11 @@
+#include <iostream>
+#include <zlib.h>
+#include "lib/cmMod.hpp"
+
+using namespace std;
+
+int main() {
+ cmModClass obj("Hello (LIB TEST)");
+ cout << obj.getStr() << " ZLIB: " << zlibVersion() << endl;
+ return 0;
+}
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
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 <iostream>
+#include <cmMod.hpp>
+#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..c10dbf5
--- /dev/null
+++ b/test cases/cmake/3 advanced no dep/meson.build
@@ -0,0 +1,15 @@
+project('cmakeSubTest_advanced', ['c', 'cpp'])
+
+cm = import('cmake')
+
+# 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])
+test('test1', exe1)
+
+# Test if we can also extract executables
+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/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 <string>
+#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 <iostream>
+#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/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 <iostream>
+#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..592f903
--- /dev/null
+++ b/test cases/cmake/4 code gen/meson.build
@@ -0,0 +1,20 @@
+project('cmake_code_gen', ['c', 'cpp'])
+
+cm = import('cmake')
+
+# Subproject with the "code generator"
+sub_pro = cm.subproject('cmCodeGen')
+sub_exe = sub_pro.target('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 <iostream>
+#include <fstream>
+
+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 <string>
+
+std::string getStr();
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 <iostream>
+#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..268c2be
--- /dev/null
+++ b/test cases/cmake/5 object library/meson.build
@@ -0,0 +1,25 @@
+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
+
+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')
+
+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 $<TARGET_OBJECTS:lib_obj>)
+add_library(lib_sta STATIC $<TARGET_OBJECTS:lib_obj>)
+
+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 <string>
+
+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 <zlib.h>
+
+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 <string>
+
+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 <iostream>
+#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..7494fee
--- /dev/null
+++ b/test cases/cmake/6 object library no dep/meson.build
@@ -0,0 +1,17 @@
+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
+
+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])
+
+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 $<TARGET_OBJECTS:lib_obj>)
+add_library(lib_sta STATIC $<TARGET_OBJECTS:lib_obj>)
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 <string>
+
+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 <string>
+
+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..8bb6d1d
--- /dev/null
+++ b/test cases/cmake/7 cmake options/meson.build
@@ -0,0 +1,3 @@
+project('cmake_set_opt', ['c', 'cpp'])
+
+import('cmake').subproject('cmOpts', 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()