From 5365d9a842938dc9182b52bcdb9674a434475200 Mon Sep 17 00:00:00 2001 From: Jussi Pakkanen Date: Wed, 22 May 2024 23:59:02 +0300 Subject: [PATCH] Refactor option classes to their own file. --- mesonbuild/ast/introspection.py | 6 +- mesonbuild/cargo/interpreter.py | 8 +- mesonbuild/compilers/c.py | 34 +- mesonbuild/compilers/compilers.py | 47 +- mesonbuild/compilers/cpp.py | 54 +- mesonbuild/compilers/cuda.py | 5 +- mesonbuild/compilers/cython.py | 6 +- mesonbuild/compilers/fortran.py | 4 +- mesonbuild/compilers/mixins/emscripten.py | 3 +- mesonbuild/compilers/objc.py | 3 +- mesonbuild/compilers/objcpp.py | 3 +- mesonbuild/compilers/rust.py | 4 +- mesonbuild/coredata.py | 569 ++---------------- mesonbuild/interpreter/compiler.py | 5 +- mesonbuild/interpreter/interpreter.py | 15 +- mesonbuild/interpreter/interpreterobjects.py | 26 +- mesonbuild/interpreter/kwargs.py | 4 +- mesonbuild/interpreter/type_checking.py | 2 +- mesonbuild/interpreterbase/helpers.py | 2 +- mesonbuild/mconf.py | 21 +- mesonbuild/mintro.py | 20 +- mesonbuild/modules/_qt.py | 4 +- mesonbuild/modules/pkgconfig.py | 2 +- mesonbuild/modules/python.py | 2 +- mesonbuild/optinterpreter.py | 35 +- mesonbuild/options.py | 480 +++++++++++++++ run_project_tests.py | 3 +- run_tests.py | 3 +- .../unit/116 empty project/expected_mods.json | 3 +- unittests/datatests.py | 5 +- unittests/platformagnostictests.py | 2 +- 31 files changed, 713 insertions(+), 667 deletions(-) create mode 100644 mesonbuild/options.py diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index fa11feb08..c7dcf73b1 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -10,7 +10,7 @@ import copy import os import typing as T -from .. import compilers, environment, mesonlib, optinterpreter +from .. import compilers, environment, mesonlib, optinterpreter, options from .. import coredata as cdata from ..build import Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..compilers import detect_compiler_for @@ -150,8 +150,8 @@ class IntrospectionInterpreter(AstInterpreter): def func_add_languages(self, node: BaseNode, args: T.List[TYPE_var], kwargs: T.Dict[str, TYPE_var]) -> None: kwargs = self.flatten_kwargs(kwargs) required = kwargs.get('required', True) - assert isinstance(required, (bool, cdata.UserFeatureOption)), 'for mypy' - if isinstance(required, cdata.UserFeatureOption): + assert isinstance(required, (bool, options.UserFeatureOption)), 'for mypy' + if isinstance(required, options.UserFeatureOption): required = required.is_enabled() if 'native' in kwargs: native = kwargs.get('native', False) diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index e1b092897..57f2bedea 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -23,7 +23,7 @@ import typing as T from . import builder from . import version from ..mesonlib import MesonException, Popen_safe, OptionKey -from .. import coredata +from .. import coredata, options if T.TYPE_CHECKING: from types import ModuleType @@ -712,11 +712,11 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. build = builder.Builder(filename) # Generate project options - options: T.Dict[OptionKey, coredata.UserOption] = {} + project_options: T.Dict[OptionKey, options.UserOption] = {} for feature in cargo.features: key = OptionKey(_option_name(feature), subproject=subp_name) enabled = feature == 'default' - options[key] = coredata.UserBooleanOption(key.name, f'Cargo {feature} feature', enabled) + project_options[key] = options.UserBooleanOption(key.name, f'Cargo {feature} feature', enabled) ast = _create_project(cargo, build) ast += [build.assign(build.function('import', [build.string('rust')]), 'rust')] @@ -730,4 +730,4 @@ def interpret(subp_name: str, subdir: str, env: Environment) -> T.Tuple[mparser. for crate_type in cargo.lib.crate_type: ast.extend(_create_lib(cargo, build, crate_type)) - return build.block(ast), options + return build.block(ast), project_options diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 18b25d46d..d0326d795 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -6,7 +6,7 @@ from __future__ import annotations import os.path import typing as T -from .. import coredata +from .. import options from .. import mlog from ..mesonlib import MesonException, version_compare, OptionKey from .c_function_attributes import C_FUNC_ATTRIBUTES @@ -96,7 +96,7 @@ class CCompiler(CLikeCompiler, Compiler): opts = super().get_options() key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - key: coredata.UserStdOption('C', _ALL_STDS), + key: options.UserStdOption('C', _ALL_STDS), }) return opts @@ -128,7 +128,7 @@ class _ClangCStds(CompilerMixinBase): if version_compare(self.version, self._C23_VERSION): stds += ['c23'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts @@ -154,7 +154,7 @@ class ClangCCompiler(_ClangCStds, ClangCompiler, CCompiler): if self.info.is_windows() or self.info.is_cygwin(): self.update_options( opts, - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, OptionKey('winlibs', machine=self.for_machine, lang=self.language), 'Standard Win libraries to link against', gnu_winlibs), @@ -247,7 +247,7 @@ class ArmclangCCompiler(ArmclangCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c90', 'c99', 'c11'], gnu=True) return opts @@ -298,12 +298,12 @@ class GnuCCompiler(GnuCompiler, CCompiler): stds += ['c23'] key = OptionKey('std', machine=self.for_machine, lang=self.language) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + 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(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -377,7 +377,7 @@ class ElbrusCCompiler(ElbrusCompiler, CCompiler): if version_compare(self.version, '>=1.26.00'): stds += ['c17', 'c18', 'iso9899:2017', 'iso9899:2018', 'gnu17', 'gnu18'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds) return opts @@ -416,7 +416,7 @@ class IntelCCompiler(IntelGnuLikeCompiler, CCompiler): if version_compare(self.version, '>=16.0.0'): stds += ['c11'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True) return opts @@ -441,7 +441,7 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): return self.update_options( super().get_options(), self.create_option( - coredata.UserArrayOption, + options.UserArrayOption, OptionKey('winlibs', machine=self.for_machine, lang=self.language), 'Windows libs to link against.', msvc_winlibs, @@ -480,7 +480,7 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi if version_compare(self.version, self._C17_VERSION): stds += ['c17', 'c18'] std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(stds, gnu=True, gnu_deprecated=True) return opts @@ -529,7 +529,7 @@ class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerM def get_options(self) -> 'MutableKeyedOptionDictType': opts = super().get_options() std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -562,7 +562,7 @@ class ArmCCompiler(ArmCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts @@ -591,7 +591,7 @@ class CcrxCCompiler(CcrxCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -638,7 +638,7 @@ class Xc16CCompiler(Xc16Compiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99'], gnu=True) return opts @@ -683,7 +683,7 @@ class CompCertCCompiler(CompCertCompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99']) return opts @@ -720,7 +720,7 @@ class TICCompiler(TICompiler, CCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c89', 'c99', 'c11']) return opts diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 673b63f9d..4267384c5 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -15,6 +15,7 @@ from functools import lru_cache from .. import coredata from .. import mlog from .. import mesonlib +from .. import options from ..mesonlib import ( HoldableObject, EnvironmentException, MesonException, @@ -35,7 +36,7 @@ if T.TYPE_CHECKING: CompilerType = T.TypeVar('CompilerType', bound='Compiler') _T = T.TypeVar('_T') - UserOptionType = T.TypeVar('UserOptionType', bound=coredata.UserOption) + UserOptionType = T.TypeVar('UserOptionType', bound=options.UserOption) """This file contains the data files of all compilers Meson knows about. To support a new compiler, add its information below. @@ -209,40 +210,40 @@ clike_debug_args: T.Dict[bool, T.List[str]] = { MSCRT_VALS = ['none', 'md', 'mdd', 'mt', 'mtd'] @dataclass -class BaseOption(T.Generic[coredata._T, coredata._U]): - opt_type: T.Type[coredata._U] +class BaseOption(T.Generic[options._T, options._U]): + opt_type: T.Type[options._U] description: str default: T.Any = None choices: T.Any = None - def init_option(self, name: OptionKey) -> coredata._U: + def init_option(self, name: OptionKey) -> options._U: 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(coredata.UserBooleanOption, 'Use precompiled headers', True), - OptionKey('b_lto'): BaseOption(coredata.UserBooleanOption, 'Use link time optimization', False), - OptionKey('b_lto_threads'): BaseOption(coredata.UserIntegerOption, 'Use multiple threads for Link Time Optimization', (None, None, 0)), - OptionKey('b_lto_mode'): BaseOption(coredata.UserComboOption, 'Select between different LTO modes.', 'default', + 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_mode'): BaseOption(options.UserComboOption, 'Select between different LTO modes.', 'default', choices=['default', 'thin']), - OptionKey('b_thinlto_cache'): BaseOption(coredata.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False), - OptionKey('b_thinlto_cache_dir'): BaseOption(coredata.UserStringOption, 'Directory to store ThinLTO cache objects', ''), - OptionKey('b_sanitize'): BaseOption(coredata.UserComboOption, 'Code sanitizer to use', 'none', + OptionKey('b_thinlto_cache'): BaseOption(options.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False), + OptionKey('b_thinlto_cache_dir'): BaseOption(options.UserStringOption, 'Directory to store ThinLTO cache objects', ''), + OptionKey('b_sanitize'): BaseOption(options.UserComboOption, 'Code sanitizer to use', 'none', choices=['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined']), - OptionKey('b_lundef'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True), - OptionKey('b_asneeded'): BaseOption(coredata.UserBooleanOption, 'Use -Wl,--as-needed when linking', True), - OptionKey('b_pgo'): BaseOption(coredata.UserComboOption, 'Use profile guided optimization', 'off', + OptionKey('b_lundef'): BaseOption(options.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True), + OptionKey('b_asneeded'): BaseOption(options.UserBooleanOption, 'Use -Wl,--as-needed when linking', True), + OptionKey('b_pgo'): BaseOption(options.UserComboOption, 'Use profile guided optimization', 'off', choices=['off', 'generate', 'use']), - OptionKey('b_coverage'): BaseOption(coredata.UserBooleanOption, 'Enable coverage tracking.', False), - OptionKey('b_colorout'): BaseOption(coredata.UserComboOption, 'Use colored output', 'always', + OptionKey('b_coverage'): BaseOption(options.UserBooleanOption, 'Enable coverage tracking.', False), + OptionKey('b_colorout'): BaseOption(options.UserComboOption, 'Use colored output', 'always', choices=['auto', 'always', 'never']), - OptionKey('b_ndebug'): BaseOption(coredata.UserComboOption, 'Disable asserts', 'false', choices=['true', 'false', 'if-release']), - OptionKey('b_staticpic'): BaseOption(coredata.UserBooleanOption, 'Build static libraries as position independent', True), - OptionKey('b_pie'): BaseOption(coredata.UserBooleanOption, 'Build executables as position independent', False), - OptionKey('b_bitcode'): BaseOption(coredata.UserBooleanOption, 'Generate and embed bitcode (only macOS/iOS/tvOS)', False), - OptionKey('b_vscrt'): BaseOption(coredata.UserComboOption, 'VS run-time library type to use.', 'from_buildtype', + OptionKey('b_ndebug'): BaseOption(options.UserComboOption, 'Disable asserts', 'false', choices=['true', 'false', 'if-release']), + OptionKey('b_staticpic'): BaseOption(options.UserBooleanOption, 'Build static libraries as position independent', True), + OptionKey('b_pie'): BaseOption(options.UserBooleanOption, 'Build executables as position independent', False), + OptionKey('b_bitcode'): BaseOption(options.UserBooleanOption, 'Generate and embed bitcode (only macOS/iOS/tvOS)', False), + OptionKey('b_vscrt'): BaseOption(options.UserComboOption, 'VS run-time library type to use.', 'from_buildtype', choices=MSCRT_VALS + ['from_buildtype', 'static_from_buildtype']), } @@ -1365,12 +1366,12 @@ def get_global_options(lang: str, comp_options = env.options.get(comp_key, []) link_options = env.options.get(largkey, []) - cargs = coredata.UserArrayOption( + cargs = options.UserArrayOption( f'{lang}_{argkey.name}', description + ' compiler', comp_options, split_args=True, allow_dups=True) - largs = coredata.UserArrayOption( + largs = options.UserArrayOption( 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 525c9fcdf..ea5182370 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -8,7 +8,7 @@ import functools import os.path import typing as T -from .. import coredata +from .. import options from .. import mlog from ..mesonlib import MesonException, version_compare, OptionKey @@ -174,7 +174,7 @@ class CPPCompiler(CLikeCompiler, Compiler): opts = super().get_options() key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - key: coredata.UserStdOption('C++', _ALL_STDS), + key: options.UserStdOption('C++', _ALL_STDS), }) return opts @@ -242,16 +242,16 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCompiler, CPPCompiler): key = OptionKey('key', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), @@ -264,12 +264,12 @@ class ClangCPPCompiler(_StdCPPLibMixin, ClangCompiler, CPPCompiler): if version_compare(self.version, self._CPP26_VERSION): cppstd_choices.append('c++26') std_opt = opts[key.evolve('std')] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + 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(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -393,14 +393,14 @@ class ArmclangCPPCompiler(ArmclangCompiler, CPPCompiler): key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + 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, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++98', 'c++03', 'c++11', 'c++14', 'c++17'], gnu=True) return opts @@ -442,16 +442,16 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCompiler, CPPCompiler): opts = CPPCompiler.get_options(self) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), @@ -465,12 +465,12 @@ class GnuCPPCompiler(_StdCPPLibMixin, GnuCompiler, CPPCompiler): if version_compare(self.version, '>=14.0.0'): cppstd_choices.append('c++26') std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + 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(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Standard Win libraries to link against', gnu_winlibs), @@ -582,18 +582,18 @@ class ElbrusCPPCompiler(ElbrusCompiler, CPPCompiler): key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds, gnu=True) return opts @@ -661,22 +661,22 @@ class IntelCPPCompiler(IntelGnuLikeCompiler, CPPCompiler): key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('debugstl'), 'STL debug mode', False), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(c_stds + g_stds) return opts @@ -734,22 +734,22 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): key = OptionKey('std', machine=self.for_machine, lang=self.language) self.update_options( opts, - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, key.evolve('eh'), 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default'), - self.create_option(coredata.UserBooleanOption, + self.create_option(options.UserBooleanOption, key.evolve('rtti'), 'Enable RTTI', True), - self.create_option(coredata.UserArrayOption, + self.create_option(options.UserArrayOption, key.evolve('winlibs'), 'Windows libs to link against.', msvc_winlibs), ) std_opt = opts[key] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(cpp_stds) return opts @@ -912,7 +912,7 @@ class ArmCPPCompiler(ArmCompiler, CPPCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03', 'c++11']) return opts @@ -973,7 +973,7 @@ class TICPPCompiler(TICompiler, CPPCompiler): def get_options(self) -> 'MutableKeyedOptionDictType': opts = CPPCompiler.get_options(self) std_opt = opts[OptionKey('std', machine=self.for_machine, lang=self.language)] - assert isinstance(std_opt, coredata.UserStdOption), 'for mypy' + assert isinstance(std_opt, options.UserStdOption), 'for mypy' std_opt.set_versions(['c++03']) return opts diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 7231a267c..8680cf076 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -9,6 +9,7 @@ import string import typing as T from .. import coredata +from .. import options from .. import mlog from ..mesonlib import ( EnvironmentException, Popen_safe, @@ -643,12 +644,12 @@ class CudaCompiler(Compiler): return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'C++ language standard to use with CUDA', cpp_stds, 'none'), - self.create_option(coredata.UserStringOption, + self.create_option(options.UserStringOption, OptionKey('ccbindir', machine=self.for_machine, lang=self.language), 'CUDA non-default toolchain directory to use (-ccbin)', ''), diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 30cec81e3..61e339bdd 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -6,7 +6,7 @@ from __future__ import annotations import typing as T -from .. import coredata +from .. import options from ..mesonlib import EnvironmentException, OptionKey, version_compare from .compilers import Compiler @@ -69,12 +69,12 @@ class CythonCompiler(Compiler): def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('version', machine=self.for_machine, lang=self.language), 'Python version to target', ['2', '3'], '3'), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('language', machine=self.for_machine, lang=self.language), 'Output C or C++ files', ['c', 'cpp'], diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 428251560..7b2f5d9d4 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -6,7 +6,7 @@ from __future__ import annotations import typing as T import os -from .. import coredata +from .. import options from .compilers import ( clike_debug_args, Compiler, @@ -114,7 +114,7 @@ class FortranCompiler(CLikeCompiler, Compiler): def get_options(self) -> 'MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'Fortran language standard to use', ['none'], diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index bb8a52054..110dbc605 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -9,6 +9,7 @@ import os.path import typing as T from ... import coredata +from ... import options from ... import mesonlib from ...mesonlib import OptionKey from ...mesonlib import LibType @@ -59,7 +60,7 @@ class EmscriptenMixin(Compiler): return self.update_options( super().get_options(), self.create_option( - coredata.UserIntegerOption, + options.UserIntegerOption, OptionKey('thread_count', machine=self.for_machine, lang=self.language), 'Number of threads to use in web assembly, set to 0 to disable', (0, None, 4), # Default was picked at random diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 7c19c1b7d..4d33ec8b2 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -6,6 +6,7 @@ from __future__ import annotations import typing as T from .. import coredata +from .. import options from ..mesonlib import OptionKey from .compilers import Compiler @@ -80,7 +81,7 @@ class ClangObjCCompiler(ClangCompiler, ObjCCompiler): def get_options(self) -> 'coredata.MutableKeyedOptionDictType': return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang='c'), 'C language standard to use', ['none', 'c89', 'c99', 'c11', 'c17', 'gnu89', 'gnu99', 'gnu11', 'gnu17'], diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 46eaa5049..e28e3ed30 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -6,6 +6,7 @@ from __future__ import annotations import typing as T from .. import coredata +from .. import options from ..mesonlib import OptionKey from .mixins.clike import CLikeCompiler @@ -80,7 +81,7 @@ class ClangObjCPPCompiler(ClangCompiler, ObjCPPCompiler): def get_options(self) -> coredata.MutableKeyedOptionDictType: return self.update_options( super().get_options(), - self.create_option(coredata.UserComboOption, + self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang='cpp'), 'C++ language standard to use', ['none', 'c++98', 'c++11', 'c++14', 'c++17', 'c++20', 'c++2b', diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index ce1079190..0f52dbb5e 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -9,7 +9,7 @@ import textwrap import re import typing as T -from .. import coredata +from .. import options from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, OptionKey from .compilers import Compiler, clike_debug_args @@ -158,7 +158,7 @@ class RustCompiler(Compiler): # use_linker_args method instead. def get_options(self) -> MutableKeyedOptionDictType: - return dict((self.create_option(coredata.UserComboOption, + return dict((self.create_option(options.UserComboOption, OptionKey('std', machine=self.for_machine, lang=self.language), 'Rust edition to use', ['none', '2015', '2018', '2021'], diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 6e67587b2..76da0b67f 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -6,7 +6,7 @@ from __future__ import annotations import copy -from . import mlog, mparser +from . import mlog, mparser, options import pickle, os, uuid import sys from itertools import chain @@ -15,12 +15,10 @@ from collections import OrderedDict, abc from dataclasses import dataclass from .mesonlib import ( - HoldableObject, MesonBugException, + MesonBugException, MesonException, EnvironmentException, MachineChoice, PerMachine, - PerMachineDefaultable, default_libdir, default_libexecdir, - default_prefix, default_datadir, default_includedir, default_infodir, - default_localedir, default_mandir, default_sbindir, default_sysconfdir, - listify_array_value, OptionKey, OptionType, stringlistify, + PerMachineDefaultable, + OptionKey, OptionType, stringlistify, pickle_load ) import ast @@ -56,8 +54,8 @@ if T.TYPE_CHECKING: cross_file: T.List[str] native_file: T.List[str] - OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], 'OptionsView'] - MutableKeyedOptionDictType = T.Dict['OptionKey', 'UserOption[T.Any]'] + OptionDictType = T.Union[T.Dict[str, 'options.UserOption[T.Any]'], 'OptionsView'] + MutableKeyedOptionDictType = T.Dict['OptionKey', 'options.UserOption[T.Any]'] KeyedOptionDictType = T.Union[MutableKeyedOptionDictType, 'OptionsView'] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, FileOrString, T.Tuple[str, ...], CompileCheckMode] # code, args @@ -82,20 +80,11 @@ if stable_version.endswith('.99'): stable_version_array[-2] = str(int(stable_version_array[-2]) + 1) stable_version = '.'.join(stable_version_array) -backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] -genvslitelist = ['vs2022'] -buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'] - -DEFAULT_YIELDING = False - -# Can't bind this near the class method it seems, sadly. -_T = T.TypeVar('_T') - def get_genvs_default_buildtype_list() -> list[str]: # just debug, debugoptimized, and release for now # but this should probably be configurable through some extra option, alongside --genvslite. - return buildtypelist[1:-2] + return options.buildtypelist[1:-2] class MesonVersionMismatchException(MesonException): @@ -108,312 +97,6 @@ class MesonVersionMismatchException(MesonException): self.current_version = current_version -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 - - def listify(self, value: T.Any) -> T.List[T.Any]: - return [value] - - def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bool]]]: - assert isinstance(self.value, (str, int, bool, list)) - return self.value - - # 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. - 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) - self.value = self.validate_value(newvalue) - return self.value != oldvalue - -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 - -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) - - def __bool__(self) -> bool: - return self.value - - def validate_value(self, value: T.Any) -> bool: - if isinstance(value, bool): - return value - if not isinstance(value, str): - raise MesonException(f'Option "{self.name}" value {value} cannot be converted to a boolean') - if value.lower() == 'true': - return True - if value.lower() == 'false': - return False - raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') - -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) - - def validate_value(self, value: T.Any) -> int: - if isinstance(value, str): - value = 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 - - def toint(self, valuestring: str) -> int: - try: - return int(valuestring) - 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 - # So we need to use oct instead of dec here if we do not want values to be misinterpreted. - def __str__(self) -> str: - return oct(int(self)) - -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'] - - def printable_value(self) -> str: - if self.value == 'preserve': - return self.value - return format(self.value, '04o') - - def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: - if value == 'preserve': - return 'preserve' - return OctalInt(super().validate_value(value)) - - def toint(self, valuestring: T.Union[str, OctalInt]) -> int: - try: - return int(valuestring, 8) - except ValueError as e: - raise MesonException(f'Invalid mode for option "{self.name}" {e}') - -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: - if isinstance(value, bool): - _type = 'boolean' - elif isinstance(value, (int, float)): - _type = 'number' - else: - _type = 'string' - optionsstring = ', '.join([f'"{item}"' for item in self.choices]) - raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.' - ' Possible choices are (as string): {}.'.format( - value, _type, self.name, optionsstring)) - return value - -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) - - def listify(self, value: T.Any) -> T.List[T.Any]: - try: - return listify_array_value(value, self.split_args) - except MesonException as e: - raise MesonException(f'error in option "{self.name}": {e!s}') - - def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: - newvalue = self.listify(value) - - if not self.allow_dups and len(set(newvalue)) != len(newvalue): - msg = 'Duplicated values in array option is deprecated. ' \ - 'This will become a hard error in the future.' - mlog.deprecation(msg) - for i in newvalue: - if not isinstance(i, str): - raise MesonException(f'String array element "{newvalue!s}" for option "{self.name}" is not a string.') - if self.choices: - bad = [x for x in newvalue if x not in self.choices] - if bad: - raise MesonException('Value{} "{}" for option "{}" {} not in allowed choices: "{}"'.format( - '' if len(bad) == 1 else 's', - ', '.join(bad), - self.name, - 'is' if len(bad) == 1 else 'are', - ', '.join(self.choices)) - ) - 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) - - -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 - - def is_enabled(self) -> bool: - return self.value == 'enabled' - - def is_disabled(self) -> bool: - return self.value == 'disabled' - - def is_auto(self) -> bool: - return self.value == 'auto' - -class UserStdOption(UserComboOption): - ''' - UserOption specific to c_std and cpp_std options. User can set a list of - STDs in preference order and it selects the first one supported by current - compiler. - - For historical reasons, some compilers (msvc) allowed setting a GNU std and - silently fell back to C std. This is now deprecated. Projects that support - both GNU and MSVC compilers should set e.g. c_std=gnu11,c11. - - This is not using self.deprecated mechanism we already have for project - options because we want to print a warning if ALL values are deprecated, not - if SOME values are deprecated. - ''' - def __init__(self, lang: str, all_stds: T.List[str]) -> None: - self.lang = lang.lower() - self.all_stds = ['none'] + all_stds - # 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') - - 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) - self.choices += versions - if gnu: - gnu_stds_map = {f'gnu{std[1:]}': std for std in versions} - if gnu_deprecated: - self.deprecated_stds.update(gnu_stds_map) - else: - self.choices += gnu_stds_map.keys() - - def validate_value(self, value: T.Union[str, T.List[str]]) -> str: - try: - candidates = listify_array_value(value) - except MesonException as e: - raise MesonException(f'error in option "{self.name}": {e!s}') - unknown = ','.join(std for std in candidates if std not in self.all_stds) - if unknown: - raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.') - # Check first if any of the candidates are not deprecated - for std in candidates: - if std in self.choices: - return std - # Fallback to a deprecated std if any - for std in candidates: - newstd = self.deprecated_stds.get(std) - if newstd is not None: - mlog.deprecation( - f'None of the values {candidates} are supported by the {self.lang} compiler.\n' + - f'However, the deprecated {std} std currently falls back to {newstd}.\n' + - 'This will be an error in the future.\n' + - 'If the project supports both GNU and MSVC compilers, a value such as\n' + - '"c_std=gnu11,c11" specifies that GNU is preferred but it can safely fallback to plain c11.') - return newstd - raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' + - f'Possible values for option "{self.name}" are {self.choices}') - -@dataclass -class OptionsView(abc.Mapping): - '''A view on an options dictionary for a given subproject and with overrides. - ''' - - # TODO: the typing here could be made more explicit using a TypeDict from - # python 3.8 or typing_extensions - options: KeyedOptionDictType - subproject: T.Optional[str] = None - overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None - - def __getitem__(self, key: OptionKey) -> UserOption: - # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal(). - # We should try to share the code somehow. - key = key.evolve(subproject=self.subproject) - if not key.is_project(): - opt = self.options.get(key) - if opt is None or opt.yielding: - opt = self.options[key.as_root()] - else: - opt = self.options[key] - if opt.yielding: - opt = self.options.get(key.as_root(), opt) - if self.overrides: - override_value = self.overrides.get(key.as_root()) - if override_value is not None: - opt = copy.copy(opt) - opt.set_value(override_value) - return opt - - def __iter__(self) -> T.Iterator[OptionKey]: - return iter(self.options) - - def __len__(self) -> int: - return len(self.options) - class DependencyCacheType(enum.Enum): OTHER = 0 @@ -664,7 +347,7 @@ class CoreData: # getting the "system default" is always wrong on multiarch # platforms as it gets a value like lib/x86_64-linux-gnu. if self.cross_files: - BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' + options.BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' def sanitize_prefix(self, prefix: str) -> str: prefix = os.path.expanduser(prefix) @@ -698,7 +381,7 @@ class CoreData: except TypeError: return value if option.name.endswith('dir') and value.is_absolute() and \ - option not in BUILTIN_DIR_NOPREFIX_OPTIONS: + option not in options.BUILTIN_DIR_NOPREFIX_OPTIONS: try: # Try to relativize the path. value = value.relative_to(prefix) @@ -717,15 +400,15 @@ class CoreData: def init_builtins(self, subproject: str) -> None: # Create builtin options with default values - for key, opt in BUILTIN_OPTIONS.items(): + for key, opt in options.BUILTIN_OPTIONS.items(): self.add_builtin_option(self.options, key.evolve(subproject=subproject), opt) for for_machine in iter(MachineChoice): - for key, opt in BUILTIN_OPTIONS_PER_MACHINE.items(): + for key, opt in options.BUILTIN_OPTIONS_PER_MACHINE.items(): self.add_builtin_option(self.options, key.evolve(subproject=subproject, machine=for_machine), opt) @staticmethod def add_builtin_option(opts_map: 'MutableKeyedOptionDictType', key: OptionKey, - opt: 'BuiltinOption') -> None: + opt: 'options.BuiltinOption') -> None: if key.subproject: if opt.yielding: # This option is global and not per-subproject @@ -733,17 +416,17 @@ class CoreData: value = opts_map[key.as_root()].value else: value = None - opts_map[key] = opt.init_option(key, value, default_prefix()) + opts_map[key] = opt.init_option(key, value, options.default_prefix()) def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': - self.options[OptionKey('backend_max_links')] = UserIntegerOption( + self.options[OptionKey('backend_max_links')] = options.UserIntegerOption( 'backend_max_links', 'Maximum number of linker processes to run or 0 for no ' 'limit', (0, None, 0)) elif backend_name.startswith('vs'): - self.options[OptionKey('backend_startup_project')] = UserStringOption( + self.options[OptionKey('backend_startup_project')] = options.UserStringOption( 'backend_startup_project', 'Default project to execute in Visual Studio', '') @@ -881,7 +564,7 @@ class CoreData: @staticmethod def is_per_machine_option(optname: OptionKey) -> bool: - if optname.as_host() in BUILTIN_OPTIONS_PER_MACHINE: + if optname.as_host() in options.BUILTIN_OPTIONS_PER_MACHINE: return True return optname.lang is not None @@ -930,7 +613,7 @@ class CoreData: def copy_build_options_from_regular_ones(self) -> bool: dirty = False assert not self.is_cross_build() - for k in BUILTIN_OPTIONS_PER_MACHINE: + for k in options.BUILTIN_OPTIONS_PER_MACHINE: o = self.options[k] dirty |= self.options[k.as_build()].set_value(o.value) for bk, bv in self.options.items(): @@ -944,21 +627,21 @@ class CoreData: return dirty - def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool: + def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = '', first_invocation: bool = False) -> bool: dirty = False if not self.is_cross_build(): - options = {k: v for k, v in options.items() if k.machine is not MachineChoice.BUILD} + opts_to_set = {k: v for k, v in opts_to_set.items() if k.machine is not MachineChoice.BUILD} # Set prefix first because it's needed to sanitize other options pfk = OptionKey('prefix') - if pfk in options: - prefix = self.sanitize_prefix(options[pfk]) + if pfk in opts_to_set: + prefix = self.sanitize_prefix(opts_to_set[pfk]) dirty |= self.options[OptionKey('prefix')].set_value(prefix) - for key in BUILTIN_DIR_NOPREFIX_OPTIONS: - if key not in options: - dirty |= self.options[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + for key in options.BUILTIN_DIR_NOPREFIX_OPTIONS: + if key not in opts_to_set: + dirty |= self.options[key].set_value(options.BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) unknown_options: T.List[OptionKey] = [] - for k, v in options.items(): + for k, v in opts_to_set.items(): if k == pfk: continue elif k in self.options: @@ -1255,9 +938,9 @@ def save(obj: CoreData, build_dir: str) -> str: def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: - for n, b in BUILTIN_OPTIONS.items(): + for n, b in options.BUILTIN_OPTIONS.items(): b.add_to_argparse(str(n), parser, '') - for n, b in BUILTIN_OPTIONS_PER_MACHINE.items(): + 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)') parser.add_argument('-D', action='append', dest='projectoptions', default=[], metavar="option", @@ -1281,185 +964,55 @@ def parse_cmd_line_options(args: SharedCMDOptions) -> None: # Merge builtin options set with --option into the dict. for key in chain( - BUILTIN_OPTIONS.keys(), - (k.as_build() for k in BUILTIN_OPTIONS_PER_MACHINE.keys()), - BUILTIN_OPTIONS_PER_MACHINE.keys(), + options.BUILTIN_OPTIONS.keys(), + (k.as_build() for k in options.BUILTIN_OPTIONS_PER_MACHINE.keys()), + options.BUILTIN_OPTIONS_PER_MACHINE.keys(), ): name = str(key) value = getattr(args, name, None) if value is not None: if key in args.cmd_line_options: - cmdline_name = BuiltinOption.argparse_name_to_arg(name) + cmdline_name = options.BuiltinOption.argparse_name_to_arg(name) raise MesonException( f'Got argument {name} as both -D{name} and {cmdline_name}. Pick one.') args.cmd_line_options[key] = value delattr(args, name) +@dataclass +class OptionsView(abc.Mapping): + '''A view on an options dictionary for a given subproject and with overrides. + ''' -_U = T.TypeVar('_U', bound=UserOption[_T]) - -class BuiltinOption(T.Generic[_T, _U]): - - """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, *, - choices: T.Any = None, readonly: bool = False): - self.opt_type = opt_type - self.description = description - self.default = default - self.choices = choices - self.yielding = yielding - self.readonly = readonly - - def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U: - """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} - if self.choices: - keywords['choices'] = self.choices - o = self.opt_type(name.name, self.description, **keywords) - o.readonly = self.readonly - return o - - def _argparse_action(self) -> T.Optional[str]: - # If the type is a boolean, the presence of the argument in --foo form - # is to enable it. Disabling happens by using -Dfoo=false, which is - # parsed under `args.projectoptions` and does not hit this codepath. - if isinstance(self.default, bool): - return 'store_true' - return None - - 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 + # TODO: the typing here could be made more explicit using a TypeDict from + # python 3.8 or typing_extensions + original_options: KeyedOptionDictType + subproject: T.Optional[str] = None + overrides: T.Optional[T.Mapping[OptionKey, T.Union[str, int, bool, T.List[str]]]] = None - @staticmethod - def argparse_name_to_arg(name: str) -> str: - if name == 'warning_level': - return '--warnlevel' + def __getitem__(self, key: OptionKey) -> options.UserOption: + # FIXME: This is fundamentally the same algorithm than interpreter.get_option_internal(). + # We should try to share the code somehow. + key = key.evolve(subproject=self.subproject) + if not key.is_project(): + opt = self.original_options.get(key) + if opt is None or opt.yielding: + opt = self.original_options[key.as_root()] else: - return '--' + name.replace('_', '-') - - def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: - if self.opt_type in [UserComboOption, UserIntegerOption]: - return self.default - try: - return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] - except KeyError: - pass - return self.default + opt = self.original_options[key] + if opt.yielding: + opt = self.original_options.get(key.as_root(), opt) + if self.overrides: + override_value = self.overrides.get(key.as_root()) + if override_value is not None: + opt = copy.copy(opt) + opt.set_value(override_value) + return opt - def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: - kwargs = OrderedDict() + def __iter__(self) -> T.Iterator[OptionKey]: + return iter(self.original_options) - c = self._argparse_choices() - b = self._argparse_action() - h = self.description - if not b: - h = '{} (default: {}).'.format(h.rstrip('.'), self.prefixed_default(name)) - else: - kwargs['action'] = b - if c and not b: - kwargs['choices'] = c - kwargs['default'] = argparse.SUPPRESS - kwargs['dest'] = name - - cmdline_name = self.argparse_name_to_arg(name) - parser.add_argument(cmdline_name, help=h + help_suffix, **kwargs) - - -# Update `docs/markdown/Builtin-options.md` after changing the options below -# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required. -# Please also update completion scripts in $MESONSRC/data/shell-completions/ -BUILTIN_DIR_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), - (OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')), - (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', default_datadir())), - (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', default_includedir())), - (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', default_infodir())), - (OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())), - (OptionKey('licensedir'), BuiltinOption(UserStringOption, 'Licenses directory', '')), - (OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), - (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', default_localedir())), - (OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), - (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', default_mandir())), - (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', default_sbindir())), - (OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), - (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', default_sysconfdir())), -]) - -BUILTIN_CORE_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ - (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), - (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist, - readonly=True)), - (OptionKey('genvslite'), - BuiltinOption( - UserComboOption, - 'Setup multiple buildtype-suffixed ninja-backend build directories, ' - 'and a [builddir]_vs containing a Visual Studio meta-backend with multiple configurations that calls into them', - 'vs2022', - choices=genvslitelist) - ), - (OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug', - choices=buildtypelist)), - (OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)), - (OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], - yielding=False)), - (OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), - (OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), - (OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), - (OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])), - (OptionKey('prefer_static'), BuiltinOption(UserBooleanOption, 'Whether to try static linking before shared linking', False)), - (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('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('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)), - - # Pkgconfig module - (OptionKey('relocatable', module='pkgconfig'), - BuiltinOption(UserBooleanOption, 'Generate pkgconfig files as relocatable', False)), - - # Python module - (OptionKey('bytecompile', module='python'), - BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))), - (OptionKey('install_env', module='python'), - BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])), - (OptionKey('platlibdir', module='python'), - BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')), - (OptionKey('purelibdir', module='python'), - BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')), - (OptionKey('allow_limited_api', module='python'), - BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)), -]) - -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', [])), -]) - -# Special prefix-dependent defaults for installation directories that reside in -# a path outside of the prefix in FHS and common usage. -BUILTIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = { - OptionKey('sysconfdir'): {'/usr': '/etc'}, - OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'}, - OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, - OptionKey('platlibdir', module='python'): {}, - OptionKey('purelibdir', module='python'): {}, -} + def __len__(self) -> int: + return len(self.original_options) FORBIDDEN_TARGET_NAMES = frozenset({ 'clean', diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 50a850af4..359e8e726 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -13,6 +13,7 @@ import typing as T from .. import build from .. import coredata from .. import dependencies +from .. import options from .. import mesonlib from .. import mlog from ..compilers import SUFFIX_TO_LANG, RunResult @@ -89,7 +90,7 @@ if T.TYPE_CHECKING: header_include_directories: T.List[build.IncludeDirs] header_no_builtin_args: bool header_prefix: str - header_required: T.Union[bool, coredata.UserFeatureOption] + header_required: T.Union[bool, options.UserFeatureOption] class PreprocessKW(TypedDict): output: str @@ -685,7 +686,7 @@ class CompilerHolder(ObjectHolder['Compiler']): @typed_pos_args('compiler.find_library', str) @typed_kwargs( 'compiler.find_library', - KwargInfo('required', (bool, coredata.UserFeatureOption), default=True), + KwargInfo('required', (bool, options.UserFeatureOption), default=True), KwargInfo('has_headers', ContainerTypeInfo(list, str), listify=True, default=[], since='0.50.0'), KwargInfo('static', (bool, NoneType), since='0.51.0'), KwargInfo('disabler', bool, default=False, since='0.49.0'), diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 50780bab2..eb6783c64 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -11,6 +11,7 @@ from .. import environment from .. import coredata from .. import dependencies from .. import mlog +from .. import options from .. import build from .. import optinterpreter from .. import compilers @@ -163,7 +164,7 @@ class Summary: elif isinstance(i, Disabler): FeatureNew.single_use('disabler in summary', '0.64.0', subproject) formatted_values.append(mlog.red('NO')) - elif isinstance(i, coredata.UserOption): + elif isinstance(i, options.UserOption): FeatureNew.single_use('feature option in summary', '0.58.0', subproject) formatted_values.append(i.printable_value()) else: @@ -450,7 +451,7 @@ class Interpreter(InterpreterBase, HoldableObject): build.StructuredSources: OBJ.StructuredSourcesHolder, compilers.RunResult: compilerOBJ.TryRunResultHolder, dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder, - coredata.UserFeatureOption: OBJ.FeatureOptionHolder, + options.UserFeatureOption: OBJ.FeatureOptionHolder, envconfig.MachineInfo: OBJ.MachineHolder, build.ConfigurationData: OBJ.ConfigurationDataHolder, }) @@ -1047,7 +1048,7 @@ class Interpreter(InterpreterBase, HoldableObject): # FIXME: Are there other files used by cargo interpreter? [os.path.join(subdir, 'Cargo.toml')]) - def get_option_internal(self, optname: str) -> coredata.UserOption: + def get_option_internal(self, optname: str) -> options.UserOption: key = OptionKey.from_string(optname).evolve(subproject=self.subproject) if not key.is_project(): @@ -1056,7 +1057,7 @@ class Interpreter(InterpreterBase, HoldableObject): if v is None or v.yielding: v = opts.get(key.as_root()) if v is not None: - assert isinstance(v, coredata.UserOption), 'for mypy' + assert isinstance(v, options.UserOption), 'for mypy' return v try: @@ -1085,7 +1086,7 @@ class Interpreter(InterpreterBase, HoldableObject): @typed_pos_args('get_option', str) @noKwargs def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], - kwargs: 'TYPE_kwargs') -> T.Union[coredata.UserOption, 'TYPE_var']: + kwargs: 'TYPE_kwargs') -> T.Union[options.UserOption, 'TYPE_var']: optname = args[0] if ':' in optname: raise InterpreterException('Having a colon in option name is forbidden, ' @@ -1096,10 +1097,10 @@ class Interpreter(InterpreterBase, HoldableObject): raise InterpreterException(f'Invalid option name {optname!r}') opt = self.get_option_internal(optname) - if isinstance(opt, coredata.UserFeatureOption): + if isinstance(opt, options.UserFeatureOption): opt.name = optname return opt - elif isinstance(opt, coredata.UserOption): + elif isinstance(opt, options.UserOption): if isinstance(opt.value, str): return P_OBJ.OptionString(opt.value, f'{{{optname}}}') return opt.value diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 8cd9a2be6..adec0d589 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -8,7 +8,7 @@ import textwrap from pathlib import Path, PurePath from .. import mesonlib -from .. import coredata +from .. import options from .. import build from .. import mlog @@ -52,7 +52,7 @@ def extract_required_kwarg(kwargs: 'kwargs.ExtractRequired', disabled = False required = False feature: T.Optional[str] = None - if isinstance(val, coredata.UserFeatureOption): + if isinstance(val, options.UserFeatureOption): if not feature_check: feature_check = FeatureNew('User option "feature"', '0.47.0') feature_check.use(subproject) @@ -85,12 +85,12 @@ def extract_search_dirs(kwargs: 'kwargs.ExtractSearchDirs') -> T.List[str]: raise InvalidCode(f'Search directory {d} is not an absolute path.') return [str(s) for s in search_dirs] -class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): - def __init__(self, option: coredata.UserFeatureOption, interpreter: 'Interpreter'): +class FeatureOptionHolder(ObjectHolder[options.UserFeatureOption]): + def __init__(self, option: options.UserFeatureOption, interpreter: 'Interpreter'): super().__init__(option, interpreter) if option and option.is_auto(): # TODO: we need to cast here because options is not a TypedDict - auto = T.cast('coredata.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) + auto = T.cast('options.UserFeatureOption', self.env.coredata.options[OptionKey('auto_features')]) self.held_object = copy.copy(auto) self.held_object.name = option.name self.methods.update({'enabled': self.enabled_method, @@ -108,12 +108,12 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): def value(self) -> str: return 'disabled' if not self.held_object else self.held_object.value - def as_disabled(self) -> coredata.UserFeatureOption: + def as_disabled(self) -> options.UserFeatureOption: disabled = copy.deepcopy(self.held_object) disabled.value = 'disabled' return disabled - def as_enabled(self) -> coredata.UserFeatureOption: + def as_enabled(self) -> options.UserFeatureOption: enabled = copy.deepcopy(self.held_object) enabled.value = 'enabled' return enabled @@ -139,7 +139,7 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): def auto_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> bool: return self.value == 'auto' - def _disable_if(self, condition: bool, message: T.Optional[str]) -> coredata.UserFeatureOption: + def _disable_if(self, condition: bool, message: T.Optional[str]) -> options.UserFeatureOption: if not condition: return copy.deepcopy(self.held_object) @@ -156,7 +156,7 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): 'feature_option.require', _ERROR_MSG_KW, ) - def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def require_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(not args[0], kwargs['error_message']) @FeatureNew('feature_option.disable_if()', '1.1.0') @@ -165,7 +165,7 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): 'feature_option.disable_if', _ERROR_MSG_KW, ) - def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def disable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: return self._disable_if(args[0], kwargs['error_message']) @FeatureNew('feature_option.enable_if()', '1.1.0') @@ -174,7 +174,7 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): 'feature_option.enable_if', _ERROR_MSG_KW, ) - def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> coredata.UserFeatureOption: + def enable_if_method(self, args: T.Tuple[bool], kwargs: 'kwargs.FeatureOptionRequire') -> options.UserFeatureOption: if not args[0]: return copy.deepcopy(self.held_object) @@ -188,13 +188,13 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): @FeatureNew('feature_option.disable_auto_if()', '0.59.0') @noKwargs @typed_pos_args('feature_option.disable_auto_if', bool) - def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: + def disable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return copy.deepcopy(self.held_object) if self.value != 'auto' or not args[0] else self.as_disabled() @FeatureNew('feature_option.enable_auto_if()', '1.1.0') @noKwargs @typed_pos_args('feature_option.enable_auto_if', bool) - def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> coredata.UserFeatureOption: + def enable_auto_if_method(self, args: T.Tuple[bool], kwargs: TYPE_kwargs) -> options.UserFeatureOption: return self.as_enabled() if self.value == 'auto' and args[0] else copy.deepcopy(self.held_object) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 17f7876a0..85779bc00 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -10,7 +10,7 @@ import typing as T from typing_extensions import TypedDict, Literal, Protocol, NotRequired from .. import build -from .. import coredata +from .. import options from ..compilers import Compiler from ..dependencies.base import Dependency from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString, OptionKey @@ -73,7 +73,7 @@ class ExtractRequired(TypedDict): a boolean or a feature option should inherit it's arguments from this class. """ - required: T.Union[bool, coredata.UserFeatureOption] + required: T.Union[bool, options.UserFeatureOption] class ExtractSearchDirs(TypedDict): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 9b7e35c63..285613636 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -11,7 +11,7 @@ from .. import compilers from ..build import (CustomTarget, BuildTarget, CustomTargetIndex, ExtractedObjects, GeneratedList, IncludeDirs, BothLibraries, SharedLibrary, StaticLibrary, Jar, Executable, StructuredSources) -from ..coredata import UserFeatureOption +from ..options import UserFeatureOption from ..dependencies import Dependency, InternalDependency from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep, diff --git a/mesonbuild/interpreterbase/helpers.py b/mesonbuild/interpreterbase/helpers.py index 3942f2c9f..0b0436209 100644 --- a/mesonbuild/interpreterbase/helpers.py +++ b/mesonbuild/interpreterbase/helpers.py @@ -5,7 +5,7 @@ from __future__ import annotations from .. import mesonlib, mparser from .exceptions import InterpreterException, InvalidArguments -from ..coredata import UserOption +from ..options import UserOption import collections.abc diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 2cef24fd7..48359ded2 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -14,6 +14,7 @@ import collections from . import build from . import coredata +from . import options from . import environment from . import mesonlib from . import mintro @@ -83,12 +84,12 @@ class Conf: # if the option file has been updated, reload it # This cannot handle options for a new subproject that has not yet # been configured. - for sub, options in self.coredata.options_files.items(): - if options is not None and os.path.exists(options[0]): - opfile = options[0] + for sub, conf_options in self.coredata.options_files.items(): + if conf_options is not None and os.path.exists(conf_options[0]): + opfile = conf_options[0] with open(opfile, 'rb') as f: ophash = hashlib.sha1(f.read()).hexdigest() - if ophash != options[1]: + if ophash != conf_options[1]: oi = OptionInterpreter(sub) oi.process(opfile) self.coredata.update_project_options(oi.options, sub) @@ -223,18 +224,18 @@ class Conf: self._add_line(mlog.normal_yellow(section + ':'), '', '', '') self.print_margin = 2 - def print_options(self, title: str, options: 'coredata.KeyedOptionDictType') -> None: - if not options: + def print_options(self, title: str, opts: 'coredata.KeyedOptionDictType') -> None: + if not opts: return if title: self.add_title(title) - auto = T.cast('coredata.UserFeatureOption', self.coredata.options[OptionKey('auto_features')]) - for k, o in sorted(options.items()): + auto = T.cast('options.UserFeatureOption', self.coredata.options[OptionKey('auto_features')]) + for k, o in sorted(opts.items()): printable_value = o.printable_value() root = k.as_root() if o.yielding and k.subproject and root in self.coredata.options: printable_value = '' - if isinstance(o, coredata.UserFeatureOption) and o.is_auto(): + 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) @@ -255,7 +256,7 @@ class Conf: if not self.default_values_only: mlog.log(' Build dir ', self.build_dir) - dir_option_names = set(coredata.BUILTIN_DIR_OPTIONS) + dir_option_names = set(options.BUILTIN_DIR_OPTIONS) test_option_names = {OptionKey('errorlogs'), OptionKey('stdsplit')} diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index bdbb59e3a..a5ce72a1f 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -19,7 +19,7 @@ from pathlib import Path, PurePath import sys import typing as T -from . import build, mesonlib, coredata as cdata +from . import build, mesonlib, options, coredata as cdata from .ast import IntrospectionInterpreter, BUILD_TARGET_FUNCTIONS, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter from .backend import backends from .dependencies import Dependency @@ -88,7 +88,7 @@ def add_arguments(parser: argparse.ArgumentParser) -> None: flag = '--' + key.replace('_', '-') parser.add_argument(flag, action='store_true', dest=key, default=False, help=val.desc) - parser.add_argument('--backend', choices=sorted(cdata.backendlist), dest='backend', default='ninja', + parser.add_argument('--backend', choices=sorted(options.backendlist), dest='backend', default='ninja', help='The backend to use for the --buildoptions introspection.') parser.add_argument('-a', '--all', action='store_true', dest='all', default=False, help='Print all available information.') @@ -284,7 +284,7 @@ def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[s optlist: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = [] subprojects = subprojects or [] - dir_option_names = set(cdata.BUILTIN_DIR_OPTIONS) + dir_option_names = set(options.BUILTIN_DIR_OPTIONS) test_option_names = {OptionKey('errorlogs'), OptionKey('stdsplit')} @@ -302,20 +302,20 @@ 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(options: 'cdata.KeyedOptionDictType', section: str) -> None: - for key, opt in sorted(options.items()): + def add_keys(opts: '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'} - if isinstance(opt, cdata.UserStringOption): + if isinstance(opt, options.UserStringOption): typestr = 'string' - elif isinstance(opt, cdata.UserBooleanOption): + elif isinstance(opt, options.UserBooleanOption): typestr = 'boolean' - elif isinstance(opt, cdata.UserComboOption): + elif isinstance(opt, options.UserComboOption): optdict['choices'] = opt.choices typestr = 'combo' - elif isinstance(opt, cdata.UserIntegerOption): + elif isinstance(opt, options.UserIntegerOption): typestr = 'integer' - elif isinstance(opt, cdata.UserArrayOption): + elif isinstance(opt, options.UserArrayOption): typestr = 'array' if opt.choices: optdict['choices'] = opt.choices diff --git a/mesonbuild/modules/_qt.py b/mesonbuild/modules/_qt.py index 7effa1f58..ebb8a3994 100644 --- a/mesonbuild/modules/_qt.py +++ b/mesonbuild/modules/_qt.py @@ -11,7 +11,7 @@ import xml.etree.ElementTree as ET from . import ModuleReturnValue, ExtensionModule from .. import build -from .. import coredata +from .. import options from .. import mlog from ..dependencies import find_external_dependency, Dependency, ExternalLibrary, InternalDependency from ..mesonlib import MesonException, File, version_compare, Popen_safe @@ -256,7 +256,7 @@ class QtBaseModule(ExtensionModule): @noPosargs @typed_kwargs( 'qt.has_tools', - KwargInfo('required', (bool, coredata.UserFeatureOption), default=False), + KwargInfo('required', (bool, options.UserFeatureOption), default=False), KwargInfo('method', str, default='auto'), ) def has_tools(self, state: 'ModuleState', args: T.Tuple, kwargs: 'HasToolKwArgs') -> bool: diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index ebe0d92d5..1a7307079 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -14,7 +14,7 @@ from .. import build from .. import dependencies from .. import mesonlib from .. import mlog -from ..coredata import BUILTIN_DIR_OPTIONS +from ..options import BUILTIN_DIR_OPTIONS from ..dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface from ..interpreter.type_checking import D_MODULE_VERSIONS_KW, INSTALL_DIR_KW, VARIABLES_KW, NoneType from ..interpreterbase import FeatureNew, FeatureDeprecated diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index 59b5050c0..d195a3fa5 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -9,7 +9,7 @@ import typing as T from . import ExtensionModule, ModuleInfo from .. import mesonlib from .. import mlog -from ..coredata import UserFeatureOption +from ..options import UserFeatureOption from ..build import known_shmod_kwargs, CustomTarget, CustomTargetIndex, BuildTarget, GeneratedList, StructuredSources, ExtractedObjects, SharedModule from ..dependencies import NotFoundDependency from ..dependencies.detect import get_dep_identifier, find_external_dependency diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 599da65d3..ffa46cda6 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -7,6 +7,7 @@ import re import typing as T from . import coredata +from . import options from . import mesonlib from . import mparser from . import mlog @@ -66,7 +67,7 @@ class OptionInterpreter: def __init__(self, subproject: 'SubProject') -> None: self.options: 'coredata.MutableKeyedOptionDictType' = {} self.subproject = subproject - self.option_types: T.Dict[str, T.Callable[..., coredata.UserOption]] = { + self.option_types: T.Dict[str, T.Callable[..., options.UserOption]] = { 'string': self.string_parser, 'boolean': self.boolean_parser, 'combo': self.combo_parser, @@ -179,7 +180,7 @@ class OptionInterpreter: since='0.60.0', since_values={str: '0.63.0'}, ), - KwargInfo('yield', bool, default=coredata.DEFAULT_YIELDING, since='0.45.0'), + KwargInfo('yield', bool, default=options.DEFAULT_YIELDING, since='0.45.0'), allow_unknown=True, ) @typed_pos_args('option', str) @@ -208,8 +209,8 @@ class OptionInterpreter: 'string option', KwargInfo('value', str, default=''), ) - def string_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArgs) -> coredata.UserOption: - return coredata.UserStringOption(name, description, kwargs['value'], *args) + 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) @typed_kwargs( 'boolean option', @@ -221,20 +222,20 @@ class OptionInterpreter: deprecated_values={str: ('1.1.0', 'use a boolean, not a string')}, ), ) - def boolean_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: BooleanArgs) -> coredata.UserOption: - return coredata.UserBooleanOption(name, description, kwargs['value'], *args) + 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) @typed_kwargs( 'combo option', KwargInfo('value', (str, NoneType)), KwargInfo('choices', ContainerTypeInfo(list, str, allow_empty=False), required=True), ) - def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> coredata.UserOption: + def combo_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: ComboArgs) -> options.UserOption: choices = kwargs['choices'] value = kwargs['value'] if value is None: value = kwargs['choices'][0] - return coredata.UserComboOption(name, description, choices, value, *args) + return options.UserComboOption(name, description, choices, value, *args) @typed_kwargs( 'integer option', @@ -248,17 +249,17 @@ class OptionInterpreter: KwargInfo('min', (int, NoneType)), KwargInfo('max', (int, NoneType)), ) - def integer_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: IntegerArgs) -> coredata.UserOption: + 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 coredata.UserIntegerOption(name, description, inttuple, *args) + return options.UserIntegerOption(name, description, inttuple, *args) @typed_kwargs( 'string array option', KwargInfo('value', (ContainerTypeInfo(list, str), str, NoneType)), KwargInfo('choices', ContainerTypeInfo(list, str), default=[]), ) - def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> coredata.UserOption: + def string_array_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: StringArrayArgs) -> options.UserOption: choices = kwargs['choices'] value = kwargs['value'] if kwargs['value'] is not None else choices if isinstance(value, str): @@ -266,14 +267,14 @@ class OptionInterpreter: FeatureDeprecated('String value for array option', '1.3.0').use(self.subproject) else: raise mesonlib.MesonException('Value does not define an array: ' + value) - return coredata.UserArrayOption(name, description, value, - choices=choices, - yielding=args[0], - deprecated=args[1]) + return options.UserArrayOption(name, description, value, + choices=choices, + yielding=args[0], + deprecated=args[1]) @typed_kwargs( 'feature option', KwargInfo('value', str, default='auto', validator=in_set_validator({'auto', 'enabled', 'disabled'})), ) - def feature_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> coredata.UserOption: - return coredata.UserFeatureOption(name, description, kwargs['value'], *args) + def feature_parser(self, name: str, description: str, args: T.Tuple[bool, _DEPRECATED_ARGS], kwargs: FeatureArgs) -> options.UserOption: + return options.UserFeatureOption(name, description, kwargs['value'], *args) diff --git a/mesonbuild/options.py b/mesonbuild/options.py new file mode 100644 index 000000000..2699dd5ce --- /dev/null +++ b/mesonbuild/options.py @@ -0,0 +1,480 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2024 Contributors to the The Meson project + +from collections import OrderedDict +from itertools import chain +import argparse + +from .mesonlib import ( + HoldableObject, + OptionKey, + default_prefix, + default_datadir, + default_includedir, + default_infodir, + default_libdir, + default_libexecdir, + default_localedir, + default_mandir, + default_sbindir, + default_sysconfdir, + MesonException, + listify_array_value, +) + +from . import mlog + +import typing as T + +DEFAULT_YIELDING = False + +# Can't bind this near the class method it seems, sadly. +_T = T.TypeVar('_T') + +backendlist = ['ninja', 'vs', 'vs2010', 'vs2012', 'vs2013', 'vs2015', 'vs2017', 'vs2019', 'vs2022', 'xcode', 'none'] +genvslitelist = ['vs2022'] +buildtypelist = ['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'] + + +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 + + def listify(self, value: T.Any) -> T.List[T.Any]: + return [value] + + def printable_value(self) -> T.Union[str, int, bool, T.List[T.Union[str, int, bool]]]: + assert isinstance(self.value, (str, int, bool, list)) + return self.value + + # 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. + 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) + 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, + 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 + +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) + + def __bool__(self) -> bool: + return self.value + + def validate_value(self, value: T.Any) -> bool: + if isinstance(value, bool): + return value + if not isinstance(value, str): + raise MesonException(f'Option "{self.name}" value {value} cannot be converted to a boolean') + if value.lower() == 'true': + return True + if value.lower() == 'false': + return False + raise MesonException(f'Option "{self.name}" value {value} is not boolean (true or false).') + +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) + + def validate_value(self, value: T.Any) -> int: + if isinstance(value, str): + value = 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 + + def toint(self, valuestring: str) -> int: + try: + return int(valuestring) + 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 + # So we need to use oct instead of dec here if we do not want values to be misinterpreted. + def __str__(self) -> str: + return oct(int(self)) + +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'] + + def printable_value(self) -> str: + if self.value == 'preserve': + return self.value + return format(self.value, '04o') + + def validate_value(self, value: T.Any) -> T.Union[str, OctalInt]: + if value == 'preserve': + return 'preserve' + return OctalInt(super().validate_value(value)) + + def toint(self, valuestring: T.Union[str, OctalInt]) -> int: + try: + return int(valuestring, 8) + except ValueError as e: + raise MesonException(f'Invalid mode for option "{self.name}" {e}') + +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: + if isinstance(value, bool): + _type = 'boolean' + elif isinstance(value, (int, float)): + _type = 'number' + else: + _type = 'string' + optionsstring = ', '.join([f'"{item}"' for item in self.choices]) + raise MesonException('Value "{}" (of type "{}") for option "{}" is not one of the choices.' + ' Possible choices are (as string): {}.'.format( + value, _type, self.name, optionsstring)) + return value + +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) + + def listify(self, value: T.Any) -> T.List[T.Any]: + try: + return listify_array_value(value, self.split_args) + except MesonException as e: + raise MesonException(f'error in option "{self.name}": {e!s}') + + def validate_value(self, value: T.Union[str, T.List[str]]) -> T.List[str]: + newvalue = self.listify(value) + + if not self.allow_dups and len(set(newvalue)) != len(newvalue): + msg = 'Duplicated values in array option is deprecated. ' \ + 'This will become a hard error in the future.' + mlog.deprecation(msg) + for i in newvalue: + if not isinstance(i, str): + raise MesonException(f'String array element "{newvalue!s}" for option "{self.name}" is not a string.') + if self.choices: + bad = [x for x in newvalue if x not in self.choices] + if bad: + raise MesonException('Value{} "{}" for option "{}" {} not in allowed choices: "{}"'.format( + '' if len(bad) == 1 else 's', + ', '.join(bad), + self.name, + 'is' if len(bad) == 1 else 'are', + ', '.join(self.choices)) + ) + 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) + + +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 + + def is_enabled(self) -> bool: + return self.value == 'enabled' + + def is_disabled(self) -> bool: + return self.value == 'disabled' + + def is_auto(self) -> bool: + return self.value == 'auto' + +class UserStdOption(UserComboOption): + ''' + UserOption specific to c_std and cpp_std options. User can set a list of + STDs in preference order and it selects the first one supported by current + compiler. + + For historical reasons, some compilers (msvc) allowed setting a GNU std and + silently fell back to C std. This is now deprecated. Projects that support + both GNU and MSVC compilers should set e.g. c_std=gnu11,c11. + + This is not using self.deprecated mechanism we already have for project + options because we want to print a warning if ALL values are deprecated, not + if SOME values are deprecated. + ''' + def __init__(self, lang: str, all_stds: T.List[str]) -> None: + self.lang = lang.lower() + self.all_stds = ['none'] + all_stds + # 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') + + 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) + self.choices += versions + if gnu: + gnu_stds_map = {f'gnu{std[1:]}': std for std in versions} + if gnu_deprecated: + self.deprecated_stds.update(gnu_stds_map) + else: + self.choices += gnu_stds_map.keys() + + def validate_value(self, value: T.Union[str, T.List[str]]) -> str: + try: + candidates = listify_array_value(value) + except MesonException as e: + raise MesonException(f'error in option "{self.name}": {e!s}') + unknown = ','.join(std for std in candidates if std not in self.all_stds) + if unknown: + raise MesonException(f'Unknown option "{self.name}" value {unknown}. Possible values are {self.all_stds}.') + # Check first if any of the candidates are not deprecated + for std in candidates: + if std in self.choices: + return std + # Fallback to a deprecated std if any + for std in candidates: + newstd = self.deprecated_stds.get(std) + if newstd is not None: + mlog.deprecation( + f'None of the values {candidates} are supported by the {self.lang} compiler.\n' + + f'However, the deprecated {std} std currently falls back to {newstd}.\n' + + 'This will be an error in the future.\n' + + 'If the project supports both GNU and MSVC compilers, a value such as\n' + + '"c_std=gnu11,c11" specifies that GNU is preferred but it can safely fallback to plain c11.') + return newstd + raise MesonException(f'None of values {candidates} are supported by the {self.lang.upper()} compiler. ' + + f'Possible values for option "{self.name}" are {self.choices}') + + +class BuiltinOption(T.Generic[_T, _U]): + + """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, *, + choices: T.Any = None, readonly: bool = False): + self.opt_type = opt_type + self.description = description + self.default = default + self.choices = choices + self.yielding = yielding + self.readonly = readonly + + def init_option(self, name: 'OptionKey', value: T.Optional[T.Any], prefix: str) -> _U: + """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} + if self.choices: + keywords['choices'] = self.choices + o = self.opt_type(name.name, self.description, **keywords) + o.readonly = self.readonly + return o + + def _argparse_action(self) -> T.Optional[str]: + # If the type is a boolean, the presence of the argument in --foo form + # is to enable it. Disabling happens by using -Dfoo=false, which is + # parsed under `args.projectoptions` and does not hit this codepath. + if isinstance(self.default, bool): + return 'store_true' + return None + + 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 + def argparse_name_to_arg(name: str) -> str: + if name == 'warning_level': + return '--warnlevel' + else: + return '--' + name.replace('_', '-') + + def prefixed_default(self, name: 'OptionKey', prefix: str = '') -> T.Any: + if self.opt_type in [UserComboOption, UserIntegerOption]: + return self.default + try: + return BUILTIN_DIR_NOPREFIX_OPTIONS[name][prefix] + except KeyError: + pass + return self.default + + def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: + kwargs = OrderedDict() + + c = self._argparse_choices() + b = self._argparse_action() + h = self.description + if not b: + h = '{} (default: {}).'.format(h.rstrip('.'), self.prefixed_default(name)) + else: + kwargs['action'] = b + if c and not b: + kwargs['choices'] = c + kwargs['default'] = argparse.SUPPRESS + kwargs['dest'] = name + + cmdline_name = self.argparse_name_to_arg(name) + parser.add_argument(cmdline_name, help=h + help_suffix, **kwargs) + + +# Update `docs/markdown/Builtin-options.md` after changing the options below +# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required. +# Please also update completion scripts in $MESONSRC/data/shell-completions/ +BUILTIN_DIR_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ + (OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), + (OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')), + (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', default_datadir())), + (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', default_includedir())), + (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', default_infodir())), + (OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())), + (OptionKey('licensedir'), BuiltinOption(UserStringOption, 'Licenses directory', '')), + (OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), + (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', default_localedir())), + (OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), + (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', default_mandir())), + (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', default_sbindir())), + (OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), + (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', default_sysconfdir())), +]) + +BUILTIN_CORE_OPTIONS: T.Dict['OptionKey', 'BuiltinOption'] = OrderedDict([ + (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), + (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist, + readonly=True)), + (OptionKey('genvslite'), + BuiltinOption( + UserComboOption, + 'Setup multiple buildtype-suffixed ninja-backend build directories, ' + 'and a [builddir]_vs containing a Visual Studio meta-backend with multiple configurations that calls into them', + 'vs2022', + choices=genvslitelist) + ), + (OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug', + choices=buildtypelist)), + (OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Enable debug symbols and other information', True)), + (OptionKey('default_library'), BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], + yielding=False)), + (OptionKey('errorlogs'), BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), + (OptionKey('install_umask'), BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), + (OptionKey('layout'), BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), + (OptionKey('optimization'), BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['plain', '0', 'g', '1', '2', '3', 's'])), + (OptionKey('prefer_static'), BuiltinOption(UserBooleanOption, 'Whether to try static linking before shared linking', False)), + (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('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('vsenv'), BuiltinOption(UserBooleanOption, 'Activate Visual Studio environment', False, readonly=True)), + + # Pkgconfig module + (OptionKey('relocatable', module='pkgconfig'), + BuiltinOption(UserBooleanOption, 'Generate pkgconfig files as relocatable', False)), + + # Python module + (OptionKey('bytecompile', module='python'), + BuiltinOption(UserIntegerOption, 'Whether to compile bytecode', (-1, 2, 0))), + (OptionKey('install_env', module='python'), + BuiltinOption(UserComboOption, 'Which python environment to install to', 'prefix', choices=['auto', 'prefix', 'system', 'venv'])), + (OptionKey('platlibdir', module='python'), + BuiltinOption(UserStringOption, 'Directory for site-specific, platform-specific files.', '')), + (OptionKey('purelibdir', module='python'), + BuiltinOption(UserStringOption, 'Directory for site-specific, non-platform-specific files.', '')), + (OptionKey('allow_limited_api', module='python'), + BuiltinOption(UserBooleanOption, 'Whether to allow use of the Python Limited API', True)), +]) + +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', [])), +]) + +# Special prefix-dependent defaults for installation directories that reside in +# a path outside of the prefix in FHS and common usage. +BUILTIN_DIR_NOPREFIX_OPTIONS: T.Dict[OptionKey, T.Dict[str, str]] = { + OptionKey('sysconfdir'): {'/usr': '/etc'}, + OptionKey('localstatedir'): {'/usr': '/var', '/usr/local': '/var/local'}, + OptionKey('sharedstatedir'): {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, + OptionKey('platlibdir', module='python'): {}, + OptionKey('purelibdir', module='python'): {}, +} + + +class OptionStore: + def __init__(self): + # This class will hold all options for a given build directory + self.dummy = None diff --git a/run_project_tests.py b/run_project_tests.py index 23561d973..974273fc7 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -41,7 +41,8 @@ from mesonbuild.compilers import compiler_from_language from mesonbuild.build import ConfigurationData from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof, setup_vsenv from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green -from mesonbuild.coredata import backendlist, version as meson_version +from mesonbuild.coredata import version as meson_version +from mesonbuild.options import backendlist from mesonbuild.modules.python import PythonExternalProgram from run_tests import ( get_fake_options, run_configure, get_meson_script, get_backend_commands, diff --git a/run_tests.py b/run_tests.py index 6d33dd99e..63eb62c19 100755 --- a/run_tests.py +++ b/run_tests.py @@ -33,7 +33,8 @@ from mesonbuild import mesonmain from mesonbuild import mtest from mesonbuild import mlog from mesonbuild.environment import Environment, detect_ninja, detect_machine_info -from mesonbuild.coredata import backendlist, version as meson_version +from mesonbuild.coredata import version as meson_version +from mesonbuild.options import backendlist from mesonbuild.mesonlib import OptionKey, setup_vsenv if T.TYPE_CHECKING: diff --git a/test cases/unit/116 empty project/expected_mods.json b/test cases/unit/116 empty project/expected_mods.json index 7463bcb12..19f56a5f7 100644 --- a/test cases/unit/116 empty project/expected_mods.json +++ b/test cases/unit/116 empty project/expected_mods.json @@ -225,6 +225,7 @@ "mesonbuild.mparser", "mesonbuild.msetup", "mesonbuild.optinterpreter", + "mesonbuild.options", "mesonbuild.programs", "mesonbuild.scripts", "mesonbuild.scripts.meson_exe", @@ -237,6 +238,6 @@ "mesonbuild.wrap", "mesonbuild.wrap.wrap" ], - "count": 68 + "count": 69 } } diff --git a/unittests/datatests.py b/unittests/datatests.py index b14bbac5a..19664e378 100644 --- a/unittests/datatests.py +++ b/unittests/datatests.py @@ -14,6 +14,7 @@ import mesonbuild.dependencies.factory import mesonbuild.envconfig import mesonbuild.environment import mesonbuild.coredata +import mesonbuild.options import mesonbuild.modules.gnome from mesonbuild.interpreter import Interpreter from mesonbuild.ast import AstInterpreter @@ -139,8 +140,8 @@ class DataTests(unittest.TestCase): found_entries |= options self.assertEqual(found_entries, { - *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS), - *(str(k.evolve(module=None)) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE), + *(str(k.evolve(module=None)) for k in mesonbuild.options.BUILTIN_OPTIONS), + *(str(k.evolve(module=None)) for k in mesonbuild.options.BUILTIN_OPTIONS_PER_MACHINE), }) # Check that `buildtype` table inside `Core options` matches how diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py index ffc4b47ad..33a789b4b 100644 --- a/unittests/platformagnostictests.py +++ b/unittests/platformagnostictests.py @@ -274,7 +274,7 @@ class PlatformAgnosticTests(BasePlatformTests): expected = json.load(f)['meson']['modules'] self.assertEqual(data['modules'], expected) - self.assertEqual(data['count'], 68) + self.assertEqual(data['count'], 69) def test_meson_package_cache_dir(self): # Copy testdir into temporary directory to not pollute meson source tree.