aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
5 files changed, 223 insertions, 20 deletions
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 a5bf545..57e6e1d 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
@@ -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':
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index b8d4fec..740b0bc 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -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():
diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py
index 6c4098b..dcbeda8 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
@@ -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)