diff options
-rw-r--r-- | docs/markdown/Build-options.md | 36 | ||||
-rw-r--r-- | docs/markdown/Reference-manual.md | 15 | ||||
-rw-r--r-- | docs/markdown/snippets/feature_options.md | 10 | ||||
-rw-r--r-- | mesonbuild/coredata.py | 18 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 128 | ||||
-rw-r--r-- | mesonbuild/optinterpreter.py | 8 | ||||
-rw-r--r-- | test cases/common/203 feature option/meson.build | 47 | ||||
-rw-r--r-- | test cases/common/203 feature option/meson_options.txt | 3 | ||||
-rw-r--r-- | test cases/common/204 feature option disabled/meson.build | 23 | ||||
-rw-r--r-- | test cases/common/204 feature option disabled/meson_options.txt | 3 |
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') |