From f42f773f598ab7d2041d3150c075f5dde770fcdb Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Wed, 28 Aug 2024 10:35:28 -0700 Subject: [PATCH 01/10] options: fix the annotations of _to_tuple --- mesonbuild/options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 1566f940c98c..aea38ec3e66a 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -142,7 +142,7 @@ def __setstate__(self, state: T.Dict[str, T.Any]) -> None: def __hash__(self) -> int: return self._hash - def _to_tuple(self) -> T.Tuple[str, str, str, MachineChoice, str]: + def _to_tuple(self) -> T.Tuple[str, MachineChoice, str]: return (self.subproject, self.machine, self.name) def __eq__(self, other: object) -> bool: From 13a4b197de76d7099702427939ad524bb44d84d4 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 29 Aug 2024 09:23:05 -0700 Subject: [PATCH 02/10] options: Add a printable_choices method to UserOption This provides a method to get choices for options in a printable form. The goal is to make refactoring options simpler. --- mesonbuild/mintro.py | 7 ++++--- mesonbuild/options.py | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 07ad533d3f05..b8332823573c 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -314,14 +314,15 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD elif isinstance(opt, options.UserBooleanOption): typestr = 'boolean' elif isinstance(opt, options.UserComboOption): - optdict['choices'] = opt.choices + optdict['choices'] = opt.printable_choices() typestr = 'combo' elif isinstance(opt, options.UserIntegerOption): typestr = 'integer' elif isinstance(opt, options.UserArrayOption): typestr = 'array' - if opt.choices: - optdict['choices'] = opt.choices + c = opt.printable_choices() + if c: + optdict['choices'] = c else: raise RuntimeError("Unknown option type") optdict['type'] = typestr diff --git a/mesonbuild/options.py b/mesonbuild/options.py index aea38ec3e66a..2ead98829fe2 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -265,6 +265,11 @@ def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bo assert isinstance(self.value, (str, int, bool, list)) return self.value + def printable_choices(self) -> T.Optional[T.List[str]]: + if not self.choices: + return None + return [str(c) for c in self.choices] + # Check that the input is a valid value and return the # "cleaned" or "native" version. For example the Boolean # option could take the string "true" and return True. From c89c5991009e5a8749f3faf6da8a3beae8a0699f Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 29 Aug 2024 09:26:59 -0700 Subject: [PATCH 03/10] options: Get rid of the invalid _U type, and use UserOption[_T] --- mesonbuild/compilers/compilers.py | 10 +++++----- mesonbuild/options.py | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index cb047be0c14c..5eed8cadbaad 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -20,9 +20,7 @@ EnvironmentException, MesonException, Popen_safe_logged, LibType, TemporaryDirectoryWinProof, ) - from ..options import OptionKey - from ..arglist import CompilerArgs if T.TYPE_CHECKING: @@ -211,19 +209,21 @@ class CompileCheckMode(enum.Enum): MSCRT_VALS = ['none', 'md', 'mdd', 'mt', 'mtd'] + @dataclass -class BaseOption(T.Generic[options._T, options._U]): - opt_type: T.Type[options._U] +class BaseOption(T.Generic[_T]): + opt_type: T.Type[options.UserOption[_T]] description: str default: T.Any = None choices: T.Any = None - def init_option(self, name: OptionKey) -> options._U: + def init_option(self, name: OptionKey) -> options.UserOption[_T]: keywords = {'value': self.default} if self.choices: keywords['choices'] = self.choices return self.opt_type(name.name, self.description, **keywords) + BASE_OPTIONS: T.Mapping[OptionKey, BaseOption] = { OptionKey('b_pch'): BaseOption(options.UserBooleanOption, 'Use precompiled headers', True), OptionKey('b_lto'): BaseOption(options.UserBooleanOption, 'Use link time optimization', False), diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 2ead98829fe2..f7a5913c7f07 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -281,8 +281,6 @@ def set_value(self, newvalue: T.Any) -> bool: self.value = self.validate_value(newvalue) return self.value != oldvalue -_U = T.TypeVar('_U', bound=UserOption[_T]) - class UserStringOption(UserOption[str]): def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, @@ -523,14 +521,14 @@ def validate_value(self, value: T.Union[str, T.List[str]]) -> str: f'Possible values for option "{self.name}" are {self.choices}') -class BuiltinOption(T.Generic[_T, _U]): +class BuiltinOption(T.Generic[_T]): """Class for a builtin option type. There are some cases that are not fully supported yet. """ - def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yielding: bool = True, *, + def __init__(self, opt_type: T.Type[UserOption[_T]], description: str, default: T.Any, yielding: bool = True, *, choices: T.Any = None, readonly: bool = False): self.opt_type = opt_type self.description = description @@ -539,7 +537,7 @@ def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yield self.yielding = yielding self.readonly = readonly - def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U: + def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> UserOption[_T]: """Create an instance of opt_type and return it.""" if value is None: value = self.prefixed_default(name, prefix) From 0d4ed702dbf1beda417c27cecf2c084b3b106fb0 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 29 Aug 2024 10:22:53 -0700 Subject: [PATCH 04/10] compilers: remove Compiler.create_option This saves a *tiny* bit of typing, but at the cost of requiring either the current solution of throwing up our hands and saying "typing is too hard, better to have bugs!" or an extensive amount of `TypedDict`s, `overloads`, and a very new version of mypy. Let's get our type safety back, even if it means writing a little bit more code. --- mesonbuild/compilers/c.py | 40 ++-- mesonbuild/compilers/compilers.py | 7 +- mesonbuild/compilers/cpp.py | 220 ++++++++++++---------- mesonbuild/compilers/cuda.py | 28 +-- mesonbuild/compilers/cython.py | 32 ++-- mesonbuild/compilers/fortran.py | 18 +- mesonbuild/compilers/mixins/emscripten.py | 18 +- mesonbuild/compilers/objc.py | 18 +- mesonbuild/compilers/objcpp.py | 22 ++- mesonbuild/compilers/rust.py | 15 +- 10 files changed, 223 insertions(+), 195 deletions(-) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 819ef8bb8628..5d6f80729556 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -154,13 +154,11 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() if self.info.is_windows() or self.info.is_cygwin(): - self.update_options( - opts, - self.create_option(options.UserArrayOption, - self.form_compileropt_key('winlibs'), - 'Standard Win libraries to link against', - gnu_winlibs), - ) + key = self.form_compileropt_key('winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + gnu_winlibs) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -307,13 +305,11 @@ def get_options(self) -> 'MutableKeyedOptionDictType': assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): - self.update_options( - opts, - self.create_option(options.UserArrayOption, - key.evolve('c_winlibs'), - 'Standard Win libraries to link against', - gnu_winlibs), - ) + key = self.form_compileropt_key('winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + gnu_winlibs) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -449,15 +445,13 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): """Shared methods that apply to MSVC-like C compilers.""" def get_options(self) -> MutableKeyedOptionDictType: - return self.update_options( - super().get_options(), - self.create_option( - options.UserArrayOption, - self.form_compileropt_key('winlibs'), - 'Windows libs to link against.', - msvc_winlibs, - ), - ) + opts = super().get_options() + key = self.form_compileropt_key('winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + msvc_winlibs) + return opts def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a TypeDict to make this work diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 5eed8cadbaad..63186c06a9fb 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -35,9 +35,10 @@ from ..dependencies import Dependency CompilerType = T.TypeVar('CompilerType', bound='Compiler') - _T = T.TypeVar('_T') UserOptionType = T.TypeVar('UserOptionType', bound=options.UserOption) +_T = T.TypeVar('_T') + """This file contains the data files of all compilers Meson knows about. To support a new compiler, add its information below. Also add corresponding autodetection code in detect.py.""" @@ -585,8 +586,8 @@ def gen_import_library_args(self, implibname: str) -> T.List[str]: """ return [] - def create_option(self, option_type: T.Type[UserOptionType], option_key: OptionKey, *args: T.Any, **kwargs: T.Any) -> T.Tuple[OptionKey, UserOptionType]: - return option_key, option_type(f'{self.language}_{option_key.name}', *args, **kwargs) + def make_option_name(self, key: OptionKey) -> str: + return f'{self.language}_{key.name}' @staticmethod def update_options(options: MutableKeyedOptionDictType, *args: T.Tuple[OptionKey, UserOptionType]) -> MutableKeyedOptionDictType: diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index f9ebf08da8e8..30a3578b0bcf 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -240,22 +240,26 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - self.update_options( - opts, - self.create_option(options.UserComboOption, - self.form_compileropt_key('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('rtti'), - 'Enable RTTI', - True), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('debugstl'), - 'STL debug mode', - False), - ) + + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + + key = self.form_compileropt_key('rtti') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Enable RTTI', + True) + + key = self.form_compileropt_key('debugstl') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'STL debug mode', + False) + cppstd_choices = [ 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20', ] @@ -267,13 +271,11 @@ def get_options(self) -> 'MutableKeyedOptionDictType': assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): - self.update_options( - opts, - self.create_option(options.UserArrayOption, - self.form_compileropt_key('winlibs'), - 'Standard Win libraries to link against', - gnu_winlibs), - ) + key = self.form_compileropt_key('winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + gnu_winlibs) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -393,15 +395,15 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) + + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + key = self.form_compileropt_key('std') - self.update_options( - opts, - self.create_option(options.UserComboOption, - key.evolve('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - ) std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True) @@ -442,24 +444,27 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ self.supported_warn_args(gnu_cpp_warning_args))} def get_options(self) -> 'MutableKeyedOptionDictType': - key = self.form_compileropt_key('std') opts = CPPCompiler.get_options(self) - self.update_options( - opts, - self.create_option(options.UserComboOption, - self.form_compileropt_key('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('rtti'), - 'Enable RTTI', - True), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('debugstl'), - 'STL debug mode', - False), - ) + + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + + key = self.form_compileropt_key('rtti') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Enable RTTI', + True) + + key = self.form_compileropt_key('debugstl') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'STL debug mode', + False) + cppstd_choices = [ 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20', @@ -468,17 +473,18 @@ def get_options(self) -> 'MutableKeyedOptionDictType': cppstd_choices.append('c++23') if version_compare(self.version, '>=14.0.0'): cppstd_choices.append('c++26') + key = self.form_compileropt_key('std') std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cppstd_choices, gnu=True) + if self.info.is_windows() or self.info.is_cygwin(): - self.update_options( - opts, - self.create_option(options.UserArrayOption, - key.evolve('cpp_winlibs'), - 'Standard Win libraries to link against', - gnu_winlibs), - ) + key = key.evolve(name='cpp_winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + gnu_winlibs) + return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -571,6 +577,19 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + + key = self.form_compileropt_key('debugstl') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'STL debug mode', + False) + cpp_stds = ['c++98'] if version_compare(self.version, '>=1.20.00'): cpp_stds += ['c++03', 'c++0x', 'c++11'] @@ -588,18 +607,6 @@ def get_options(self) -> 'MutableKeyedOptionDictType': cpp_stds += ['c++20'] key = self.form_compileropt_key('std') - self.update_options( - opts, - self.create_option(options.UserComboOption, - self.form_compileropt_key('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('debugstl'), - 'STL debug mode', - False), - ) std_opt = opts[key] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds, gnu=True) @@ -652,6 +659,26 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) + + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + + key = self.form_compileropt_key('rtti') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Enable RTTI', + True) + + key = self.form_compileropt_key('debugstl') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'STL debug mode', + False) + # Every Unix compiler under the sun seems to accept -std=c++03, # with the exception of ICC. Instead of preventing the user from # globally requesting C++03, we transparently remap it to C++98 @@ -668,24 +695,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': c_stds += ['c++2a'] g_stds += ['gnu++2a'] - key = self.form_compileropt_key('std') - self.update_options( - opts, - self.create_option(options.UserComboOption, - self.form_compileropt_key('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('rtti'), - 'Enable RTTI', - True), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('debugstl'), - 'STL debug mode', - False), - ) - std_opt = opts[key] + std_opt = opts[self.form_compileropt_key('std')] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(c_stds + g_stds) return opts @@ -741,24 +751,28 @@ def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return T.cast('T.List[str]', options.get_value(key)[:]) def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List[str]) -> 'MutableKeyedOptionDictType': - key = self.form_compileropt_key('std') - self.update_options( - opts, - self.create_option(options.UserComboOption, - self.form_compileropt_key('eh'), - 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default'), - self.create_option(options.UserBooleanOption, - self.form_compileropt_key('rtti'), - 'Enable RTTI', - True), - self.create_option(options.UserArrayOption, - self.form_compileropt_key('winlibs'), - 'Windows libs to link against.', - msvc_winlibs), - ) - std_opt = opts[key] + opts = super().get_options() + + key = self.form_compileropt_key('eh') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ exception handling type.', + ['none', 'default', 'a', 's', 'sc'], + 'default') + + key = self.form_compileropt_key('rtti') + opts[key] = options.UserBooleanOption( + self.make_option_name(key), + 'Enable RTTI', + True) + + key = self.form_compileropt_key('winlibs') + opts[key] = options.UserArrayOption( + self.make_option_name(key), + 'Standard Win libraries to link against', + msvc_winlibs) + + std_opt = opts[self.form_compileropt_key('std')] assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds) return opts diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 38a938f24aff..07fda95dd6d5 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -646,18 +646,22 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if version_compare(self.version, self._CPP20_VERSION): cpp_stds += ['c++20'] - return self.update_options( - super().get_options(), - self.create_option(options.UserComboOption, - self.form_compileropt_key('std'), - 'C++ language standard to use with CUDA', - cpp_stds, - 'none'), - self.create_option(options.UserStringOption, - self.form_compileropt_key('ccbindir'), - 'CUDA non-default toolchain directory to use (-ccbin)', - ''), - ) + opts = super().get_options() + + key = self.form_compileropt_key('std') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ language standard to use with CUDA', + cpp_stds, + 'none') + + key = self.form_compileropt_key('ccbindir') + opts[key] = options.UserStringOption( + self.make_option_name(key), + 'CUDA non-default toolchain directory to use (-ccbin)', + '') + + return opts def _to_host_compiler_options(self, master_options: 'KeyedOptionDictType') -> 'KeyedOptionDictType': """ diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 5cc0200458fa..2d0f21d00aa2 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright © 2021 Intel Corporation +# Copyright © 2021-2024 Intel Corporation from __future__ import annotations """Abstraction for Cython language compilers.""" @@ -67,19 +67,23 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], return new def get_options(self) -> 'MutableKeyedOptionDictType': - return self.update_options( - super().get_options(), - self.create_option(options.UserComboOption, - self.form_compileropt_key('version'), - 'Python version to target', - ['2', '3'], - '3'), - self.create_option(options.UserComboOption, - self.form_compileropt_key('language'), - 'Output C or C++ files', - ['c', 'cpp'], - 'c'), - ) + opts = super().get_options() + + key = self.form_compileropt_key('version') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'Python version to target', + ['2', '3'], + '3') + + key = self.form_compileropt_key('language') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'Output C or C++ files', + ['c', 'cpp'], + 'c') + + return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args: T.List[str] = [] diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index a99a95ce37ba..a57c25df8805 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -112,14 +112,16 @@ def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.T return self._has_multi_link_arguments(args, env, 'stop; end program') def get_options(self) -> 'MutableKeyedOptionDictType': - return self.update_options( - super().get_options(), - self.create_option(options.UserComboOption, - self.form_compileropt_key('std'), - 'Fortran language standard to use', - ['none'], - 'none'), - ) + opts = super().get_options() + + key = self.form_compileropt_key('std') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'Fortran language standard to use', + ['none'], + 'none') + + return opts class GnuFortranCompiler(GnuCompiler, FortranCompiler): diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 64315ae96797..fa862056923a 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -57,15 +57,15 @@ def thread_link_flags(self, env: 'Environment') -> T.List[str]: return args def get_options(self) -> coredata.MutableKeyedOptionDictType: - return self.update_options( - super().get_options(), - self.create_option( - options.UserIntegerOption, - OptionKey(f'{self.language}_thread_count', machine=self.for_machine), - 'Number of threads to use in web assembly, set to 0 to disable', - (0, None, 4), # Default was picked at random - ), - ) + opts = super().get_options() + + key = OptionKey(f'{self.language}_thread_count', machine=self.for_machine) + opts[key] = options.UserIntegerOption( + self.make_option_name(key), + 'Number of threads to use in web assembly, set to 0 to disable', + (0, None, 4)) # Default was picked at random + + return opts @classmethod def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 97550c2ea251..284bd4d2cd3b 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -79,14 +79,16 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ 'everything': ['-Weverything']} def get_options(self) -> 'coredata.MutableKeyedOptionDictType': - return self.update_options( - super().get_options(), - self.create_option(options.UserComboOption, - OptionKey('c_std', machine=self.for_machine), - 'C language standard to use', - ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], - 'none'), - ) + opts = super().get_options() + + key = OptionKey('c_std', machine=self.for_machine) + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C language standard to use', + ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], + 'none') + + return opts def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: args = [] diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 973d7bb0cfb8..bfc0367c6bca 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -79,16 +79,18 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ 'everything': ['-Weverything']} def get_options(self) -> coredata.MutableKeyedOptionDictType: - return self.update_options( - super().get_options(), - self.create_option(options.UserComboOption, - OptionKey('cpp_std', machine=self.for_machine), - 'C++ language standard to use', - ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', - 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20', - 'gnu++2b'], - 'none'), - ) + opts = super().get_options() + + key = OptionKey('cpp_std', machine=self.for_machine) + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'C++ language standard to use', + ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', + 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20', + 'gnu++2b'], + 'none') + + return opts def get_option_compile_args(self, options: 'coredata.KeyedOptionDictType') -> T.List[str]: args = [] diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index f09911db642c..fd078a9f88df 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -183,11 +183,16 @@ def use_linker_args(cls, linker: str, version: str) -> T.List[str]: # use_linker_args method instead. def get_options(self) -> MutableKeyedOptionDictType: - return dict((self.create_option(options.UserComboOption, - self.form_compileropt_key('std'), - 'Rust edition to use', - ['none', '2015', '2018', '2021'], - 'none'),)) + opts = super().get_options() + + key = self.form_compileropt_key('std') + opts[key] = options.UserComboOption( + self.make_option_name(key), + 'Rust edition to use', + ['none', '2015', '2018', '2021'], + 'none') + + return opts def get_dependency_compile_args(self, dep: 'Dependency') -> T.List[str]: # Rust doesn't have dependency compile arguments so simply return From aee5c1954473ca672858caeffeb39a8810ee2be5 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 29 Aug 2024 09:47:39 -0700 Subject: [PATCH 05/10] options: use dataclasses for UserOption This reduces code, makes this clearer, and will be a nice step toward the goal of getting everything typesafe --- mesonbuild/compilers/compilers.py | 6 +- mesonbuild/compilers/cpp.py | 24 ++-- mesonbuild/compilers/cuda.py | 5 +- mesonbuild/compilers/cython.py | 8 +- mesonbuild/compilers/fortran.py | 4 +- mesonbuild/compilers/mixins/emscripten.py | 3 +- mesonbuild/compilers/objc.py | 4 +- mesonbuild/compilers/objcpp.py | 4 +- mesonbuild/compilers/rust.py | 4 +- mesonbuild/coredata.py | 3 +- mesonbuild/optinterpreter.py | 16 +-- mesonbuild/options.py | 127 ++++++++++------------ 12 files changed, 100 insertions(+), 108 deletions(-) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 63186c06a9fb..6a1e4a2238b0 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -219,16 +219,16 @@ class BaseOption(T.Generic[_T]): choices: T.Any = None def init_option(self, name: OptionKey) -> options.UserOption[_T]: - keywords = {'value': self.default} + keywords = {} if self.choices: keywords['choices'] = self.choices - return self.opt_type(name.name, self.description, **keywords) + return self.opt_type(name.name, self.description, self.default, **keywords) BASE_OPTIONS: T.Mapping[OptionKey, BaseOption] = { OptionKey('b_pch'): BaseOption(options.UserBooleanOption, 'Use precompiled headers', True), OptionKey('b_lto'): BaseOption(options.UserBooleanOption, 'Use link time optimization', False), - OptionKey('b_lto_threads'): BaseOption(options.UserIntegerOption, 'Use multiple threads for Link Time Optimization', (None, None, 0)), + OptionKey('b_lto_threads'): BaseOption(options.UserIntegerOption, 'Use multiple threads for Link Time Optimization', 0), OptionKey('b_lto_mode'): BaseOption(options.UserComboOption, 'Select between different LTO modes.', 'default', choices=['default', 'thin']), OptionKey('b_thinlto_cache'): BaseOption(options.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False), diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 30a3578b0bcf..d3d5b1b8c390 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -245,8 +245,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -400,8 +400,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('std') std_opt = opts[key] @@ -450,8 +450,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -581,8 +581,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('debugstl') opts[key] = options.UserBooleanOption( @@ -664,8 +664,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -757,8 +757,8 @@ def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ exception handling type.', - ['none', 'default', 'a', 's', 'sc'], - 'default') + 'default', + ['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 07fda95dd6d5..aefcc512bce1 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -648,12 +648,13 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() + # XXX: cpp_std is correct, the annotations are wrong key = self.form_compileropt_key('std') opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ language standard to use with CUDA', - cpp_stds, - 'none') + 'none', + cpp_stds) key = self.form_compileropt_key('ccbindir') opts[key] = options.UserStringOption( diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 2d0f21d00aa2..ba04aea0bced 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -73,15 +73,15 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'Python version to target', - ['2', '3'], - '3') + '3', + ['2', '3']) key = self.form_compileropt_key('language') opts[key] = options.UserComboOption( self.make_option_name(key), 'Output C or C++ files', - ['c', 'cpp'], - 'c') + 'c', + ['c', 'cpp']) return opts diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index a57c25df8805..4ff2be9bee61 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -118,8 +118,8 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'Fortran language standard to use', - ['none'], - 'none') + 'none', + ['none']) return opts diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index fa862056923a..24ffceda63ed 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -63,7 +63,8 @@ def get_options(self) -> coredata.MutableKeyedOptionDictType: opts[key] = options.UserIntegerOption( self.make_option_name(key), 'Number of threads to use in web assembly, set to 0 to disable', - (0, None, 4)) # Default was picked at random + 4, # Default was picked at random + min_value=0) return opts diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 284bd4d2cd3b..0c8e3df33b2a 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -85,8 +85,8 @@ def get_options(self) -> 'coredata.MutableKeyedOptionDictType': opts[key] = options.UserComboOption( self.make_option_name(key), 'C language standard to use', - ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], - 'none') + 'none', + ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17']) return opts diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index bfc0367c6bca..94a1642fb7c8 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -85,10 +85,10 @@ def get_options(self) -> coredata.MutableKeyedOptionDictType: opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ language standard to use', + 'none', ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20', - 'gnu++2b'], - 'none') + 'gnu++2b']) return opts diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index fd078a9f88df..47fc3d18e4b7 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -189,8 +189,8 @@ def get_options(self) -> MutableKeyedOptionDictType: opts[key] = options.UserComboOption( self.make_option_name(key), 'Rust edition to use', - ['none', '2015', '2018', '2021'], - 'none') + 'none', + ['none', '2015', '2018', '2021']) return opts diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 276c3a67807b..0525491f6be3 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -434,7 +434,8 @@ def init_backend_options(self, backend_name: str) -> None: 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', - (0, None, 0))) + 0, + min_value=0)) elif backend_name.startswith('vs'): self.optstore.add_system_option('backend_startup_project', options.UserStringOption( 'backend_startup_project', diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 4688ee4c4f49..99efd511ea79 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -213,7 +213,7 @@ def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None: KwargInfo('value', str, default=''), ) def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> options.UserOption: - return options.UserStringOption(name, description, kwargs['value'], *args) + return options.UserStringOption(name, description, kwargs['value'], None, *args) @typed_kwargs( 'boolean option', @@ -226,7 +226,8 @@ def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPREC ), ) def boolean_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> options.UserOption: - return options.UserBooleanOption(name, description, kwargs['value'], *args) + yielding, deprecated = args + return options.UserBooleanOption(name, description, kwargs['value'], yielding=yielding, deprecated=deprecated) @typed_kwargs( 'combo option', @@ -238,7 +239,7 @@ def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECA value = kwargs['value'] if value is None: value = kwargs['choices'][0] - return options.UserComboOption(name, description, choices, value, *args) + return options.UserComboOption(name, description, value, choices, *args) @typed_kwargs( 'integer option', @@ -253,9 +254,8 @@ def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECA KwargInfo('max', (int, NoneType)), ) def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> options.UserOption: - value = kwargs['value'] - inttuple = (kwargs['min'], kwargs['max'], value) - return options.UserIntegerOption(name, description, inttuple, *args) + return options.UserIntegerOption( + name, description, kwargs['value'], None, *args, min_value=kwargs['min'], max_value=kwargs['max']) @typed_kwargs( 'string array option', @@ -270,8 +270,10 @@ def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _ FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject) else: raise mesonlib.MesonException('Value does not define an array: ' + value) + # XXX: the value of choices is correct, the annotation is wrong. + # the annotation will be fixed in a later commit return options.UserArrayOption(name, description, value, - choices=choices, + choices=choices, # type: ignore[arg-type] yielding=args[0], deprecated=args[1]) diff --git a/mesonbuild/options.py b/mesonbuild/options.py index f7a5913c7f07..af0df4e0ede4 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -7,6 +7,7 @@ from itertools import chain from functools import total_ordering import argparse +import dataclasses import typing as T from .mesonlib import ( @@ -28,7 +29,9 @@ from . import mlog if T.TYPE_CHECKING: - from typing_extensions import TypedDict + from typing_extensions import TypeAlias, TypedDict + + DeprecatedType: TypeAlias = T.Union[bool, str, T.Dict[str, str], T.List[str]] class ArgparseKWs(TypedDict, total=False): @@ -244,19 +247,19 @@ def without_module_prefix(self) -> 'OptionKey': return self +@dataclasses.dataclass class UserOption(T.Generic[_T], HoldableObject): - def __init__(self, name: str, description: str, choices: T.Optional[T.Union[str, T.List[_T]]], - yielding: bool, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__() - self.name = name - self.choices = choices - self.description = description - if not isinstance(yielding, bool): - raise MesonException('Value of "yielding" must be a boolean.') - self.yielding = yielding - self.deprecated = deprecated - self.readonly = False + + name: str + description: str + value_: dataclasses.InitVar[_T] + choices: T.Optional[T.Union[str, T.List[_T]]] = None + yielding: bool = DEFAULT_YIELDING + deprecated: DeprecatedType = False + readonly: bool = dataclasses.field(default=False, init=False) + + def __post_init__(self, value_: _T) -> None: + self.value = self.validate_value(value_) def listify(self, value: T.Any) -> T.List[T.Any]: return [value] @@ -277,27 +280,23 @@ def validate_value(self, value: T.Any) -> _T: raise RuntimeError('Derived option class did not override validate_value.') def set_value(self, newvalue: T.Any) -> bool: - oldvalue = getattr(self, 'value', None) + oldvalue = self.value self.value = self.validate_value(newvalue) return self.value != oldvalue +@dataclasses.dataclass class UserStringOption(UserOption[str]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, None, yielding, deprecated) - self.set_value(value) def validate_value(self, value: T.Any) -> str: if not isinstance(value, str): raise MesonException(f'The value of option "{self.name}" is "{value}", which is not a string.') return value +@dataclasses.dataclass class UserBooleanOption(UserOption[bool]): - def __init__(self, name: str, description: str, value: bool, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, [True, False], yielding, deprecated) - self.set_value(value) + + choices: T.List[bool] = dataclasses.field(default_factory=lambda: [True, False]) def __bool__(self) -> bool: return self.value @@ -313,20 +312,20 @@ def validate_value(self, value: T.Any) -> bool: return False raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') +@dataclasses.dataclass class UserIntegerOption(UserOption[int]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - min_value, max_value, default_value = value - self.min_value = min_value - self.max_value = max_value - c: T.List[str] = [] - if min_value is not None: - c.append('>=' + str(min_value)) - if max_value is not None: - c.append('<=' + str(max_value)) - choices = ', '.join(c) - super().__init__(name, description, choices, yielding, deprecated) - self.set_value(default_value) + + min_value: T.Optional[int] = None + max_value: T.Optional[int] = None + + def __post_init__(self, value_: int) -> None: + super().__post_init__(value_) + choices: T.List[str] = [] + if self.min_value is not None: + choices.append(f'>= {self.min_value!s}') + if self.max_value is not None: + choices.append(f'<= {self.max_value!s}') + self.choices = ', '.join(choices) def validate_value(self, value: T.Any) -> int: if isinstance(value, str): @@ -352,11 +351,11 @@ class OctalInt(int): def __str__(self) -> str: return oct(int(self)) +@dataclasses.dataclass class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]): - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, (0, 0o777, value), yielding, deprecated) - self.choices = ['preserve', '0000-0777'] + + min_value: T.Optional[int] = dataclasses.field(default=0, init=False) + max_value: T.Optional[int] = dataclasses.field(default=0o777, init=False) def printable_value(self) -> str: if self.value == 'preserve': @@ -374,17 +373,8 @@ def toint(self, valuestring: T.Union[str, OctalInt]) -> int: except ValueError as e: raise MesonException(f'Invalid mode for option "{self.name}" {e}') +@dataclasses.dataclass class UserComboOption(UserOption[str]): - def __init__(self, name: str, description: str, choices: T.List[str], value: T.Any, - yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, choices, yielding, deprecated) - if not isinstance(self.choices, list): - raise MesonException(f'Combo choices for option "{self.name}" must be an array.') - for i in self.choices: - if not isinstance(i, str): - raise MesonException(f'Combo choice elements for option "{self.name}" must be strings.') - self.set_value(value) def validate_value(self, value: T.Any) -> str: if value not in self.choices: @@ -400,16 +390,12 @@ def validate_value(self, value: T.Any) -> str: value, _type, self.name, optionsstring)) return value +@dataclasses.dataclass class UserArrayOption(UserOption[T.List[str]]): - def __init__(self, name: str, description: str, value: T.Union[str, T.List[str]], - split_args: bool = False, - allow_dups: bool = False, yielding: bool = DEFAULT_YIELDING, - choices: T.Optional[T.List[str]] = None, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, choices if choices is not None else [], yielding, deprecated) - self.split_args = split_args - self.allow_dups = allow_dups - self.set_value(value) + + value_: dataclasses.InitVar[T.Union[str, T.List[str]]] + split_args: bool = False + allow_dups: bool = False def listify(self, value: T.Any) -> T.List[T.Any]: try: @@ -445,13 +431,12 @@ def extend_value(self, value: T.Union[str, T.List[str]]) -> None: self.set_value(self.value + new) +@dataclasses.dataclass class UserFeatureOption(UserComboOption): - static_choices = ['enabled', 'disabled', 'auto'] - def __init__(self, name: str, description: str, value: T.Any, yielding: bool = DEFAULT_YIELDING, - deprecated: T.Union[bool, str, T.Dict[str, str], T.List[str]] = False): - super().__init__(name, description, self.static_choices, value, yielding, deprecated) - self.name: T.Optional[str] = None # TODO: Refactor options to all store their name + choices: T.List[str] = dataclasses.field( + # Ensure we get a copy with the lambda + default_factory=lambda: ['enabled', 'disabled', 'auto'], init=False) def is_enabled(self) -> bool: return self.value == 'enabled' @@ -462,6 +447,8 @@ def is_disabled(self) -> bool: def is_auto(self) -> bool: return self.value == 'auto' + +@dataclasses.dataclass(init=False) class UserStdOption(UserComboOption): ''' UserOption specific to c_std and cpp_std options. User can set a list of @@ -482,7 +469,7 @@ def __init__(self, lang: str, all_stds: T.List[str]) -> None: # Map a deprecated std to its replacement. e.g. gnu11 -> c11. self.deprecated_stds: T.Dict[str, str] = {} opt_name = 'cpp_std' if lang == 'c++' else f'{lang}_std' - super().__init__(opt_name, f'{lang} language standard to use', ['none'], 'none') + super().__init__(opt_name, f'{lang} language standard to use', 'none', ['none']) def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None: assert all(std in self.all_stds for std in versions) @@ -529,19 +516,21 @@ class BuiltinOption(T.Generic[_T]): """ def __init__(self, opt_type: T.Type[UserOption[_T]], description: str, default: T.Any, yielding: bool = True, *, - choices: T.Any = None, readonly: bool = False): + choices: T.Any = None, readonly: bool = False, **kwargs: object): self.opt_type = opt_type self.description = description self.default = default self.choices = choices self.yielding = yielding self.readonly = readonly + self.kwargs = kwargs def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> UserOption[_T]: """Create an instance of opt_type and return it.""" if value is None: value = self.prefixed_default(name, prefix) - keywords = {'yielding': self.yielding, 'value': value} + keywords = {'yielding': self.yielding, 'value_': value} + keywords.update(self.kwargs) if self.choices: keywords['choices'] = self.choices o = self.opt_type(name.name, self.description, **keywords) @@ -559,8 +548,6 @@ def _argparse_action(self) -> T.Optional[str]: def _argparse_choices(self) -> T.Any: if self.opt_type is UserBooleanOption: return [True, False] - elif self.opt_type is UserFeatureOption: - return UserFeatureOption.static_choices return self.choices @staticmethod @@ -644,7 +631,7 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi (OptionKey('stdsplit'), BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)), (OptionKey('strip'), BuiltinOption(UserBooleanOption, 'Strip targets on install', False)), (OptionKey('unity'), BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])), - (OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))), + (OptionKey('unity_size'), BuiltinOption(UserIntegerOption, 'Unity block size', 4, min_value=2)), (OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything'], yielding=False)), (OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), (OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])), @@ -657,7 +644,7 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi # Python module (OptionKey('python.bytecompile'), - BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))), + BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', 0, min_value=-1, max_value=2)), (OptionKey('python.install_env'), BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])), (OptionKey('python.platlibdir'), From 9c48b1cefbd44913bdfbf44170af003edaaf6f78 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Thu, 29 Aug 2024 15:47:20 -0700 Subject: [PATCH 06/10] options: Add an EnumeratedUserOption class This will allow us to take choices out of the UserOption class, which doesn't actually use this attribute. --- mesonbuild/cmake/interpreter.py | 12 +++---- mesonbuild/compilers/c.py | 14 +++----- mesonbuild/compilers/compilers.py | 13 +++++-- mesonbuild/compilers/cpp.py | 28 +++++++-------- mesonbuild/compilers/cuda.py | 3 +- mesonbuild/compilers/cython.py | 4 +-- mesonbuild/compilers/fortran.py | 15 +++----- mesonbuild/compilers/objc.py | 2 +- mesonbuild/compilers/objcpp.py | 8 +++-- mesonbuild/compilers/rust.py | 2 +- mesonbuild/coredata.py | 2 +- mesonbuild/mconf.py | 2 +- mesonbuild/mintro.py | 2 +- mesonbuild/optinterpreter.py | 15 ++++---- mesonbuild/options.py | 59 +++++++++++++++++++++---------- 15 files changed, 99 insertions(+), 82 deletions(-) diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index fafee86abd4f..27ce54e2074d 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -19,6 +19,7 @@ from .traceparser import CMakeTraceParser from .tracetargets import resolve_cmake_trace_targets from .. import mlog, mesonlib +from .. import options from ..mesonlib import MachineChoice, OrderedSet, path_is_in_root, relative_to_if_possible from ..options import OptionKey from ..mesondata import DataFile @@ -533,17 +534,12 @@ def _all_source_suffixes(self) -> 'ImmutableListProtocol[str]': @lru_cache(maxsize=None) def _all_lang_stds(self, lang: str) -> 'ImmutableListProtocol[str]': try: - res = self.env.coredata.optstore.get_value_object(OptionKey(f'{lang}_std', machine=MachineChoice.BUILD)).choices + opt = self.env.coredata.optstore.get_value_object(OptionKey(f'{lang}_std', machine=MachineChoice.BUILD)) + assert isinstance(opt, (options.UserStdOption, options.UserComboOption)), 'for mypy' + return opt.choices or [] except KeyError: return [] - # TODO: Get rid of this once we have proper typing for options - assert isinstance(res, list) - for i in res: - assert isinstance(i, str) - - return res - def process_inter_target_dependencies(self) -> None: # Move the dependencies from all TRANSFER_DEPENDENCIES_FROM to the target to_process = list(self.depends) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 5d6f80729556..33eddb4e38d2 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -155,7 +155,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() if self.info.is_windows() or self.info.is_cygwin(): key = self.form_compileropt_key('winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', gnu_winlibs) @@ -306,7 +306,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': std_opt.set_versions(stds, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): key = self.form_compileropt_key('winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', gnu_winlibs) @@ -447,7 +447,7 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): def get_options(self) -> MutableKeyedOptionDictType: opts = super().get_options() key = self.form_compileropt_key('winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', msvc_winlibs) @@ -774,9 +774,7 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - c_stds = ['c99'] - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + c_stds + self._update_language_stds(opts, ['c99']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -804,9 +802,7 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) - c_stds = ['c99'] - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + c_stds + self._update_language_stds(opts, ['c99']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 6a1e4a2238b0..21f44be70c34 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1353,6 +1353,15 @@ def get_preprocessor(self) -> Compiler: def form_compileropt_key(self, basename: str) -> OptionKey: return OptionKey(f'{self.language}_{basename}', machine=self.for_machine) + def _update_language_stds(self, opts: MutableKeyedOptionDictType, value: T.List[str]) -> None: + key = self.form_compileropt_key('std') + std = opts[key] + assert isinstance(std, (options.UserStdOption, options.UserComboOption)), 'for mypy' + if 'none' not in value: + value = ['none'] + value + std.choices = value + + def get_global_options(lang: str, comp: T.Type[Compiler], for_machine: MachineChoice, @@ -1368,12 +1377,12 @@ def get_global_options(lang: str, comp_options = env.options.get(comp_key, []) link_options = env.options.get(largkey, []) - cargs = options.UserArrayOption( + cargs = options.UserStringArrayOption( f'{lang}_{argkey.name}', description + ' compiler', comp_options, split_args=True, allow_dups=True) - largs = options.UserArrayOption( + largs = options.UserStringArrayOption( f'{lang}_{largkey.name}', description + ' linker', link_options, split_args=True, allow_dups=True) diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index d3d5b1b8c390..a266a415eb16 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -246,7 +246,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -272,7 +272,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': std_opt.set_versions(cppstd_choices, gnu=True) if self.info.is_windows() or self.info.is_cygwin(): key = self.form_compileropt_key('winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', gnu_winlibs) @@ -401,7 +401,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('std') std_opt = opts[key] @@ -451,7 +451,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -480,7 +480,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': if self.info.is_windows() or self.info.is_cygwin(): key = key.evolve(name='cpp_winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', gnu_winlibs) @@ -582,7 +582,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('debugstl') opts[key] = options.UserBooleanOption( @@ -665,7 +665,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -695,9 +695,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': c_stds += ['c++2a'] g_stds += ['gnu++2a'] - std_opt = opts[self.form_compileropt_key('std')] - assert isinstance(std_opt, options.UserStdOption), 'for mypy' - std_opt.set_versions(c_stds + g_stds) + self._update_language_stds(opts, c_stds + g_stds) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -758,7 +756,7 @@ def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List self.make_option_name(key), 'C++ exception handling type.', 'default', - ['none', 'default', 'a', 's', 'sc']) + choices=['none', 'default', 'a', 's', 'sc']) key = self.form_compileropt_key('rtti') opts[key] = options.UserBooleanOption( @@ -767,7 +765,7 @@ def _get_options_impl(self, opts: 'MutableKeyedOptionDictType', cpp_stds: T.List True) key = self.form_compileropt_key('winlibs') - opts[key] = options.UserArrayOption( + opts[key] = options.UserStringArrayOption( self.make_option_name(key), 'Standard Win libraries to link against', msvc_winlibs) @@ -1039,8 +1037,7 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + self._update_language_stds(opts, []) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -1068,8 +1065,7 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + self._update_language_stds(opts, []) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index aefcc512bce1..6a49d95aeb6d 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -648,13 +648,12 @@ def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() - # XXX: cpp_std is correct, the annotations are wrong key = self.form_compileropt_key('std') opts[key] = options.UserComboOption( self.make_option_name(key), 'C++ language standard to use with CUDA', 'none', - cpp_stds) + choices=cpp_stds) key = self.form_compileropt_key('ccbindir') opts[key] = options.UserStringOption( diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index ba04aea0bced..ed0ab31ad376 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -74,14 +74,14 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'Python version to target', '3', - ['2', '3']) + choices=['2', '3']) key = self.form_compileropt_key('language') opts[key] = options.UserComboOption( self.make_option_name(key), 'Output C or C++ files', 'c', - ['c', 'cpp']) + choices=['c', 'cpp']) return opts diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 4ff2be9bee61..b422547a481a 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -119,7 +119,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': self.make_option_name(key), 'Fortran language standard to use', 'none', - ['none']) + choices=['none']) return opts @@ -149,8 +149,7 @@ def get_options(self) -> 'MutableKeyedOptionDictType': fortran_stds += ['f2008'] if version_compare(self.version, '>=8.0.0'): fortran_stds += ['f2018'] - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + fortran_stds + self._update_language_stds(opts, fortran_stds) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -206,9 +205,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) - fortran_stds = ['f95', 'f2003', 'f2008', 'gnu', 'legacy', 'f2008ts'] - key = self.form_compileropt_key('std') - opts[key].choices = ['none'] + fortran_stds + self._update_language_stds(opts, ['f95', 'f2003', 'f2008', 'gnu', 'legacy', 'f2008ts']) return opts def get_module_outdir_args(self, path: str) -> T.List[str]: @@ -286,8 +283,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) - key = self.form_compileropt_key('std') - opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] + self._update_language_stds(opts, ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: @@ -341,8 +337,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_options(self) -> 'MutableKeyedOptionDictType': opts = FortranCompiler.get_options(self) - key = self.form_compileropt_key('std') - opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] + self._update_language_stds(opts, ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018']) return opts def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 0c8e3df33b2a..1af632fb5a5f 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -86,7 +86,7 @@ def get_options(self) -> 'coredata.MutableKeyedOptionDictType': self.make_option_name(key), 'C language standard to use', 'none', - ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17']) + choices=['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17']) return opts diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 94a1642fb7c8..7038ed66d3c0 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -86,9 +86,11 @@ def get_options(self) -> coredata.MutableKeyedOptionDictType: self.make_option_name(key), 'C++ language standard to use', 'none', - ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', - 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20', - 'gnu++2b']) + choices=[ + 'none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', + 'gnu++98', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++20', + 'gnu++2b', + ]) return opts diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 47fc3d18e4b7..e973d25ddf8c 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -190,7 +190,7 @@ def get_options(self) -> MutableKeyedOptionDictType: self.make_option_name(key), 'Rust edition to use', 'none', - ['none', '2015', '2018', '2021']) + choices=['none', '2015', '2018', '2021']) return opts diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 0525491f6be3..b8702d5e9e93 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -599,7 +599,7 @@ def update_project_options(self, project_options: 'MutableKeyedOptionDictType', oldval = self.optstore.get_value_object(key) if type(oldval) is not type(value): self.optstore.set_value(key, value.value) - elif oldval.choices != value.choices: + elif oldval.printable_choices() != value.printable_choices(): # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 3a6343ba1233..7c2270edcbc3 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -240,7 +240,7 @@ def print_options(self, title: str, opts: 'T.Union[dict[OptionKey, UserOption[An printable_value = '' if isinstance(o, options.UserFeatureOption) and o.is_auto(): printable_value = auto.printable_value() - self.add_option(str(root), o.description, printable_value, o.choices) + self.add_option(str(root), o.description, printable_value, o.printable_choices()) def print_conf(self, pager: bool) -> None: if pager: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index b8332823573c..d13e983c86da 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -318,7 +318,7 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD typestr = 'combo' elif isinstance(opt, options.UserIntegerOption): typestr = 'integer' - elif isinstance(opt, options.UserArrayOption): + elif isinstance(opt, options.UserStringArrayOption): typestr = 'array' c = opt.printable_choices() if c: diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 99efd511ea79..2b36b612174c 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -213,7 +213,7 @@ def func_option(self, args: T.Tuple[str], kwargs: 'FuncOptionArgs') -> None: KwargInfo('value', str, default=''), ) def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> options.UserOption: - return options.UserStringOption(name, description, kwargs['value'], None, *args) + return options.UserStringOption(name, description, kwargs['value'], *args) @typed_kwargs( 'boolean option', @@ -239,7 +239,7 @@ def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECA value = kwargs['value'] if value is None: value = kwargs['choices'][0] - return options.UserComboOption(name, description, value, choices, *args) + return options.UserComboOption(name, description, value, *args, choices) @typed_kwargs( 'integer option', @@ -255,7 +255,7 @@ def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECA ) def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> options.UserOption: return options.UserIntegerOption( - name, description, kwargs['value'], None, *args, min_value=kwargs['min'], max_value=kwargs['max']) + name, description, kwargs['value'], *args, min_value=kwargs['min'], max_value=kwargs['max']) @typed_kwargs( 'string array option', @@ -272,10 +272,11 @@ def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _ raise mesonlib.MesonException('Value does not define an array: ' + value) # XXX: the value of choices is correct, the annotation is wrong. # the annotation will be fixed in a later commit - return options.UserArrayOption(name, description, value, - choices=choices, # type: ignore[arg-type] - yielding=args[0], - deprecated=args[1]) + return options.UserStringArrayOption( + name, description, value, + choices=choices, + yielding=args[0], + deprecated=args[1]) @typed_kwargs( 'feature option', diff --git a/mesonbuild/options.py b/mesonbuild/options.py index af0df4e0ede4..5f5d08bcc8cd 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -253,7 +253,6 @@ class UserOption(T.Generic[_T], HoldableObject): name: str description: str value_: dataclasses.InitVar[_T] - choices: T.Optional[T.Union[str, T.List[_T]]] = None yielding: bool = DEFAULT_YIELDING deprecated: DeprecatedType = False readonly: bool = dataclasses.field(default=False, init=False) @@ -269,9 +268,7 @@ def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bo return self.value def printable_choices(self) -> T.Optional[T.List[str]]: - if not self.choices: - return None - return [str(c) for c in self.choices] + return None # Check that the input is a valid value and return the # "cleaned" or "native" version. For example the Boolean @@ -285,6 +282,17 @@ def set_value(self, newvalue: T.Any) -> bool: return self.value != oldvalue +@dataclasses.dataclass +class EnumeratedUserOption(UserOption[_T]): + + """A generic UserOption that has enumerated values.""" + + choices: T.List[_T] = dataclasses.field(default_factory=list) + + def printable_choices(self) -> T.Optional[T.List[str]]: + return [str(c) for c in self.choices] + + @dataclasses.dataclass class UserStringOption(UserOption[str]): @@ -294,7 +302,7 @@ def validate_value(self, value: T.Any) -> str: return value @dataclasses.dataclass -class UserBooleanOption(UserOption[bool]): +class UserBooleanOption(EnumeratedUserOption[bool]): choices: T.List[bool] = dataclasses.field(default_factory=lambda: [True, False]) @@ -325,7 +333,10 @@ def __post_init__(self, value_: int) -> None: choices.append(f'>= {self.min_value!s}') if self.max_value is not None: choices.append(f'<= {self.max_value!s}') - self.choices = ', '.join(choices) + self.__choices: str = ', '.join(choices) + + def printable_choices(self) -> T.Optional[T.List[str]]: + return [self.__choices] def validate_value(self, value: T.Any) -> int: if isinstance(value, str): @@ -374,7 +385,7 @@ def toint(self, valuestring: T.Union[str, OctalInt]) -> int: raise MesonException(f'Invalid mode for option "{self.name}" {e}') @dataclasses.dataclass -class UserComboOption(UserOption[str]): +class UserComboOption(EnumeratedUserOption[str]): def validate_value(self, value: T.Any) -> str: if value not in self.choices: @@ -388,15 +399,32 @@ def validate_value(self, value: T.Any) -> str: raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.' ' Possible choices are (as string): {}.'.format( value, _type, self.name, optionsstring)) + + assert isinstance(value, str), 'for mypy' return value @dataclasses.dataclass -class UserArrayOption(UserOption[T.List[str]]): +class UserArrayOption(UserOption[T.List[_T]]): - value_: dataclasses.InitVar[T.Union[str, T.List[str]]] + value_: dataclasses.InitVar[T.Union[_T, T.List[_T]]] + choices: T.Optional[T.List[_T]] = None split_args: bool = False allow_dups: bool = False + def extend_value(self, value: T.Union[str, T.List[str]]) -> None: + """Extend the value with an additional value.""" + new = self.validate_value(value) + self.set_value(self.value + new) + + def printable_choices(self) -> T.Optional[T.List[str]]: + if self.choices is None: + return None + return [str(c) for c in self.choices] + + +@dataclasses.dataclass +class UserStringArrayOption(UserArrayOption[str]): + def listify(self, value: T.Any) -> T.List[T.Any]: try: return listify_array_value(value, self.split_args) @@ -425,11 +453,6 @@ def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: ) return newvalue - def extend_value(self, value: T.Union[str, T.List[str]]) -> None: - """Extend the value with an additional value.""" - new = self.validate_value(value) - self.set_value(self.value + new) - @dataclasses.dataclass class UserFeatureOption(UserComboOption): @@ -469,7 +492,7 @@ def __init__(self, lang: str, all_stds: T.List[str]) -> None: # Map a deprecated std to its replacement. e.g. gnu11 -> c11. self.deprecated_stds: T.Dict[str, str] = {} opt_name = 'cpp_std' if lang == 'c++' else f'{lang}_std' - super().__init__(opt_name, f'{lang} language standard to use', 'none', ['none']) + super().__init__(opt_name, f'{lang} language standard to use', 'none', choices=['none']) def set_versions(self, versions: T.List[str], gnu: bool = False, gnu_deprecated: bool = False) -> None: assert all(std in self.all_stds for std in versions) @@ -635,7 +658,7 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi (OptionKey('warning_level'), BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3', 'everything'], yielding=False)), (OptionKey('werror'), BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), (OptionKey('wrap_mode'), BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback', 'nopromote'])), - (OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), + (OptionKey('force_fallback_for'), BuiltinOption(UserStringArrayOption, 'Force fallback for those subprojects', [])), (OptionKey('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)), # Pkgconfig module @@ -658,8 +681,8 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) BUILTIN_OPTIONS_PER_MACHINE: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('pkg_config_path'), BuiltinOption(UserArrayOption, 'List of additional paths for pkg-config to search', [])), - (OptionKey('cmake_prefix_path'), BuiltinOption(UserArrayOption, 'List of additional prefixes for cmake to search', [])), + (OptionKey('pkg_config_path'), BuiltinOption(UserStringArrayOption, 'List of additional paths for pkg-config to search', [])), + (OptionKey('cmake_prefix_path'), BuiltinOption(UserStringArrayOption, 'List of additional prefixes for cmake to search', [])), ]) # Special prefix-dependent defaults for installation directories that reside in From 1127964a8f3fb8c115d644ece6b3a3954ec40555 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 30 Aug 2024 10:52:17 -0700 Subject: [PATCH 07/10] options: fix typing of add_to_argparse Which wants a string, but then passes that string to a function that wants an OptionKey, which means that we'll always miss the lookup in BULITIN_DIR_NOPREFIX_OPTIONS, and return the default. The only case this gets used we cast an OptionKey to str, and then pass that. So instead, do the cast inside the function when necessary and pass the OptionKey --- mesonbuild/coredata.py | 6 +++--- mesonbuild/options.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index b8702d5e9e93..356e655b3cb8 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -857,10 +857,10 @@ def save(obj: CoreData, build_dir: str) -> str: def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: for n, b in options.BUILTIN_OPTIONS.items(): - b.add_to_argparse(str(n), parser, '') + b.add_to_argparse(n, parser, '') for n, b in options.BUILTIN_OPTIONS_PER_MACHINE.items(): - b.add_to_argparse(str(n), parser, ' (just for host machine)') - b.add_to_argparse(str(n.as_build()), parser, ' (just for build machine)') + b.add_to_argparse(n, parser, ' (just for host machine)') + b.add_to_argparse(n.as_build(), parser, ' (just for build machine)') parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", help='Set the value of an option, can be used several times to set multiple options.') diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 5f5d08bcc8cd..702c0dd62973 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -589,7 +589,7 @@ def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: pass return self.default - def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: + def add_to_argparse(self, name: OptionKey, parser: argparse.ArgumentParser, help_suffix: str) -> None: kwargs: ArgparseKWs = {} c = self._argparse_choices() @@ -602,9 +602,9 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffi if c and not b: kwargs['choices'] = c kwargs['default'] = argparse.SUPPRESS - kwargs['dest'] = name + kwargs['dest'] = str(name) - cmdline_name = self.argparse_name_to_arg(name) + cmdline_name = self.argparse_name_to_arg(str(name)) parser.add_argument(cmdline_name, help=h + help_suffix, **kwargs) From b6425dfbfa3afbe62a45d4873227aafa008cfa9c Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 30 Aug 2024 11:12:57 -0700 Subject: [PATCH 08/10] options: split UserIntegerOption and UserUmaskOption They are very similar, but they are not exactly the same. By splitting them we can get full type safety, and run mypy over the options.py file! --- mesonbuild/mintro.py | 4 ++-- mesonbuild/options.py | 45 ++++++++++++++++++++++++++++--------------- run_mypy.py | 1 + 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index d13e983c86da..4afc15830e6d 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -316,7 +316,7 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD elif isinstance(opt, options.UserComboOption): optdict['choices'] = opt.printable_choices() typestr = 'combo' - elif isinstance(opt, options.UserIntegerOption): + elif isinstance(opt, (options.UserIntegerOption, options.UserUmaskOption)): typestr = 'integer' elif isinstance(opt, options.UserStringArrayOption): typestr = 'array' @@ -324,7 +324,7 @@ def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionD if c: optdict['choices'] = c else: - raise RuntimeError("Unknown option type") + raise RuntimeError('Unknown option type: ', type(opt)) optdict['type'] = typestr optdict['description'] = opt.description optlist.append(optdict) diff --git a/mesonbuild/options.py b/mesonbuild/options.py index 702c0dd62973..b929e7daa793 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -29,7 +29,7 @@ from . import mlog if T.TYPE_CHECKING: - from typing_extensions import TypeAlias, TypedDict + from typing_extensions import Literal, TypeAlias, TypedDict DeprecatedType: TypeAlias = T.Union[bool, str, T.Dict[str, str], T.List[str]] @@ -320,13 +320,16 @@ def validate_value(self, value: T.Any) -> bool: return False raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') -@dataclasses.dataclass -class UserIntegerOption(UserOption[int]): - min_value: T.Optional[int] = None - max_value: T.Optional[int] = None +class _UserIntegerBase(UserOption[_T]): + + min_value: T.Optional[int] + max_value: T.Optional[int] + + if T.TYPE_CHECKING: + def toint(self, v: str) -> int: ... - def __post_init__(self, value_: int) -> None: + def __post_init__(self, value_: _T) -> None: super().__post_init__(value_) choices: T.List[str] = [] if self.min_value is not None: @@ -338,16 +341,23 @@ def __post_init__(self, value_: int) -> None: def printable_choices(self) -> T.Optional[T.List[str]]: return [self.__choices] - def validate_value(self, value: T.Any) -> int: + def validate_value(self, value: T.Any) -> _T: if isinstance(value, str): - value = self.toint(value) + value = T.cast('_T', self.toint(value)) if not isinstance(value, int): raise MesonException(f'Value {value!r} for option "{self.name}" is not an integer.') if self.min_value is not None and value < self.min_value: raise MesonException(f'Value {value} for option "{self.name}" is less than minimum value {self.min_value}.') if self.max_value is not None and value > self.max_value: raise MesonException(f'Value {value} for option "{self.name}" is more than maximum value {self.max_value}.') - return value + return T.cast('_T', value) + + +@dataclasses.dataclass +class UserIntegerOption(_UserIntegerBase[int]): + + min_value: T.Optional[int] = None + max_value: T.Optional[int] = None def toint(self, valuestring: str) -> int: try: @@ -355,6 +365,7 @@ def toint(self, valuestring: str) -> int: except ValueError: raise MesonException(f'Value string "{valuestring}" for option "{self.name}" is not convertible to an integer.') + class OctalInt(int): # NinjaBackend.get_user_option_args uses str() to converts it to a command line option # UserUmaskOption.toint() uses int(str, 8) to convert it to an integer @@ -362,28 +373,30 @@ class OctalInt(int): def __str__(self) -> str: return oct(int(self)) + @dataclasses.dataclass -class UserUmaskOption(UserIntegerOption, UserOption[T.Union[str, OctalInt]]): +class UserUmaskOption(_UserIntegerBase[T.Union["Literal['preserve']", OctalInt]]): min_value: T.Optional[int] = dataclasses.field(default=0, init=False) max_value: T.Optional[int] = dataclasses.field(default=0o777, init=False) def printable_value(self) -> str: - if self.value == 'preserve': - return self.value - return format(self.value, '04o') + if isinstance(self.value, int): + return format(self.value, '04o') + return self.value - def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: + def validate_value(self, value: T.Any) -> T.Union[Literal['preserve'], OctalInt]: if value == 'preserve': return 'preserve' return OctalInt(super().validate_value(value)) - def toint(self, valuestring: T.Union[str, OctalInt]) -> int: + def toint(self, valuestring: str) -> int: try: return int(valuestring, 8) except ValueError as e: raise MesonException(f'Invalid mode for option "{self.name}" {e}') + @dataclasses.dataclass class UserComboOption(EnumeratedUserOption[str]): @@ -581,7 +594,7 @@ def argparse_name_to_arg(name: str) -> str: return '--' + name.replace('_', '-') def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: - if self.opt_type in [UserComboOption, UserIntegerOption]: + if self.opt_type in {UserComboOption, UserIntegerOption, UserUmaskOption}: return self.default try: return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] diff --git a/run_mypy.py b/run_mypy.py index 9a4e241ec833..4f7a6317be57 100755 --- a/run_mypy.py +++ b/run_mypy.py @@ -79,6 +79,7 @@ 'mesonbuild/msetup.py', 'mesonbuild/mtest.py', 'mesonbuild/optinterpreter.py', + 'mesonbuild/options.py', 'mesonbuild/programs.py', ] additional = [ From 58c432799da4119d1166e9a7bf8f2db1fa229ab0 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Tue, 3 Sep 2024 09:02:15 -0700 Subject: [PATCH 09/10] options: Add a function to compare different option choices This allows us to hide type differences inside the options module, but still get accurate change information. --- mesonbuild/coredata.py | 2 +- mesonbuild/options.py | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 356e655b3cb8..746fe3ba29da 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -599,7 +599,7 @@ def update_project_options(self, project_options: 'MutableKeyedOptionDictType', oldval = self.optstore.get_value_object(key) if type(oldval) is not type(value): self.optstore.set_value(key, value.value) - elif oldval.printable_choices() != value.printable_choices(): + elif options.choices_are_different(oldval, value): # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. diff --git a/mesonbuild/options.py b/mesonbuild/options.py index b929e7daa793..b50f77701f9e 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -484,7 +484,31 @@ def is_auto(self) -> bool: return self.value == 'auto' -@dataclasses.dataclass(init=False) +_U = T.TypeVar('_U', bound=UserOption) + + +def choices_are_different(a: _U, b: _U) -> bool: + """Are the choices between two options the same? + + :param a: A UserOption[T] + :param b: A second UserOption[T] + :return: True if the choices have changed, otherwise False + """ + if isinstance(a, EnumeratedUserOption): + # We expect `a` and `b` to be of the same type, but can't really annotate it that way. + assert isinstance(b, EnumeratedUserOption), 'for mypy' + return a.choices != b.choices + elif isinstance(a, UserArrayOption): + # We expect `a` and `b` to be of the same type, but can't really annotate it that way. + assert isinstance(b, UserArrayOption), 'for mypy' + return a.choices != b.choices + elif isinstance(a, _UserIntegerBase): + assert isinstance(b, _UserIntegerBase), 'for mypy' + return a.max_value != b.max_value or a.min_value != b.min_value + + return False + + class UserStdOption(UserComboOption): ''' UserOption specific to c_std and cpp_std options. User can set a list of From 4a69ef008cd301aec6222e1f2539cf14e7f47ba9 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Fri, 6 Sep 2024 11:52:56 -0700 Subject: [PATCH 10/10] options: Replace uses of `UserOption[T.Any]` with a Union of UserOption types The fact that UserOption is generic is really an implementation detail, not something to be used publicly. So by having an `AnyOptionType` alias, we can get better type checking, as can be seen by the patch as a whole. One of the big fixes it replace open-coded equivlalents of `MutableKeydOptionDictType` with that type alias. --- mesonbuild/compilers/compilers.py | 3 +-- mesonbuild/coredata.py | 4 ++-- mesonbuild/mconf.py | 6 ++---- mesonbuild/mintro.py | 4 +--- mesonbuild/optinterpreter.py | 2 +- mesonbuild/options.py | 30 +++++++++++++++++------------- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 21f44be70c34..753e29e25b0d 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -35,7 +35,6 @@ from ..dependencies import Dependency CompilerType = T.TypeVar('CompilerType', bound='Compiler') - UserOptionType = T.TypeVar('UserOptionType', bound=options.UserOption) _T = T.TypeVar('_T') @@ -590,7 +589,7 @@ def make_option_name(self, key: OptionKey) -> str: return f'{self.language}_{key.name}' @staticmethod - def update_options(options: MutableKeyedOptionDictType, *args: T.Tuple[OptionKey, UserOptionType]) -> MutableKeyedOptionDictType: + def update_options(options: MutableKeyedOptionDictType, *args: T.Tuple[OptionKey, options.AnyOptionType]) -> MutableKeyedOptionDictType: options.update(args) return options diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 746fe3ba29da..a5b599221815 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -60,8 +60,8 @@ class SharedCMDOptions(Protocol): cross_file: T.List[str] native_file: T.List[str] - OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] - MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] + OptionDictType = T.Union[T.Dict[str, options.AnyOptionType], 'OptionsView'] + MutableKeyedOptionDictType = T.Dict['OptionKey', options.AnyOptionType] KeyedOptionDictType = T.Union['options.OptionStore', 'OptionsView'] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 7c2270edcbc3..9d65cc26fa21 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -26,8 +26,6 @@ if T.TYPE_CHECKING: from typing_extensions import Protocol - from typing import Any - from .options import UserOption import argparse class CMDOptions(coredata.SharedCMDOptions, Protocol): @@ -189,7 +187,7 @@ def wrap_text(text: LOGLINE, width: int) -> mlog.TV_LoggableList: items = [l[i] if l[i] else ' ' * four_column[i] for i in range(4)] mlog.log(*items) - def split_options_per_subproject(self, options: 'T.Union[dict[OptionKey, UserOption[Any]], coredata.KeyedOptionDictType]') -> T.Dict[str, 'coredata.MutableKeyedOptionDictType']: + def split_options_per_subproject(self, options: T.Union[coredata.MutableKeyedOptionDictType, coredata.KeyedOptionDictType]) -> T.Dict[str, 'coredata.MutableKeyedOptionDictType']: result: T.Dict[str, 'coredata.MutableKeyedOptionDictType'] = {} for k, o in options.items(): if k.subproject: @@ -227,7 +225,7 @@ def add_section(self, section: str) -> None: self._add_line(mlog.normal_yellow(section + ':'), '', '', '') self.print_margin = 2 - def print_options(self, title: str, opts: 'T.Union[dict[OptionKey, UserOption[Any]], coredata.KeyedOptionDictType]') -> None: + def print_options(self, title: str, opts: T.Union[coredata.MutableKeyedOptionDictType, coredata.KeyedOptionDictType]) -> None: if not opts: return if title: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 4afc15830e6d..0d319c1f9aea 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -30,8 +30,6 @@ if T.TYPE_CHECKING: import argparse - from typing import Any - from .options import UserOption from .interpreter import Interpreter from .mparser import BaseNode @@ -305,7 +303,7 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s for s in subprojects: core_options[k.evolve(subproject=s)] = v - def add_keys(opts: 'T.Union[dict[OptionKey, UserOption[Any]], cdata.KeyedOptionDictType]', section: str) -> None: + def add_keys(opts: T.Union[cdata.MutableKeyedOptionDictType, cdata.KeyedOptionDictType], section: str) -> None: for key, opt in sorted(opts.items()): optdict = {'name': str(key), 'value': opt.value, 'section': section, 'machine': key.machine.get_lower_case_name() if coredata.is_per_machine_option(key) else 'any'} diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 2b36b612174c..034e7b483e0b 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -69,7 +69,7 @@ class OptionInterpreter: def __init__(self, optionstore: 'OptionStore', subproject: 'SubProject') -> None: self.options: 'coredata.MutableKeyedOptionDictType' = {} self.subproject = subproject - self.option_types: T.Dict[str, T.Callable[..., options.UserOption]] = { + self.option_types: T.Dict[str, T.Callable[..., options.AnyOptionType]] = { 'string': self.string_parser, 'boolean': self.boolean_parser, 'combo': self.combo_parser, diff --git a/mesonbuild/options.py b/mesonbuild/options.py index b50f77701f9e..d46155bff62f 100644 --- a/mesonbuild/options.py +++ b/mesonbuild/options.py @@ -32,6 +32,10 @@ from typing_extensions import Literal, TypeAlias, TypedDict DeprecatedType: TypeAlias = T.Union[bool, str, T.Dict[str, str], T.List[str]] + AnyOptionType: TypeAlias = T.Union[ + 'UserBooleanOption', 'UserComboOption', 'UserFeatureOption', + 'UserIntegerOption', 'UserStdOption', 'UserStringArrayOption', + 'UserStringOption', 'UserUmaskOption'] class ArgparseKWs(TypedDict, total=False): @@ -734,7 +738,7 @@ def add_to_argparse(self, name: OptionKey, parser: argparse.ArgumentParser, help class OptionStore: def __init__(self) -> None: - self.d: T.Dict['OptionKey', 'UserOption[T.Any]'] = {} + self.d: T.Dict['OptionKey', AnyOptionType] = {} self.project_options: T.Set[OptionKey] = set() self.module_options: T.Set[OptionKey] = set() from .compilers import all_languages @@ -748,35 +752,35 @@ def ensure_key(self, key: T.Union[OptionKey, str]) -> OptionKey: return OptionKey(key) return key - def get_value_object(self, key: T.Union[OptionKey, str]) -> 'UserOption[T.Any]': + def get_value_object(self, key: T.Union[OptionKey, str]) -> AnyOptionType: return self.d[self.ensure_key(key)] def get_value(self, key: T.Union[OptionKey, str]) -> 'T.Any': return self.get_value_object(key).value - def add_system_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]') -> None: + def add_system_option(self, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_key(key) if '.' in key.name: raise MesonException(f'Internal error: non-module option has a period in its name {key.name}.') self.add_system_option_internal(key, valobj) - def add_system_option_internal(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]') -> None: + def add_system_option_internal(self, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_key(key) assert isinstance(valobj, UserOption) self.d[key] = valobj - def add_compiler_option(self, language: str, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]') -> None: + def add_compiler_option(self, language: str, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_key(key) if not key.name.startswith(language + '_'): raise MesonException(f'Internal error: all compiler option names must start with language prefix. ({key.name} vs {language}_)') self.add_system_option(key, valobj) - def add_project_option(self, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]') -> None: + def add_project_option(self, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_key(key) self.d[key] = valobj self.project_options.add(key) - def add_module_option(self, modulename: str, key: T.Union[OptionKey, str], valobj: 'UserOption[T.Any]') -> None: + def add_module_option(self, modulename: str, key: T.Union[OptionKey, str], valobj: AnyOptionType) -> None: key = self.ensure_key(key) if key.name.startswith('build.'): raise MesonException('FATAL internal error: somebody goofed option handling.') @@ -790,7 +794,7 @@ def set_value(self, key: T.Union[OptionKey, str], new_value: 'T.Any') -> bool: return self.d[key].set_value(new_value) # FIXME, this should be removed.or renamed to "change_type_of_existing_object" or something like that - def set_value_object(self, key: T.Union[OptionKey, str], new_object: 'UserOption[T.Any]') -> None: + def set_value_object(self, key: T.Union[OptionKey, str], new_object: AnyOptionType) -> None: key = self.ensure_key(key) self.d[key] = new_object @@ -807,20 +811,20 @@ def __repr__(self) -> str: def keys(self) -> T.KeysView[OptionKey]: return self.d.keys() - def values(self) -> T.ValuesView[UserOption[T.Any]]: + def values(self) -> T.ValuesView[AnyOptionType]: return self.d.values() - def items(self) -> T.ItemsView['OptionKey', 'UserOption[T.Any]']: + def items(self) -> T.ItemsView['OptionKey', AnyOptionType]: return self.d.items() # FIXME: this method must be deleted and users moved to use "add_xxx_option"s instead. - def update(self, **kwargs: UserOption[T.Any]) -> None: + def update(self, **kwargs: AnyOptionType) -> None: self.d.update(**kwargs) - def setdefault(self, k: OptionKey, o: UserOption[T.Any]) -> UserOption[T.Any]: + def setdefault(self, k: OptionKey, o: AnyOptionType) -> AnyOptionType: return self.d.setdefault(k, o) - def get(self, o: OptionKey, default: T.Optional[UserOption[T.Any]] = None) -> T.Optional[UserOption[T.Any]]: + def get(self, o: OptionKey, default: T.Optional[AnyOptionType] = None) -> T.Optional[AnyOptionType]: return self.d.get(o, default) def is_project_option(self, key: OptionKey) -> bool: