aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/CMake-module.md77
-rw-r--r--docs/markdown/snippets/cmake.md17
-rw-r--r--mesonbuild/cmake/__init__.py5
-rw-r--r--mesonbuild/cmake/common.py95
-rw-r--r--mesonbuild/cmake/interpreter.py21
-rw-r--r--mesonbuild/interpreter.py12
-rw-r--r--mesonbuild/modules/cmake.py110
-rw-r--r--test cases/cmake/19 advanced options/main.cpp18
-rw-r--r--test cases/cmake/19 advanced options/meson.build29
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/CMakeLists.txt18
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.cpp31
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.hpp14
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.cpp25
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.hpp3
-rw-r--r--test cases/cmake/19 advanced options/subprojects/cmOpts/main.cpp10
-rw-r--r--test cases/cmake/19 advanced options/test.json8
16 files changed, 470 insertions, 23 deletions
diff --git a/docs/markdown/CMake-module.md b/docs/markdown/CMake-module.md
index 7103608..fc6157e 100644
--- a/docs/markdown/CMake-module.md
+++ b/docs/markdown/CMake-module.md
@@ -48,8 +48,6 @@ 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).
@@ -87,6 +85,49 @@ 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.
+### Configuration options
+
+*New in meson 0.55.0*
+
+Meson also supports passing configuration options to CMake and overriding
+certain build details extracted from the CMake subproject.
+
+```meson
+cmake = import('cmake')
+opt_var = cmake.subproject_options()
+
+# Call CMake with `-DSOME_OTHER_VAR=ON`
+opt_var.add_cmake_defines({'SOME_OTHER_VAR': true})
+
+# Globally override the C++ standard to c++11
+opt_var.set_override_option('cpp_std', 'c++11')
+
+# Override the previous global C++ standard
+# with c++14 only for the CMake target someLib
+opt_var.set_override_option('cpp_std', 'c++14', target: 'someLib')
+
+sub_pro = cmake.subproject('someLibProject', options: opt_var)
+
+# Further changes to opt_var have no effect
+```
+
+See [the CMake options object](#cmake-options-object) for a complete reference
+of all supported functions.
+
+The CMake configuration options object is very similar to the
+[configuration data object](Reference-manual.md#configuration-data-object) object
+returned by [`configuration_data`](Reference-manual.md#configuration_data). It
+is generated by the `subproject_options` function
+
+All configuration options have to be set *before* the subproject is configured
+and must be passed to the `subproject` method via the `options` key. Altering
+the configuration object won't have any effect on previous `cmake.subproject`
+calls.
+
+In earlier meson versions CMake command-line parameters could be set with the
+`cmake_options` kwarg. However, this feature is deprecated since 0.55.0 and only
+kept for compatibility. It will not work together with the `options` kwarg.
+
### `subproject` object
This object is returned by the `subproject` function described above
@@ -103,7 +144,37 @@ and supports the following methods:
the subproject. Usually `dependency()` or `target()` should be
preferred to extract build targets.
- `found` returns true if the subproject is available, otherwise false
- *new in in 0.53.2*
+ *new in meson 0.53.2*
+
+### `cmake options` object
+
+This object is returned by the `subproject_options()` function and consumed by
+the `options` kwarg of the `subproject` function. The following methods are
+supported:
+
+ - `add_cmake_defines({'opt1': val1, ...})` add additional CMake commandline defines
+ - `set_override_option(opt, val)` set specific [build options](Build-options.md)
+ for targets. This will effectively add `opt=val` to the `override_options`
+ array of the [build target](Reference-manual.md#executable)
+ - `set_install(bool)` override wether targets should be installed or not
+ - `append_compile_args(lang, arg1, ...)` append compile flags for a specific
+ language to the targets
+ - `append_link_args(arg1, ...)` append linger args to the targets
+ - `clear()` reset all data in the `cmake options` object
+
+The methods `set_override_option`, `set_install`, `append_compile_args` and
+`append_link_args` support the optional `target` kwarg. If specified, the set
+options affect the specific target. The effect of the option is global for the
+subproject otherwise.
+
+If, for instance, `opt_var.set_install(false)` is called, no target will be
+installed regardless of what is set by CMake. However, it is still possible to
+install specific targets (here `foo`) by setting the `target` kwarg:
+`opt_var.set_install(true, target: 'foo')`
+
+Options that are not set won't affect the generated subproject. So, if for
+instance, `set_install` was not called then the values extracted from CMake will
+be used.
## CMake configuration files
diff --git a/docs/markdown/snippets/cmake.md b/docs/markdown/snippets/cmake.md
new file mode 100644
index 0000000..16da78e
--- /dev/null
+++ b/docs/markdown/snippets/cmake.md
@@ -0,0 +1,17 @@
+## Configure CMake subprojects with meson.subproject_options
+
+Meson now supports passing configuration options to CMake and overriding
+certain build details extracted from the CMake subproject.
+
+The new CMake configuration options object is very similar to the
+[configuration data object](Reference-manual.md#configuration-data-object) object
+returned by [`configuration_data`](Reference-manual.md#configuration_data). It
+is generated by the `subproject_options` function
+
+All configuration options have to be set *before* the subproject is configured
+and must be passed to the `subproject` method via the `options` key. Altering
+the configuration object won't have any effect on previous `cmake.subproject`
+calls.
+
+**Note:** The `cmake_options` kwarg for the `subproject` function is now
+deprecated since it is replaced by the new `options` system.
diff --git a/mesonbuild/cmake/__init__.py b/mesonbuild/cmake/__init__.py
index 01cc3f9..db7aefd 100644
--- a/mesonbuild/cmake/__init__.py
+++ b/mesonbuild/cmake/__init__.py
@@ -24,11 +24,14 @@ __all__ = [
'CMakeTarget',
'CMakeTraceLine',
'CMakeTraceParser',
+ 'SingleTargetOptions',
+ 'TargetOptions',
'parse_generator_expressions',
'language_map',
+ 'cmake_defines_to_args',
]
-from .common import CMakeException
+from .common import CMakeException, SingleTargetOptions, TargetOptions, cmake_defines_to_args
from .client import CMakeClient
from .executor import CMakeExecutor
from .fileapi import CMakeFileAPI
diff --git a/mesonbuild/cmake/common.py b/mesonbuild/cmake/common.py
index e7da0d7..4510b5d 100644
--- a/mesonbuild/cmake/common.py
+++ b/mesonbuild/cmake/common.py
@@ -60,6 +60,26 @@ def _flags_to_list(raw: str) -> T.List[str]:
res = list(filter(lambda x: len(x) > 0, res))
return res
+def cmake_defines_to_args(raw: T.Any, permissive: bool = False) -> T.List[str]:
+ res = [] # type: T.List[str]
+ if not isinstance(raw, list):
+ raw = [raw]
+
+ for i in raw:
+ if not isinstance(i, dict):
+ raise MesonException('Invalid CMake defines. Expected a dict, but got a {}'.format(type(i).__name__))
+ for key, val in i.items():
+ assert isinstance(key, str)
+ if isinstance(val, (str, int, float)):
+ res += ['-D{}={}'.format(key, val)]
+ elif isinstance(val, bool):
+ val_str = 'ON' if val else 'OFF'
+ res += ['-D{}={}'.format(key, val_str)]
+ else:
+ raise MesonException('Type "{}" of "{}" is not supported as for a CMake define value'.format(type(val).__name__, key))
+
+ return res
+
class CMakeFileGroup:
def __init__(self, data: dict):
self.defines = data.get('defines', '')
@@ -163,3 +183,78 @@ class CMakeConfiguration:
mlog.log('Project {}:'.format(idx))
with mlog.nested():
i.log()
+
+class SingleTargetOptions:
+ def __init__(self) -> None:
+ self.opts = {} # type: T.Dict[str, str]
+ self.lang_args = {} # type: T.Dict[str, T.List[str]]
+ self.link_args = [] # type: T.List[str]
+ self.install = 'preserve'
+
+ def set_opt(self, opt: str, val: str) -> None:
+ self.opts[opt] = val
+
+ def append_args(self, lang: str, args: T.List[str]) -> None:
+ if lang not in self.lang_args:
+ self.lang_args[lang] = []
+ self.lang_args[lang] += args
+
+ def append_link_args(self, args: T.List[str]) -> None:
+ self.link_args += args
+
+ def set_install(self, install: bool) -> None:
+ self.install = 'true' if install else 'false'
+
+ def get_override_options(self, initial: T.List[str]) -> T.List[str]:
+ res = [] # type: T.List[str]
+ for i in initial:
+ opt = i[:i.find('=')]
+ if opt not in self.opts:
+ res += [i]
+ res += ['{}={}'.format(k, v) for k, v in self.opts.items()]
+ return res
+
+ def get_compile_args(self, lang: str, initial: T.List[str]) -> T.List[str]:
+ if lang in self.lang_args:
+ return initial + self.lang_args[lang]
+ return initial
+
+ def get_link_args(self, initial: T.List[str]) -> T.List[str]:
+ return initial + self.link_args
+
+ def get_install(self, initial: bool) -> bool:
+ return {'preserve': initial, 'true': True, 'false': False}[self.install]
+
+class TargetOptions:
+ def __init__(self) -> None:
+ self.global_options = SingleTargetOptions()
+ self.target_options = {} # type: T.Dict[str, SingleTargetOptions]
+
+ def __getitem__(self, tgt: str) -> SingleTargetOptions:
+ if tgt not in self.target_options:
+ self.target_options[tgt] = SingleTargetOptions()
+ return self.target_options[tgt]
+
+ def get_override_options(self, tgt: str, initial: T.List[str]) -> T.List[str]:
+ initial = self.global_options.get_override_options(initial)
+ if tgt in self.target_options:
+ initial = self.target_options[tgt].get_override_options(initial)
+ return initial
+
+ def get_compile_args(self, tgt: str, lang: str, initial: T.List[str]) -> T.List[str]:
+ initial = self.global_options.get_compile_args(lang, initial)
+ if tgt in self.target_options:
+ initial = self.target_options[tgt].get_compile_args(lang, initial)
+ return initial
+
+ def get_link_args(self, tgt: str, initial: T.List[str]) -> T.List[str]:
+ initial = self.global_options.get_link_args(initial)
+ if tgt in self.target_options:
+ initial = self.target_options[tgt].get_link_args(initial)
+ return initial
+
+ def get_install(self, tgt: str, initial: bool) -> bool:
+ initial = self.global_options.get_install(initial)
+ if tgt in self.target_options:
+ initial = self.target_options[tgt].get_install(initial)
+ return initial
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py
index 2857527..0516947 100644
--- a/mesonbuild/cmake/interpreter.py
+++ b/mesonbuild/cmake/interpreter.py
@@ -17,7 +17,7 @@
import pkg_resources
-from .common import CMakeException, CMakeTarget
+from .common import CMakeException, CMakeTarget, TargetOptions
from .client import CMakeClient, RequestCMakeInputs, RequestConfigure, RequestCompute, RequestCodeModel
from .fileapi import CMakeFileAPI
from .executor import CMakeExecutor
@@ -993,7 +993,7 @@ class CMakeInterpreter:
mlog.log('CMake project', mlog.bold(self.project_name), 'has', mlog.bold(str(len(self.targets) + len(self.custom_targets))), 'build targets.')
- def pretend_to_be_meson(self) -> CodeBlockNode:
+ def pretend_to_be_meson(self, options: TargetOptions) -> CodeBlockNode:
if not self.project_name:
raise CMakeException('CMakeInterpreter was not analysed')
@@ -1154,21 +1154,26 @@ class CMakeInterpreter:
dep_var = '{}_dep'.format(tgt.name)
tgt_var = tgt.name
+ install_tgt = options.get_install(tgt.cmake_name, tgt.install)
+
# Generate target kwargs
tgt_kwargs = {
- 'build_by_default': tgt.install,
- 'link_args': tgt.link_flags + tgt.link_libraries,
+ 'build_by_default': install_tgt,
+ 'link_args': options.get_link_args(tgt.cmake_name, 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,
+ 'install': install_tgt,
+ 'override_options': options.get_override_options(tgt.cmake_name, tgt.override_options),
'objects': [method(x, 'extract_all_objects') for x in objec_libs],
}
+ # Only set if installed and only override if it is set
+ if install_tgt and tgt.install_dir:
+ tgt_kwargs['install_dir'] = tgt.install_dir
+
# Handle compiler args
for key, val in tgt.compile_opts.items():
- tgt_kwargs['{}_args'.format(key)] = val
+ tgt_kwargs['{}_args'.format(key)] = options.get_compile_args(tgt.cmake_name, key, val)
# Handle -fPCI, etc
if tgt_func == 'executable':
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 76c8254..487bdd6 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -2444,7 +2444,7 @@ class Interpreter(InterpreterBase):
if isinstance(item, build.CustomTarget):
return CustomTargetHolder(item, self)
- elif isinstance(item, (int, str, bool, Disabler)) or item is None:
+ elif isinstance(item, (int, str, bool, Disabler, InterpreterObject)) or item is None:
return item
elif isinstance(item, build.Executable):
return ExecutableHolder(item, self)
@@ -2867,13 +2867,21 @@ external dependencies (including libraries) must go to "dependencies".''')
with mlog.nested():
new_build = self.build.copy()
prefix = self.coredata.builtins['prefix'].value
+
+ from .modules.cmake import CMakeSubprojectOptions
+ options = kwargs.get('options', CMakeSubprojectOptions())
+ if not isinstance(options, CMakeSubprojectOptions):
+ raise InterpreterException('"options" kwarg must be CMakeSubprojectOptions'
+ ' object (created by cmake.subproject_options())')
+
cmake_options = mesonlib.stringlistify(kwargs.get('cmake_options', []))
+ cmake_options += options.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()
+ ast = cm_int.pretend_to_be_meson(options.target_options)
mlog.log()
with mlog.nested():
diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py
index 6d91b99..ca98b1c 100644
--- a/mesonbuild/modules/cmake.py
+++ b/mesonbuild/modules/cmake.py
@@ -14,12 +14,28 @@
import re
import os, os.path, pathlib
import shutil
+import typing as T
from . import ExtensionModule, ModuleReturnValue
from .. import build, dependencies, mesonlib, mlog
-from ..interpreterbase import permittedKwargs, FeatureNew, stringArgs, InterpreterObject, ObjectHolder, noPosargs
+from ..cmake import SingleTargetOptions, TargetOptions, cmake_defines_to_args
from ..interpreter import ConfigurationDataHolder, InterpreterException, SubprojectHolder
+from ..interpreterbase import (
+ InterpreterObject,
+ ObjectHolder,
+
+ FeatureNew,
+ FeatureNewKwargs,
+ FeatureDeprecatedKwargs,
+
+ stringArgs,
+ permittedKwargs,
+ noPosargs,
+ noKwargs,
+
+ InvalidArguments,
+)
COMPATIBILITIES = ['AnyNewerVersion', 'SameMajorVersion', 'SameMinorVersion', 'ExactVersion']
@@ -82,42 +98,107 @@ class CMakeSubprojectHolder(InterpreterObject, ObjectHolder):
assert(all([x in res for x in ['inc', 'src', 'dep', 'tgt', 'func']]))
return res
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def get_variable(self, args, kwargs):
return self.held_object.get_variable_method(args, kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def dependency(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['dep']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def include_directories(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['inc']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def target(self, args, kwargs):
info = self._args_to_info(args)
return self.get_variable([info['tgt']], kwargs)
- @permittedKwargs({})
+ @noKwargs
+ @stringArgs
def target_type(self, args, kwargs):
info = self._args_to_info(args)
return info['func']
@noPosargs
- @permittedKwargs({})
+ @noKwargs
def target_list(self, args, kwargs):
return self.held_object.cm_interpreter.target_list()
@noPosargs
- @permittedKwargs({})
+ @noKwargs
@FeatureNew('CMakeSubproject.found()', '0.53.2')
def found_method(self, args, kwargs):
return self.held_object is not None
+class CMakeSubprojectOptions(InterpreterObject):
+ def __init__(self) -> None:
+ super().__init__()
+ self.cmake_options = [] # type: T.List[str]
+ self.target_options = TargetOptions()
+
+ self.methods.update(
+ {
+ 'add_cmake_defines': self.add_cmake_defines,
+ 'set_override_option': self.set_override_option,
+ 'set_install': self.set_install,
+ 'append_compile_args': self.append_compile_args,
+ 'append_link_args': self.append_link_args,
+ 'clear': self.clear,
+ }
+ )
+
+ def _get_opts(self, kwargs: dict) -> SingleTargetOptions:
+ if 'target' in kwargs:
+ return self.target_options[kwargs['target']]
+ return self.target_options.global_options
+
+ @noKwargs
+ def add_cmake_defines(self, args, kwargs) -> None:
+ self.cmake_options += cmake_defines_to_args(args)
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def set_override_option(self, args, kwargs) -> None:
+ if len(args) != 2:
+ raise InvalidArguments('set_override_option takes exactly 2 positional arguments')
+ self._get_opts(kwargs).set_opt(args[0], args[1])
+
+ @permittedKwargs({'target'})
+ def set_install(self, args, kwargs) -> None:
+ if len(args) != 1 or not isinstance(args[0], bool):
+ raise InvalidArguments('set_install takes exactly 1 boolean argument')
+ self._get_opts(kwargs).set_install(args[0])
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def append_compile_args(self, args, kwargs) -> None:
+ if len(args) < 2:
+ raise InvalidArguments('append_compile_args takes at least 2 positional arguments')
+ self._get_opts(kwargs).append_args(args[0], args[1:])
+
+ @stringArgs
+ @permittedKwargs({'target'})
+ def append_link_args(self, args, kwargs) -> None:
+ if not args:
+ raise InvalidArguments('append_link_args takes at least 1 positional argument')
+ self._get_opts(kwargs).append_link_args(args)
+
+ @noPosargs
+ @noKwargs
+ def clear(self, args, kwargs) -> None:
+ self.cmake_options.clear()
+ self.target_options = TargetOptions()
+
+
class CmakeModule(ExtensionModule):
cmake_detected = False
cmake_root = None
@@ -286,16 +367,27 @@ class CmakeModule(ExtensionModule):
return res
@FeatureNew('subproject', '0.51.0')
- @permittedKwargs({'cmake_options', 'required'})
+ @FeatureNewKwargs('subproject', '0.55.0', ['options'])
+ @FeatureDeprecatedKwargs('subproject', '0.55.0', ['cmake_options'])
+ @permittedKwargs({'cmake_options', 'required', 'options'})
@stringArgs
def subproject(self, interpreter, state, args, kwargs):
if len(args) != 1:
raise InterpreterException('Subproject takes exactly one argument')
+ if 'cmake_options' in kwargs and 'options' in kwargs:
+ raise InterpreterException('"options" cannot be used together with "cmake_options"')
dirname = args[0]
subp = interpreter.do_subproject(dirname, 'cmake', kwargs)
if not subp.held_object:
return subp
return CMakeSubprojectHolder(subp, dirname)
+ @FeatureNew('subproject_options', '0.55.0')
+ @noKwargs
+ @noPosargs
+ def subproject_options(self, state, args, kwargs) -> ModuleReturnValue:
+ opts = CMakeSubprojectOptions()
+ return ModuleReturnValue(opts, [])
+
def initialize(*args, **kwargs):
return CmakeModule(*args, **kwargs)
diff --git a/test cases/cmake/19 advanced options/main.cpp b/test cases/cmake/19 advanced options/main.cpp
new file mode 100644
index 0000000..6a071cc
--- /dev/null
+++ b/test cases/cmake/19 advanced options/main.cpp
@@ -0,0 +1,18 @@
+#include <iostream>
+#include <cmMod.hpp>
+#include <cmTest.hpp>
+
+using namespace std;
+
+int main(void) {
+ cmModClass obj("Hello");
+ cout << obj.getStr() << endl;
+
+ int v1 = obj.getInt();
+ int v2 = getTestInt();
+ if (v1 != ((1 + v2) * 2)) {
+ cerr << "Number test failed" << endl;
+ return 1;
+ }
+ return 0;
+}
diff --git a/test cases/cmake/19 advanced options/meson.build b/test cases/cmake/19 advanced options/meson.build
new file mode 100644
index 0000000..6332ca4
--- /dev/null
+++ b/test cases/cmake/19 advanced options/meson.build
@@ -0,0 +1,29 @@
+project('cmake_set_opt', ['c', 'cpp'])
+
+comp = meson.get_compiler('cpp')
+if comp.get_argument_syntax() == 'msvc'
+ error('MESON_SKIP_TEST: MSVC is not supported because it does not support C++11')
+endif
+
+cm = import('cmake')
+opts = cm.subproject_options()
+
+opts.add_cmake_defines({'SOME_CMAKE_VAR': 'something', 'SOME_OTHER_VAR': true})
+
+opts.set_override_option('cpp_std', 'c++11') # Global is C++11
+opts.set_override_option('cpp_std', 'c++14', target: 'cmModLib++') # Override it with C++14 for cmModLib++
+
+opts.append_compile_args('cpp', '-DMESON_GLOBAL_FLAG=1')
+opts.append_compile_args('cpp', ['-DMESON_SPECIAL_FLAG1=1', ['-DMESON_SPECIAL_FLAG2=1']], target: 'cmModLib++')
+opts.append_compile_args('cpp', '-DMESON_MAGIC_INT=42', target: 'cmModLib++')
+opts.append_compile_args('cpp', [[[['-DMESON_MAGIC_INT=20']]]], target: 'cmTestLib')
+
+opts.set_install(false)
+opts.set_install(true, target: 'testEXE')
+
+sp = cm.subproject('cmOpts', options: opts)
+dep1 = sp.dependency('cmModLib++')
+dep2 = sp.dependency('cmTestLib')
+
+exe1 = executable('main', ['main.cpp'], dependencies: [dep1, dep2])
+test('test1', exe1)
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/CMakeLists.txt b/test cases/cmake/19 advanced options/subprojects/cmOpts/CMakeLists.txt
new file mode 100644
index 0000000..584841e
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/CMakeLists.txt
@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.7)
+
+project(CmOpts)
+
+set(CMAKE_CXX_STANDARD 98)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+if(NOT "${SOME_CMAKE_VAR}" STREQUAL "something")
+ message(FATAL_ERROR "Setting the CMake var failed")
+endif()
+
+add_library(cmModLib++ STATIC cmMod.cpp)
+add_library(cmTestLib STATIC cmTest.cpp)
+add_executable(testEXE main.cpp)
+
+target_link_libraries(testEXE cmModLib++)
+
+install(TARGETS cmTestLib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin)
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.cpp b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.cpp
new file mode 100644
index 0000000..7651b60
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.cpp
@@ -0,0 +1,31 @@
+#include "cmMod.hpp"
+
+using namespace std;
+
+#if __cplusplus < 201402L
+#error "At least C++14 is required"
+#endif
+
+#ifndef MESON_GLOBAL_FLAG
+#error "MESON_GLOBAL_FLAG was not set"
+#endif
+
+#ifndef MESON_SPECIAL_FLAG1
+#error "MESON_SPECIAL_FLAG1 was not set"
+#endif
+
+#ifndef MESON_SPECIAL_FLAG2
+#error "MESON_SPECIAL_FLAG2 was not set"
+#endif
+
+cmModClass::cmModClass(string foo) {
+ str = foo + " World";
+}
+
+string cmModClass::getStr() const {
+ return str;
+}
+
+int cmModClass::getInt() const {
+ return MESON_MAGIC_INT;
+}
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.hpp b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.hpp
new file mode 100644
index 0000000..0748936
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmMod.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <string>
+
+class cmModClass {
+private:
+ std::string str;
+
+public:
+ cmModClass(std::string foo);
+
+ std::string getStr() const;
+ int getInt() const;
+};
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.cpp b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.cpp
new file mode 100644
index 0000000..a00cdcd
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.cpp
@@ -0,0 +1,25 @@
+#include "cmTest.hpp"
+
+#if __cplusplus < 201103L
+#error "At least C++11 is required"
+#endif
+
+#if __cplusplus >= 201402L
+#error "At most C++11 is required"
+#endif
+
+#ifndef MESON_GLOBAL_FLAG
+#error "MESON_GLOBAL_FLAG was not set"
+#endif
+
+#ifdef MESON_SPECIAL_FLAG1
+#error "MESON_SPECIAL_FLAG1 *was* set"
+#endif
+
+#ifdef MESON_SPECIAL_FLAG2
+#error "MESON_SPECIAL_FLAG2 *was* set"
+#endif
+
+int getTestInt() {
+ return MESON_MAGIC_INT;
+}
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.hpp b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.hpp
new file mode 100644
index 0000000..5a3bf7b
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/cmTest.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+int getTestInt();
diff --git a/test cases/cmake/19 advanced options/subprojects/cmOpts/main.cpp b/test cases/cmake/19 advanced options/subprojects/cmOpts/main.cpp
new file mode 100644
index 0000000..497d1ce
--- /dev/null
+++ b/test cases/cmake/19 advanced options/subprojects/cmOpts/main.cpp
@@ -0,0 +1,10 @@
+#include <iostream>
+#include "cmMod.hpp"
+
+using namespace std;
+
+int main(void) {
+ cmModClass obj("Hello (LIB TEST)");
+ cout << obj.getStr() << endl;
+ return 0;
+}
diff --git a/test cases/cmake/19 advanced options/test.json b/test cases/cmake/19 advanced options/test.json
new file mode 100644
index 0000000..e2d9c05
--- /dev/null
+++ b/test cases/cmake/19 advanced options/test.json
@@ -0,0 +1,8 @@
+{
+ "installed": [
+ {"type": "exe", "file": "usr/bin/cm_testEXE"}
+ ],
+ "tools": {
+ "cmake": ">=3.11"
+ }
+}