cmake: Add more advanced subproject configuration options

This is done with the new cmake subprojects options object
that is similar to the already exisiting configuration data
object. It is consumed by the new `options` kwarg of the
cmake.subproject function.
pull/7231/head
Daniel Mensinger 5 years ago
parent 2e30afb23b
commit a2f94ca18b
No known key found for this signature in database
GPG Key ID: 54DD94C131E277D4
  1. 5
      mesonbuild/cmake/__init__.py
  2. 95
      mesonbuild/cmake/common.py
  3. 21
      mesonbuild/cmake/interpreter.py
  4. 12
      mesonbuild/interpreter.py
  5. 110
      mesonbuild/modules/cmake.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

@ -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

@ -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
@ -994,7 +994,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')
@ -1158,21 +1158,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':

@ -2440,7 +2440,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)
@ -2851,13 +2851,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():

@ -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
@ -287,16 +368,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)

Loading…
Cancel
Save