diff --git a/docs/markdown/Build-options.md b/docs/markdown/Build-options.md index 86f100194..4b66cbfc5 100644 --- a/docs/markdown/Build-options.md +++ b/docs/markdown/Build-options.md @@ -123,6 +123,32 @@ only the few they don't want, if any. This type is available since version 0.47.0 +## Deprecated options + +Since *0.60.0* + +Project options can be marked as deprecated and Meson will warn when user sets a +value to it. It is also possible to deprecate only some of the choices, and map +deprecated values to a new value. + +```meson +# Option fully deprecated, it warns when any value is set. +option('o1', type: 'boolean', deprecated: true) + +# One of the choices is deprecated, it warns only when 'a' is in the list of values. +option('o2', type: 'array', choices: ['a', 'b'], deprecated: ['a']) + +# One of the choices is deprecated, it warns only when 'a' is in the list of values +# and replace it by 'c'. +option('o3', type: 'array', choices: ['a', 'b', 'c'], deprecated: {'a': 'c'}) + +# A boolean option has been replaced by a feature, old true/false values are remapped. +option('o4', type: 'feature', deprecated: {'true': 'enabled', 'false': 'disabled'}) + +# A feature option has been replaced by a boolean, enabled/disabled/auto values are remapped. +option('o5', type: 'boolean', deprecated: {'enabled': 'true', 'disabled': 'false', 'auto': 'false'}) +``` + ## Using build options ```meson diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 4c7b24c92..7782bd1bf 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -72,6 +72,10 @@ class UserOption(T.Generic[_T], HoldableObject): if not isinstance(yielding, bool): raise MesonException('Value of "yielding" must be a boolean.') self.yielding = yielding + self.deprecated: T.Union[bool, T.Dict[str, str], T.List[str]] = False + + def listify(self, value: T.Any) -> T.List[T.Any]: + return [value] def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bool]]]: assert isinstance(self.value, (str, int, bool, list)) @@ -205,7 +209,7 @@ class UserArrayOption(UserOption[T.List[str]]): self.allow_dups = allow_dups self.value = self.validate_value(value, user_input=user_input) - def validate_value(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]: + def listify(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]: # User input is for options defined on the command line (via -D # options). Users can put their input in as a comma separated # string, but for defining options in meson_options.txt the format @@ -230,6 +234,10 @@ class UserArrayOption(UserOption[T.List[str]]): newvalue = value else: raise MesonException(f'"{newvalue}" should be a string array, but it is not') + return newvalue + + def validate_value(self, value: T.Union[str, T.List[str]], user_input: bool = True) -> T.List[str]: + newvalue = self.listify(value, user_input) if not self.allow_dups and len(set(newvalue)) != len(newvalue): msg = 'Duplicated values in array option is deprecated. ' \ @@ -629,10 +637,28 @@ class CoreData: value = self.sanitize_dir_option_value(prefix, key, value) try: - self.options[key].set_value(value) + opt = self.options[key] except KeyError: raise MesonException(f'Tried to set unknown builtin option {str(key)}') + if opt.deprecated is True: + mlog.deprecation(f'Option {key.name!r} is deprecated') + elif isinstance(opt.deprecated, list): + for v in opt.listify(value): + if v in opt.deprecated: + mlog.deprecation(f'Option {key.name!r} value {v!r} is deprecated') + elif isinstance(opt.deprecated, dict): + def replace(v): + newvalue = opt.deprecated.get(v) + if newvalue is not None: + mlog.deprecation(f'Option {key.name!r} value {v!r} is replaced by {newvalue!r}') + return newvalue + return v + newvalue = [replace(v) for v in opt.listify(value)] + value = ','.join(newvalue) + + opt.set_value(value) + if key.name == 'buildtype': self._set_others_from_buildtype(value) elif key.name in {'wrap_mode', 'force_fallback_for'}: diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 74b0a56a8..d7565f27b 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -32,6 +32,7 @@ if T.TYPE_CHECKING: 'value': object, 'min': T.Optional[int], 'max': T.Optional[int], + 'deprecated': T.Union[bool, T.Dict[str, str], T.List[str]], }) ParserArgs = TypedDict('ParserArgs', { 'yield': bool, @@ -167,6 +168,8 @@ class OptionInterpreter: KwargInfo('value', object), KwargInfo('min', (int, type(None))), KwargInfo('max', (int, type(None))), + KwargInfo('deprecated', (bool, ContainerTypeInfo(dict, str), ContainerTypeInfo(list, str)), + default=False, since='0.60.0') ) @typed_pos_args('option', str) def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None: @@ -187,6 +190,7 @@ class OptionInterpreter: known_parser_kwargs = {'value', 'choices', 'yield', 'min', 'max'} parser_kwargs = {k: v for k, v in kwargs.items() if k in known_parser_kwargs and v is not None} opt = parser(description, T.cast('ParserArgs', parser_kwargs)) + opt.deprecated = kwargs['deprecated'] key = mesonlib.OptionKey(opt_name, self.subproject) if key in self.options: diff --git a/test cases/common/245 deprecated option/meson.build b/test cases/common/245 deprecated option/meson.build new file mode 100644 index 000000000..5102fd0a2 --- /dev/null +++ b/test cases/common/245 deprecated option/meson.build @@ -0,0 +1,15 @@ +project('deprecated options', + default_options: [ + 'o1=false', + 'o2=a,b', + 'o3=a,b', + 'o4=true', + 'o5=auto', + ] +) + +assert(get_option('o1') == false) +assert(get_option('o2') == ['a', 'b']) +assert(get_option('o3') == ['c', 'b']) +assert(get_option('o4').enabled()) +assert(get_option('o5') == false) diff --git a/test cases/common/245 deprecated option/meson_options.txt b/test cases/common/245 deprecated option/meson_options.txt new file mode 100644 index 000000000..5814531aa --- /dev/null +++ b/test cases/common/245 deprecated option/meson_options.txt @@ -0,0 +1,15 @@ +# Option fully deprecated, it warns when any value is set. +option('o1', type: 'boolean', deprecated: true) + +# One of the choices is deprecated, it warns only when 'a' is in the list of values. +option('o2', type: 'array', choices: ['a', 'b'], deprecated: ['a']) + +# One of the choices is deprecated, it warns only when 'a' is in the list of values +# and replace it by 'c'. +option('o3', type: 'array', choices: ['a', 'b', 'c'], deprecated: {'a': 'c'}) + +# A boolean option has been replaced by a feature, old true/false values are remapped. +option('o4', type: 'feature', deprecated: {'true': 'enabled', 'false': 'disabled'}) + +# A feature option has been replaced by a boolean, enabled/disabled/auto values are remapped. +option('o5', type: 'boolean', deprecated: {'enabled': 'true', 'disabled': 'false', 'auto': 'false'}) diff --git a/test cases/common/245 deprecated option/test.json b/test cases/common/245 deprecated option/test.json new file mode 100644 index 000000000..c2f2ca325 --- /dev/null +++ b/test cases/common/245 deprecated option/test.json @@ -0,0 +1,29 @@ +{ + "stdout": [ + { + "line": ".*DEPRECATION: Option 'o1' is deprecated", + "match": "re", + "count": 1 + }, + { + "line": ".*DEPRECATION: Option 'o2' value 'a' is deprecated", + "match": "re", + "count": 1 + }, + { + "line": ".*DEPRECATION: Option 'o3' value 'a' is replaced by 'c'", + "match": "re", + "count": 1 + }, + { + "line": ".*DEPRECATION: Option 'o4' value 'true' is replaced by 'enabled'", + "match": "re", + "count": 1 + }, + { + "line": ".*DEPRECATION: Option 'o5' value 'auto' is replaced by 'false'", + "match": "re", + "count": 1 + } + ] +}