aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Build-options.md36
-rw-r--r--docs/markdown/Reference-manual.md15
-rw-r--r--docs/markdown/snippets/feature_options.md10
-rw-r--r--mesonbuild/coredata.py18
-rw-r--r--mesonbuild/interpreter.py128
-rw-r--r--mesonbuild/optinterpreter.py8
-rw-r--r--test cases/common/203 feature option/meson.build47
-rw-r--r--test cases/common/203 feature option/meson_options.txt3
-rw-r--r--test cases/common/204 feature option disabled/meson.build23
-rw-r--r--test cases/common/204 feature option disabled/meson_options.txt3
10 files changed, 263 insertions, 28 deletions
diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md
index ec4a3bb..0093a1b 100644
--- a/docs/markdown/Build-options.md
+++ b/docs/markdown/Build-options.md
@@ -19,6 +19,7 @@ option('combo_opt', type : 'combo', choices : ['one', 'two', 'three'], value : '
option('integer_opt', type : 'integer', min : 0, max : 5, value : 3) # Since 0.45.0
option('free_array_opt', type : 'array', value : ['one', 'two'])
option('array_opt', type : 'array', choices : ['one', 'two', 'three'], value : ['one', 'two'])
+option('some_feature', type : 'feature', value : 'enabled')
```
## Build option types
@@ -62,6 +63,41 @@ default.
This type is available since version 0.44.0
+### Features
+
+A `feature` option has three states: `enabled`, `disabled` or `auto`. It is intended
+to be passed as value for the `required` keyword argument of most functions.
+Currently supported in
+[`dependency()`](Reference-manual.md#dependency),
+[`find_library()`](Reference-manual.md#compiler-object),
+[`find_program()`](Reference-manual.md#find_program) and
+[`add_languages()`](Reference-manual.md#add_languages) functions.
+
+- `enabled` is the same as passing `required : true`.
+- `auto` is the same as passing `required : false`.
+- `disabled` do not look for the dependency and always return 'not-found'.
+
+When getting the value of this type of option using `get_option()`, a special
+object is returned instead of the string representation of the option's value.
+That object has three methods returning boolean and taking no argument:
+`enabled()`, `disabled()`, and `auto()`.
+
+```meson
+d = dependency('foo', required : get_option('myfeature'))
+if d.found()
+ app = executable('myapp', 'main.c', dependencies : [d])
+endif
+```
+
+If the value of a `feature` option is set to `auto`, that value is overriden by
+the global `auto_features` option (which defaults to `auto`). This is intended
+to be used by packagers who want to have full control on which dependencies are
+required and which are disabled, and not rely on build-deps being installed
+(at the right version) to get a feature enabled. They could set
+`auto_features=enabled` to enable all features and disable explicitly only the
+few they don't want, if any.
+
+This type is available since version 0.47.0
## Using build options
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md
index 9829781..f75030f 100644
--- a/docs/markdown/Reference-manual.md
+++ b/docs/markdown/Reference-manual.md
@@ -60,7 +60,8 @@ endif
Takes one keyword argument, `required`. It defaults to `true`, which
means that if any of the languages specified is not found, Meson will
halt. Returns true if all languages specified were found and false
-otherwise.
+otherwise. Since *0.47.0* the value of a [`feature`](Build-options.md#features)
+option can also be passed to the `required` keyword argument.
### add_project_arguments()
@@ -354,7 +355,8 @@ otherwise. This function supports the following keyword arguments:
cross compiled binary will run on), usually only needed if you build
a tool to be used during compilation.
- `required`, when set to false, Meson will proceed with the build
- even if the dependency is not found
+ even if the dependency is not found. Since *0.47.0* the value of a
+ [`feature`](Build-options.md#features) option can also be passed.
- `static` tells the dependency provider to try to get static
libraries instead of dynamic ones (note that this is not supported
by all dependency backends)
@@ -540,7 +542,9 @@ Keyword arguments are the following:
abort if no program can be found. If `required` is set to `false`,
Meson continue even if none of the programs can be found. You can
then use the `.found()` method on the returned object to check
- whether it was found or not.
+ whether it was found or not. Since *0.47.0* the value of a
+ [`feature`](Build-options.md#features) option can also be passed to the
+ `required` keyword argument.
- `native` *(since 0.43)* defines how this executable should be searched. By default
it is set to `false`, which causes Meson to first look for the
@@ -675,6 +679,9 @@ handles that as expected, but if you need the absolute path to one of these
e.g. to use in a define etc., you should use `join_paths(get_option('prefix'),
get_option('localstatedir')))`
+For options of type `feature` a special object is returned instead of a string.
+See [`feature` options](Build-options.md#features) documentation for more details.
+
### get_variable()
``` meson
@@ -1564,6 +1571,8 @@ the following methods:
library is searched for in the system library directory
(e.g. /usr/lib). This can be overridden with the `dirs` keyword
argument, which can be either a string or a list of strings.
+ Since *0.47.0* the value of a [`feature`](Build-options.md#features) option
+ can also be passed to the `required` keyword argument.
- `first_supported_argument(list_of_strings)`, given a list of
strings, returns the first argument that passes the `has_argument`
diff --git a/docs/markdown/snippets/feature_options.md b/docs/markdown/snippets/feature_options.md
new file mode 100644
index 0000000..22d9201
--- /dev/null
+++ b/docs/markdown/snippets/feature_options.md
@@ -0,0 +1,10 @@
+## New feature option type
+
+A new type of option can be defined in `meson_options.txt` for the traditional
+`enabled / disabled / auto` tristate. The value of this option can be passed to
+the `required` keyword argument of functions `dependency()`, `find_library()`,
+`find_program()` and `add_languages()`.
+
+A new global option `auto_features` has been added to override the value of all
+`auto` features. It is intended to be used by packagers to have full control on
+which feature must be enabled or disabled.
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 4db8e4a..557aabc 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -178,6 +178,21 @@ class UserArrayOption(UserOption):
', '.join(bad), ', '.join(self.choices)))
return newvalue
+class UserFeatureOption(UserComboOption):
+ static_choices = ['enabled', 'disabled', 'auto']
+
+ def __init__(self, name, description, value, yielding=None):
+ super().__init__(name, description, self.static_choices, value, yielding)
+
+ def is_enabled(self):
+ return self.value == 'enabled'
+
+ def is_disabled(self):
+ return self.value == 'disabled'
+
+ def is_auto(self):
+ return self.value == 'auto'
+
# This class contains all data that must persist over multiple
# invocations of Meson. It is roughly the same thing as
# cmakecache.
@@ -437,6 +452,8 @@ def get_builtin_option_choices(optname):
return builtin_options[optname][2]
elif builtin_options[optname][0] == UserBooleanOption:
return [True, False]
+ elif builtin_options[optname][0] == UserFeatureOption:
+ return UserFeatureOption.static_choices
else:
return None
else:
@@ -549,6 +566,7 @@ builtin_options = {
'stdsplit': [UserBooleanOption, 'Split stdout and stderr in test logs.', True],
'errorlogs': [UserBooleanOption, "Whether to print the logs from failing tests.", True],
'install_umask': [UserUmaskOption, 'Default umask to apply on permissions of installed files.', '022'],
+ 'auto_features': [UserFeatureOption, "Override value of all 'auto' features.", 'auto'],
}
# Special prefix-dependent defaults for installation directories that reside in
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
index 421ddd9..3de58bd 100644
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -62,6 +62,55 @@ class ObjectHolder:
def __repr__(self):
return '<Holder: {!r}>'.format(self.held_object)
+class FeatureOptionHolder(InterpreterObject, ObjectHolder):
+ def __init__(self, env, option):
+ InterpreterObject.__init__(self)
+ ObjectHolder.__init__(self, option)
+ if option.is_auto():
+ self.held_object = env.coredata.builtins['auto_features']
+ self.name = option.name
+ self.methods.update({'enabled': self.enabled_method,
+ 'disabled': self.disabled_method,
+ 'auto': self.auto_method,
+ })
+
+ @noPosargs
+ @permittedKwargs({})
+ def enabled_method(self, args, kwargs):
+ return self.held_object.is_enabled()
+
+ @noPosargs
+ @permittedKwargs({})
+ def disabled_method(self, args, kwargs):
+ return self.held_object.is_disabled()
+
+ @noPosargs
+ @permittedKwargs({})
+ def auto_method(self, args, kwargs):
+ return self.held_object.is_auto()
+
+def extract_required_kwarg(kwargs):
+ val = kwargs.get('required', True)
+ disabled = False
+ required = False
+ feature = None
+ if isinstance(val, FeatureOptionHolder):
+ option = val.held_object
+ feature = val.name
+ if option.is_disabled():
+ disabled = True
+ elif option.is_enabled():
+ required = True
+ elif isinstance(required, bool):
+ required = val
+ else:
+ raise InterpreterException('required keyword argument must be boolean or a feature option')
+
+ # Keep boolean value in kwargs to simplify other places where this kwarg is
+ # checked.
+ kwargs['required'] = required
+
+ return disabled, required, feature
class TryRunResultHolder(InterpreterObject):
def __init__(self, res):
@@ -1337,9 +1386,16 @@ class CompilerHolder(InterpreterObject):
libname = args[0]
if not isinstance(libname, str):
raise InterpreterException('Library name not a string.')
- required = kwargs.get('required', True)
- if not isinstance(required, bool):
- raise InterpreterException('required must be boolean.')
+
+ disabled, required, feature = extract_required_kwarg(kwargs)
+ if disabled:
+ mlog.log('Library', mlog.bold(libname), 'skipped: feature', mlog.bold(feature), 'disabled')
+ lib = dependencies.ExternalLibrary(libname, None,
+ self.environment,
+ self.compiler.language,
+ silent=True)
+ return ExternalLibraryHolder(lib)
+
search_dirs = mesonlib.stringlistify(kwargs.get('dirs', []))
for i in search_dirs:
if not os.path.isabs(i):
@@ -2168,44 +2224,54 @@ external dependencies (including libraries) must go to "dependencies".''')
self.build_def_files += subi.build_def_files
return self.subprojects[dirname]
- @stringArgs
- @noKwargs
- def func_get_option(self, nodes, args, kwargs):
- if len(args) != 1:
- raise InterpreterException('Argument required for get_option.')
- undecorated_optname = optname = args[0]
- if ':' in optname:
- raise InterpreterException('''Having a colon in option name is forbidden, projects are not allowed
-to directly access options of other subprojects.''')
+ def get_option_internal(self, optname):
+ undecorated_optname = optname
try:
- return self.environment.get_coredata().base_options[optname].value
+ return self.coredata.base_options[optname]
except KeyError:
pass
try:
- return self.environment.coredata.get_builtin_option(optname)
- except RuntimeError:
+ return self.coredata.builtins[optname]
+ except KeyError:
pass
try:
- return self.environment.coredata.compiler_options[optname].value
+ return self.coredata.compiler_options[optname]
except KeyError:
pass
if not coredata.is_builtin_option(optname) and self.is_subproject():
optname = self.subproject + ':' + optname
try:
- opt = self.environment.coredata.user_options[optname]
+ opt = self.coredata.user_options[optname]
if opt.yielding and ':' in optname:
# If option not present in superproject, keep the original.
- opt = self.environment.coredata.user_options.get(undecorated_optname, opt)
- return opt.value
+ opt = self.coredata.user_options.get(undecorated_optname, opt)
+ return opt
except KeyError:
pass
# Some base options are not defined in some environments, return the default value.
try:
- return compilers.base_options[optname].value
+ return compilers.base_options[optname]
except KeyError:
pass
raise InterpreterException('Tried to access unknown option "%s".' % optname)
+ @stringArgs
+ @noKwargs
+ def func_get_option(self, nodes, args, kwargs):
+ if len(args) != 1:
+ raise InterpreterException('Argument required for get_option.')
+ optname = args[0]
+ if ':' in optname:
+ raise InterpreterException('Having a colon in option name is forbidden, '
+ 'projects are not allowed to directly access '
+ 'options of other subprojects.')
+ opt = self.get_option_internal(optname)
+ if isinstance(opt, coredata.UserFeatureOption):
+ return FeatureOptionHolder(self.environment, opt)
+ elif isinstance(opt, coredata.UserOption):
+ return opt.value
+ return opt
+
@noKwargs
def func_configuration_data(self, node, args, kwargs):
if args:
@@ -2350,7 +2416,12 @@ to directly access options of other subprojects.''')
@permittedKwargs(permitted_kwargs['add_languages'])
@stringArgs
def func_add_languages(self, node, args, kwargs):
- return self.add_languages(args, kwargs.get('required', True))
+ disabled, required, feature = extract_required_kwarg(kwargs)
+ if disabled:
+ for lang in sorted(args, key=compilers.sort_clike):
+ mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return False
+ return self.add_languages(args, required)
def get_message_string_arg(self, node):
# reduce arguments again to avoid flattening posargs
@@ -2604,7 +2675,12 @@ to directly access options of other subprojects.''')
def func_find_program(self, node, args, kwargs):
if not args:
raise InterpreterException('No program name specified.')
- required = kwargs.get('required', True)
+
+ disabled, required, feature = extract_required_kwarg(kwargs)
+ if disabled:
+ mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return ExternalProgramHolder(dependencies.NonExistingExternalProgram())
+
if not isinstance(required, bool):
raise InvalidArguments('"required" argument must be a boolean.')
use_native = kwargs.get('native', False)
@@ -2699,11 +2775,13 @@ to directly access options of other subprojects.''')
@permittedKwargs(permitted_kwargs['dependency'])
def func_dependency(self, node, args, kwargs):
self.validate_arguments(args, 1, [str])
- required = kwargs.get('required', True)
- if not isinstance(required, bool):
- raise DependencyException('Keyword "required" must be a boolean.')
name = args[0]
+ disabled, required, feature = extract_required_kwarg(kwargs)
+ if disabled:
+ mlog.log('Dependency', mlog.bold(name), 'skipped: feature', mlog.bold(feature), 'disabled')
+ return DependencyHolder(NotFoundDependency(self.environment))
+
if name == '':
if required:
raise InvalidArguments('Dependency is both required and not-found')
diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py
index 4207f45..cd3139b 100644
--- a/mesonbuild/optinterpreter.py
+++ b/mesonbuild/optinterpreter.py
@@ -116,11 +116,19 @@ def string_array_parser(name, description, kwargs):
choices=choices,
yielding=kwargs.get('yield', coredata.default_yielding))
+@permitted_kwargs({'value', 'yield'})
+def FeatureParser(name, description, kwargs):
+ return coredata.UserFeatureOption(name,
+ description,
+ kwargs.get('value', 'enabled'),
+ yielding=kwargs.get('yield', coredata.default_yielding))
+
option_types = {'string': StringParser,
'boolean': BooleanParser,
'combo': ComboParser,
'integer': IntegerParser,
'array': string_array_parser,
+ 'feature': FeatureParser,
}
class OptionInterpreter:
diff --git a/test cases/common/203 feature option/meson.build b/test cases/common/203 feature option/meson.build
new file mode 100644
index 0000000..ef3fa22
--- /dev/null
+++ b/test cases/common/203 feature option/meson.build
@@ -0,0 +1,47 @@
+project('feature user option', 'c')
+
+feature_opts = get_option('auto_features')
+required_opt = get_option('required')
+optional_opt = get_option('optional')
+disabled_opt = get_option('disabled')
+
+assert(not feature_opts.enabled(), 'Should be auto option')
+assert(not feature_opts.disabled(), 'Should be auto option')
+assert(feature_opts.auto(), 'Should be auto option')
+
+assert(required_opt.enabled(), 'Should be enabled option')
+assert(not required_opt.disabled(), 'Should be enabled option')
+assert(not required_opt.auto(), 'Should be enabled option')
+
+assert(not optional_opt.enabled(), 'Should be auto option')
+assert(not optional_opt.disabled(), 'Should be auto option')
+assert(optional_opt.auto(), 'Should be auto option')
+
+assert(not disabled_opt.enabled(), 'Should be disabled option')
+assert(disabled_opt.disabled(), 'Should be disabled option')
+assert(not disabled_opt.auto(), 'Should be disabled option')
+
+dep = dependency('threads', required : required_opt)
+assert(dep.found(), 'Should find required "threads" dep')
+
+dep = dependency('threads', required : optional_opt)
+assert(dep.found(), 'Should find optional "threads" dep')
+
+dep = dependency('threads', required : disabled_opt)
+assert(not dep.found(), 'Should not find disabled "threads" dep')
+
+dep = dependency('notfounddep', required : optional_opt)
+assert(not dep.found(), 'Should not find optional "notfounddep" dep')
+
+dep = dependency('notfounddep', required : disabled_opt)
+assert(not dep.found(), 'Should not find disabled "notfounddep" dep')
+
+cc = meson.get_compiler('c')
+lib = cc.find_library('m', required : disabled_opt)
+assert(not lib.found(), 'Should not find "m" library')
+
+cp = find_program('cp', required : disabled_opt)
+assert(not cp.found(), 'Should not find "cp" program')
+
+found = add_languages('cpp', required : disabled_opt)
+assert(not found, 'Should not find "cpp" language')
diff --git a/test cases/common/203 feature option/meson_options.txt b/test cases/common/203 feature option/meson_options.txt
new file mode 100644
index 0000000..063a35f
--- /dev/null
+++ b/test cases/common/203 feature option/meson_options.txt
@@ -0,0 +1,3 @@
+option('required', type : 'feature', value : 'enabled', description : 'An required feature')
+option('optional', type : 'feature', value : 'auto', description : 'An optional feature')
+option('disabled', type : 'feature', value : 'disabled', description : 'A disabled feature')
diff --git a/test cases/common/204 feature option disabled/meson.build b/test cases/common/204 feature option disabled/meson.build
new file mode 100644
index 0000000..1a83187
--- /dev/null
+++ b/test cases/common/204 feature option disabled/meson.build
@@ -0,0 +1,23 @@
+project('feature user option', 'c',
+ default_options : ['auto_features=disabled'])
+
+feature_opts = get_option('auto_features')
+required_opt = get_option('required')
+optional_opt = get_option('optional')
+disabled_opt = get_option('disabled')
+
+assert(not feature_opts.enabled(), 'Should be disabled option')
+assert(feature_opts.disabled(), 'Should be disabled option')
+assert(not feature_opts.auto(), 'Should be disabled option')
+
+assert(required_opt.enabled(), 'Should be enabled option')
+assert(not required_opt.disabled(), 'Should be enabled option')
+assert(not required_opt.auto(), 'Should be enabled option')
+
+assert(not optional_opt.enabled(), 'Auto feature should be disabled')
+assert(optional_opt.disabled(), 'Auto feature should be disabled')
+assert(not optional_opt.auto(), 'Auto feature should be disabled')
+
+assert(not disabled_opt.enabled(), 'Should be disabled option')
+assert(disabled_opt.disabled(), 'Should be disabled option')
+assert(not disabled_opt.auto(), 'Should be disabled option')
diff --git a/test cases/common/204 feature option disabled/meson_options.txt b/test cases/common/204 feature option disabled/meson_options.txt
new file mode 100644
index 0000000..063a35f
--- /dev/null
+++ b/test cases/common/204 feature option disabled/meson_options.txt
@@ -0,0 +1,3 @@
+option('required', type : 'feature', value : 'enabled', description : 'An required feature')
+option('optional', type : 'feature', value : 'auto', description : 'An optional feature')
+option('disabled', type : 'feature', value : 'disabled', description : 'A disabled feature')