Skip to content

Commit

Permalink
Merge pull request #7231 from mensinda/cmOverride
Browse files Browse the repository at this point in the history
cmake: Add more advanced subproject configuration options
  • Loading branch information
jpakkane authored Jun 30, 2020
2 parents b6981bd + 0e98a76 commit 64f3661
Show file tree
Hide file tree
Showing 16 changed files with 470 additions and 23 deletions.
77 changes: 74 additions & 3 deletions docs/markdown/CMake-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
17 changes: 17 additions & 0 deletions docs/markdown/snippets/cmake.md
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 4 additions & 1 deletion mesonbuild/cmake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
'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
Expand Down
95 changes: 95 additions & 0 deletions mesonbuild/cmake/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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', '')
Expand Down Expand Up @@ -163,3 +183,78 @@ def log(self) -> None:
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
21 changes: 13 additions & 8 deletions mesonbuild/cmake/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -993,7 +993,7 @@ def analyse(self) -> None:

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')

Expand Down Expand Up @@ -1154,21 +1154,26 @@ def process_target(tgt: ConverterTarget):
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':
Expand Down
12 changes: 10 additions & 2 deletions mesonbuild/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2444,7 +2444,7 @@ def holderify(self, item):

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)
Expand Down Expand Up @@ -2867,13 +2867,21 @@ def _do_subproject_cmake(self, dirname, subdir, subdir_abs, default_options, kwa
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():
Expand Down
Loading

0 comments on commit 64f3661

Please sign in to comment.