diff --git a/mesonbuild/ast/introspection.py b/mesonbuild/ast/introspection.py index eca869fa3e2c..97ebb5ac49ed 100644 --- a/mesonbuild/ast/introspection.py +++ b/mesonbuild/ast/introspection.py @@ -19,7 +19,7 @@ from .visitor import AstVisitor from .. import compilers, environment, mesonlib, optinterpreter from .. import coredata as cdata -from ..mesonlib import MachineChoice +from ..mesonlib import MachineChoice, OptionKey from ..interpreterbase import InvalidArguments, TYPE_nvar from ..build import BuildTarget, Executable, Jar, SharedLibrary, SharedModule, StaticLibrary from ..mparser import BaseNode, ArithmeticNode, ArrayNode, ElementaryNode, IdNode, FunctionNode, StringNode @@ -65,7 +65,7 @@ def __init__(self, self.coredata = self.environment.get_coredata() self.option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') self.backend = backend - self.default_options = {'backend': self.backend} + self.default_options = {OptionKey('backend'): self.backend} self.project_data = {} # type: T.Dict[str, T.Any] self.targets = [] # type: T.List[T.Dict[str, T.Any]] self.dependencies = [] # type: T.List[T.Dict[str, T.Any]] @@ -103,11 +103,11 @@ def func_project(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[s if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject) oi.process(self.option_file) - self.coredata.merge_user_options(oi.options) + self.coredata.update_project_options(oi.options) def_opts = self.flatten_args(kwargs.get('default_options', [])) _project_default_options = mesonlib.stringlistify(def_opts) - self.project_default_options = cdata.create_options_dict(_project_default_options) + self.project_default_options = cdata.create_options_dict(_project_default_options, self.subproject) self.default_options.update(self.project_default_options) self.coredata.set_default_options(self.default_options, self.subproject, self.environment) @@ -125,7 +125,7 @@ def func_project(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[s self.do_subproject(i) self.coredata.init_backend_options(self.backend) - options = {k: v for k, v in self.environment.raw_options.items() if k.startswith('backend_')} + options = {k: v for k, v in self.environment.options.items() if k.is_backend()} self.coredata.set_options(options) self._add_languages(proj_langs, MachineChoice.HOST) @@ -269,7 +269,7 @@ def traverse_nodes(inqueue: T.List[BaseNode]) -> T.List[BaseNode]: return new_target def build_library(self, node: BaseNode, args: T.List[TYPE_nvar], kwargs: T.Dict[str, TYPE_nvar]) -> T.Optional[T.Dict[str, T.Any]]: - default_library = self.coredata.get_builtin_option('default_library') + default_library = self.coredata.get_option(OptionKey('default_library')) if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibrary) elif default_library == 'static': diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index b9f175a0bbb5..ec3aca647e2c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -31,11 +31,13 @@ from .. import mlog from ..compilers import languages_using_ldflags from ..mesonlib import ( - File, MachineChoice, MesonException, OrderedSet, OptionOverrideProxy, - classify_unity_sources, unholder + File, MachineChoice, MesonException, OptionType, OrderedSet, OptionOverrideProxy, + classify_unity_sources, unholder, OptionKey ) if T.TYPE_CHECKING: + from ..arglist import CompilerArgs + from ..compilers import Compiler from ..interpreter import Interpreter, Test @@ -209,24 +211,21 @@ def get_target_filename(self, t, *, warn_multi_output: bool = True): def get_target_filename_abs(self, target): return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)) - def get_base_options_for_target(self, target): + def get_base_options_for_target(self, target: build.BuildTarget) -> OptionOverrideProxy: return OptionOverrideProxy(target.option_overrides_base, - self.environment.coredata.builtins, - self.environment.coredata.base_options) + {k: v for k, v in self.environment.coredata.options.items() + if k.type in {OptionType.BASE, OptionType.BUILTIN}}) - def get_compiler_options_for_target(self, target): - comp_reg = self.environment.coredata.compiler_options[target.for_machine] + def get_compiler_options_for_target(self, target: build.BuildTarget) -> OptionOverrideProxy: + comp_reg = {k: v for k, v in self.environment.coredata.options.items() if k.is_compiler()} comp_override = target.option_overrides_compiler - return { - lang: OptionOverrideProxy(comp_override[lang], comp_reg[lang]) - for lang in set(comp_reg.keys()) | set(comp_override.keys()) - } + return OptionOverrideProxy(comp_override, comp_reg) - def get_option_for_target(self, option_name, target): + def get_option_for_target(self, option_name: 'OptionKey', target: build.BuildTarget): if option_name in target.option_overrides_base: override = target.option_overrides_base[option_name] return self.environment.coredata.validate_option_value(option_name, override) - return self.environment.coredata.get_builtin_option(option_name, target.subproject) + return self.environment.coredata.get_option(option_name.evolve(subproject=target.subproject)) def get_target_filename_for_linking(self, target): # On some platforms (msvc for instance), the file that is used for @@ -251,7 +250,7 @@ def get_target_filename_for_linking(self, target): @lru_cache(maxsize=None) def get_target_dir(self, target): - if self.environment.coredata.get_builtin_option('layout') == 'mirror': + if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': dirname = target.get_subdir() else: dirname = 'meson-out' @@ -300,7 +299,7 @@ def generate_unity_files(self, target, unity_src): abs_files = [] result = [] compsrcs = classify_unity_sources(target.compilers.values(), unity_src) - unity_size = self.get_option_for_target('unity_size', target) + unity_size = self.get_option_for_target(OptionKey('unity_size'), target) def init_language_file(suffix, unity_file_number): unity_src = self.get_unity_source_file(target, suffix, unity_file_number) @@ -542,7 +541,7 @@ def rpaths_for_bundled_shared_libraries(self, target, exclude_system=True): return paths def determine_rpath_dirs(self, target): - if self.environment.coredata.get_builtin_option('layout') == 'mirror': + if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': result = target.get_link_dep_subdirs() else: result = OrderedSet() @@ -621,7 +620,8 @@ def determine_ext_objs(self, extobj, proj_dir_to_build_root): if self.is_unity(extobj.target): compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources) sources = [] - unity_size = self.get_option_for_target('unity_size', extobj.target) + unity_size = self.get_option_for_target(OptionKey('unity_size'), extobj.target) + for comp, srcs in compsrcs.items(): for i in range(len(srcs) // unity_size + 1): osrc = self.get_unity_source_file(extobj.target, @@ -672,13 +672,13 @@ def escape_extra_args(compiler, args): return extra_args - def generate_basic_compiler_args(self, target, compiler, no_warn_args=False): + def generate_basic_compiler_args(self, target: build.BuildTarget, compiler: 'Compiler', no_warn_args: bool = False) -> 'CompilerArgs': # Create an empty commands list, and start adding arguments from # various sources in the order in which they must override each other # starting from hard-coded defaults followed by build options and so on. commands = compiler.compiler_args() - copt_proxy = self.get_compiler_options_for_target(target)[compiler.language] + copt_proxy = self.get_compiler_options_for_target(target) # First, the trivial ones that are impossible to override. # # Add -nostdinc/-nostdinc++ if needed; can't be overridden @@ -690,20 +690,20 @@ def generate_basic_compiler_args(self, target, compiler, no_warn_args=False): if no_warn_args: commands += compiler.get_no_warn_args() else: - commands += compiler.get_warn_args(self.get_option_for_target('warning_level', target)) + commands += compiler.get_warn_args(self.get_option_for_target(OptionKey('warning_level'), target)) # Add -Werror if werror=true is set in the build options set on the # command-line or default_options inside project(). This only sets the # action to be done for warnings if/when they are emitted, so it's ok # to set it after get_no_warn_args() or get_warn_args(). - if self.get_option_for_target('werror', target): + if self.get_option_for_target(OptionKey('werror'), target): commands += compiler.get_werror_args() # Add compile args for c_* or cpp_* build options set on the # command-line or default_options inside project(). commands += compiler.get_option_compile_args(copt_proxy) # Add buildtype args: optimization level, debugging, etc. - commands += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) - commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target)) - commands += compiler.get_debug_args(self.get_option_for_target('debug', target)) + commands += compiler.get_buildtype_args(self.get_option_for_target(OptionKey('buildtype'), target)) + commands += compiler.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) + commands += compiler.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) # MSVC debug builds have /ZI argument by default and /Zi is added with debug flag # /ZI needs to be removed in that case to avoid cl's warning to that effect (D9025 : overriding '/ZI' with '/Zi') if ('/ZI' in commands) and ('/Zi' in commands): @@ -1022,7 +1022,7 @@ def get_custom_target_provided_libraries(self, target): return libs def is_unity(self, target): - optval = self.get_option_for_target('unity', target) + optval = self.get_option_for_target(OptionKey('unity'), target) if optval == 'on' or (optval == 'subprojects' and target.subproject != ''): return True return False @@ -1183,7 +1183,7 @@ def create_install_data(self) -> InstallData: self.environment.get_build_dir(), self.environment.get_prefix(), strip_bin, - self.environment.coredata.get_builtin_option('install_umask'), + self.environment.coredata.get_option(OptionKey('install_umask')), self.environment.get_build_command() + ['introspect'], self.environment.coredata.version) self.generate_depmf_install(d) @@ -1228,7 +1228,7 @@ def generate_target_install(self, d): # # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more # fine-grained stripping of static archives. - should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target('strip', t) + should_strip = not isinstance(t, build.StaticLibrary) and self.get_option_for_target(OptionKey('strip'), t) # Install primary build output (library/executable/jar, etc) # Done separately because of strip/aliases/rpath if outdirs[0] is not False: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index b847c2cbac95..d66708c59074 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -43,11 +43,14 @@ File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine, ProgressBar, quote_arg, unholder, ) -from ..mesonlib import get_compiler_for_source, has_path_sep +from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey from .backends import CleanTrees from ..build import InvalidArguments from ..interpreter import Interpreter +if T.TYPE_CHECKING: + from ..linkers import StaticLinker + FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]" FORTRAN_MODULE_PAT = r"^\s*\bmodule\b\s+(\w+)\s*(?:!+.*)*$" FORTRAN_SUBMOD_PAT = r"^\s*\bsubmodule\b\s*\((\w+:?\w+)\)\s*(\w+)" @@ -514,7 +517,7 @@ def generate(self): outfile.write('# Do not edit by hand.\n\n') outfile.write('ninja_required_version = 1.8.2\n\n') - num_pools = self.environment.coredata.backend_options['backend_max_links'].value + num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value if num_pools > 0: outfile.write('''pool link_pool depth = {} @@ -534,8 +537,9 @@ def generate(self): self.add_build_comment(NinjaComment('Install rules')) self.generate_install() self.generate_dist() - if 'b_coverage' in self.environment.coredata.base_options and \ - self.environment.coredata.base_options['b_coverage'].value: + key = OptionKey('b_coverage') + if (key in self.environment.coredata.options and + self.environment.coredata.options[key].value): self.add_build_comment(NinjaComment('Coverage rules')) self.generate_coverage_rules() self.add_build_comment(NinjaComment('Suffix')) @@ -811,7 +815,7 @@ def generate_target(self, target): source2object[s] = o obj_list.append(o) - use_pch = self.environment.coredata.base_options.get('b_pch', False) + use_pch = self.environment.coredata.options.get(OptionKey('b_pch')) if use_pch and target.has_pch(): pch_objects = self.generate_pch(target, header_deps=header_deps) else: @@ -890,7 +894,7 @@ def should_use_dyndeps_for_target(self, target: 'build.BuildTarget') -> bool: cpp = target.compilers['cpp'] if cpp.get_id() != 'msvc': return False - if self.environment.coredata.compiler_options[target.for_machine]['cpp']['std'] != 'c++latest': + if self.environment.coredata.options[OptionKey('std', machine=target.for_machine, lang='cpp')] != 'latest': return False if not mesonlib.current_vs_supports_modules(): return False @@ -1124,9 +1128,9 @@ def generate_install(self): def generate_tests(self): self.serialize_tests() cmd = self.environment.get_build_command(True) + ['test', '--no-rebuild'] - if not self.environment.coredata.get_builtin_option('stdsplit'): + if not self.environment.coredata.get_option(OptionKey('stdsplit')): cmd += ['--no-stdsplit'] - if self.environment.coredata.get_builtin_option('errorlogs'): + if self.environment.coredata.get_option(OptionKey('errorlogs')): cmd += ['--print-errorlogs'] elem = NinjaBuildElement(self.all_outputs, 'meson-test', 'CUSTOM_COMMAND', ['all', 'PHONY']) elem.add_item('COMMAND', cmd) @@ -1294,8 +1298,8 @@ def generate_cs_resource_tasks(self, target): args.append(a) return args, deps - def generate_cs_target(self, target): - buildtype = self.get_option_for_target('buildtype', target) + def generate_cs_target(self, target: build.BuildTarget): + buildtype = self.get_option_for_target(OptionKey('buildtype'), target) fname = target.get_filename() outname_rel = os.path.join(self.get_target_dir(target), fname) src_list = target.get_sources() @@ -1304,8 +1308,8 @@ def generate_cs_target(self, target): deps = [] commands = compiler.compiler_args(target.extra_args.get('cs', [])) commands += compiler.get_buildtype_args(buildtype) - commands += compiler.get_optimization_args(self.get_option_for_target('optimization', target)) - commands += compiler.get_debug_args(self.get_option_for_target('debug', target)) + commands += compiler.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) + commands += compiler.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) if isinstance(target, build.Executable): commands.append('-target:exe') elif isinstance(target, build.SharedLibrary): @@ -1346,7 +1350,7 @@ def generate_cs_target(self, target): def determine_single_java_compile_args(self, target, compiler): args = [] - args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target)) + args += compiler.get_buildtype_args(self.get_option_for_target(OptionKey('buildtype'), target)) args += self.build.get_global_args(compiler, target.for_machine) args += self.build.get_project_args(compiler, target.subproject, target.for_machine) args += target.get_java_args() @@ -1509,7 +1513,7 @@ def generate_vala_compile(self, target): valac_outputs.append(vala_c_file) args = self.generate_basic_compiler_args(target, valac) - args += valac.get_colorout_args(self.environment.coredata.base_options.get('b_colorout').value) + args += valac.get_colorout_args(self.environment.coredata.options.get(OptionKey('b_colorout')).value) # Tell Valac to output everything in our private directory. Sadly this # means it will also preserve the directory components of Vala sources # found inside the build tree (generated sources). @@ -1613,12 +1617,12 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: for a in rustc.linker.get_always_args(): args += ['-C', 'link-arg={}'.format(a)] - opt_proxy = self.get_compiler_options_for_target(target)[rustc.language] + opt_proxy = self.get_compiler_options_for_target(target) args += ['--crate-name', target.name] - args += rustc.get_buildtype_args(self.get_option_for_target('buildtype', target)) - args += rustc.get_debug_args(self.get_option_for_target('debug', target)) - args += rustc.get_optimization_args(self.get_option_for_target('optimization', target)) + args += rustc.get_buildtype_args(self.get_option_for_target(OptionKey('buildtype'), target)) + args += rustc.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) + args += rustc.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) args += rustc.get_option_compile_args(opt_proxy) args += self.build.get_global_args(rustc, target.for_machine) args += self.build.get_project_args(rustc, target.subproject, target.for_machine) @@ -1769,8 +1773,8 @@ def generate_swift_target(self, target): raise InvalidArguments('Swift target {} contains a non-swift source file.'.format(target.get_basename())) os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True) compile_args = swiftc.get_compile_only_args() - compile_args += swiftc.get_optimization_args(self.get_option_for_target('optimization', target)) - compile_args += swiftc.get_debug_args(self.get_option_for_target('debug', target)) + compile_args += swiftc.get_optimization_args(self.get_option_for_target(OptionKey('optimization'), target)) + compile_args += swiftc.get_debug_args(self.get_option_for_target(OptionKey('debug'), target)) compile_args += swiftc.get_module_args(module_name) compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine) compile_args += self.build.get_global_args(swiftc, target.for_machine) @@ -1846,7 +1850,7 @@ def generate_swift_target(self, target): self.create_target_source_introspection(target, swiftc, compile_args + header_imports + module_includes, relsrc, rel_generated) def generate_static_link_rules(self): - num_pools = self.environment.coredata.backend_options['backend_max_links'].value + num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value if 'java' in self.environment.coredata.compilers.host: self.generate_java_link() for for_machine in MachineChoice: @@ -1879,7 +1883,7 @@ def generate_static_link_rules(self): extra=pool)) def generate_dynamic_link_rules(self): - num_pools = self.environment.coredata.backend_options['backend_max_links'].value + num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value for for_machine in MachineChoice: complist = self.environment.coredata.compilers[for_machine] for langname, compiler in complist.items(): @@ -2495,7 +2499,7 @@ def generate_single_compile(self, target, src, is_generated=False, header_deps=N commands += self.get_compile_debugfile_args(compiler, target, rel_obj) # PCH handling - if self.environment.coredata.base_options.get('b_pch', False): + if self.environment.coredata.options.get(OptionKey('b_pch')): commands += self.get_pch_include_args(compiler, target) pchlist = target.get_pch(compiler.language) else: @@ -2695,7 +2699,7 @@ def get_target_type_link_args(self, target, linker): commands += linker.get_pie_link_args() elif isinstance(target, build.SharedLibrary): if isinstance(target, build.SharedModule): - options = self.environment.coredata.base_options + options = self.environment.coredata.options commands += linker.get_std_shared_module_link_args(options) else: commands += linker.get_std_shared_lib_link_args() @@ -2831,7 +2835,7 @@ def generate_prelink(self, target, obj_list): self.add_build(elem) return [prelink_name] - def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdlib_args=None): + def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T.Union['Compiler', 'StaticLinker'], extra_args=None, stdlib_args=None): extra_args = extra_args if extra_args is not None else [] stdlib_args = stdlib_args if stdlib_args is not None else [] implicit_outs = [] @@ -2866,9 +2870,9 @@ def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdl # Add things like /NOLOGO; usually can't be overridden commands += linker.get_linker_always_args() # Add buildtype linker args: optimization level, etc. - commands += linker.get_buildtype_linker_args(self.get_option_for_target('buildtype', target)) + commands += linker.get_buildtype_linker_args(self.get_option_for_target(OptionKey('buildtype'), target)) # Add /DEBUG and the pdb filename when using MSVC - if self.get_option_for_target('debug', target): + if self.get_option_for_target(OptionKey('debug'), target): commands += self.get_link_debugfile_args(linker, target, outname) debugfile = self.get_link_debugfile_name(linker, target, outname) if debugfile is not None: @@ -2928,14 +2932,14 @@ def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdl # to be after all internal and external libraries so that unresolved # symbols from those can be found here. This is needed when the # *_winlibs that we want to link to are static mingw64 libraries. - if hasattr(linker, 'get_language'): + if isinstance(linker, Compiler): # The static linker doesn't know what language it is building, so we # don't know what option. Fortunately, it doesn't care to see the # language-specific options either. # # We shouldn't check whether we are making a static library, because # in the LTO case we do use a real compiler here. - commands += linker.get_option_link_args(self.environment.coredata.compiler_options[target.for_machine][linker.get_language()]) + commands += linker.get_option_link_args(self.environment.coredata.options) dep_targets = [] dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal)) @@ -3029,8 +3033,8 @@ def generate_gcov_clean(self): def get_user_option_args(self): cmds = [] - for (k, v) in self.environment.coredata.user_options.items(): - cmds.append('-D' + k + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower())) + for (k, v) in self.environment.coredata.options.items(): + cmds.append('-D' + str(k) + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower())) # The order of these arguments must be the same between runs of Meson # to ensure reproducible output. The order we pass them shouldn't # affect behavior in any other way. @@ -3152,8 +3156,8 @@ def generate_ending(self): if ctlist: elem.add_dep(self.generate_custom_target_clean(ctlist)) - if 'b_coverage' in self.environment.coredata.base_options and \ - self.environment.coredata.base_options['b_coverage'].value: + if OptionKey('b_coverage') in self.environment.coredata.options and \ + self.environment.coredata.options[OptionKey('b_coverage')].value: self.generate_gcov_clean() elem.add_dep('clean-gcda') elem.add_dep('clean-gcno') diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 6d81e6904527..6e070a7b8648 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -28,7 +28,7 @@ from .. import compilers from ..interpreter import Interpreter from ..mesonlib import ( - MesonException, File, python_command, replace_if_different + MesonException, File, python_command, replace_if_different, OptionKey, ) from ..environment import Environment, build_filename @@ -181,9 +181,9 @@ def generate(self): self.platform = 'ARM' else: raise MesonException('Unsupported Visual Studio platform: ' + target_machine) - self.buildtype = self.environment.coredata.get_builtin_option('buildtype') - self.optimization = self.environment.coredata.get_builtin_option('optimization') - self.debug = self.environment.coredata.get_builtin_option('debug') + self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype')) + self.optimization = self.environment.coredata.get_option(OptionKey('optimization')) + self.debug = self.environment.coredata.get_option(OptionKey('debug')) sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') projlist = self.generate_projects() self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj')) @@ -316,7 +316,7 @@ def generate_solution(self, sln_filename, projlist): prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n' for prj in projlist: coredata = self.environment.coredata - if coredata.get_builtin_option('layout') == 'mirror': + if coredata.get_option(OptionKey('layout')) == 'mirror': self.generate_solution_dirs(ofile, prj[1].parents) target = self.build.targets[prj[0]] lang = 'default' @@ -403,7 +403,7 @@ def generate_solution(self, sln_filename, projlist): replace_if_different(sln_filename, sln_filename_tmp) def generate_projects(self): - startup_project = self.environment.coredata.backend_options['backend_startup_project'].value + startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value projlist = [] startup_idx = 0 for (i, (name, target)) in enumerate(self.build.targets.items()): @@ -785,7 +785,7 @@ def gen_vcxproj(self, target, ofname, guid): build_args += compiler.get_optimization_args(self.optimization) build_args += compiler.get_debug_args(self.debug) buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype) - vscrt_type = self.environment.coredata.base_options['b_vscrt'] + vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')] project_name = target.name target_name = target.name root = ET.Element('Project', {'DefaultTargets': "Build", @@ -878,7 +878,7 @@ def gen_vcxproj(self, target, ofname, guid): # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise # cl will give warning D9025: overriding '/Ehs' with cpp_eh value if 'cpp' in target.compilers: - eh = self.environment.coredata.compiler_options[target.for_machine]['cpp']['eh'] + eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')] if eh.value == 'a': ET.SubElement(clconf, 'ExceptionHandling').text = 'Async' elif eh.value == 's': @@ -926,7 +926,7 @@ def gen_vcxproj(self, target, ofname, guid): file_args[l] += compilers.get_base_compile_args( self.get_base_options_for_target(target), comp) file_args[l] += comp.get_option_compile_args( - self.environment.coredata.compiler_options[target.for_machine][comp.language]) + self.environment.coredata.options) # Add compile args added using add_project_arguments() for l, args in self.build.projects_args[target.for_machine].get(target.subproject, {}).items(): @@ -940,10 +940,8 @@ def gen_vcxproj(self, target, ofname, guid): # Compile args added from the env or cross file: CFLAGS/CXXFLAGS, etc. We want these # to override all the defaults, but not the per-target compile args. for l in file_args.keys(): - opts = self.environment.coredata.compiler_options[target.for_machine][l] - k = 'args' - if k in opts: - file_args[l] += opts[k].value + opts = self.environment.coredata.options[OptionKey('args', machine=target.for_machine, lang=l)] + file_args[l] += opts.value for args in file_args.values(): # This is where Visual Studio will insert target_args, target_defines, # etc, which are added later from external deps (see below). @@ -1050,9 +1048,9 @@ def gen_vcxproj(self, target, ofname, guid): ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines) ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true' # Warning level - warning_level = self.get_option_for_target('warning_level', target) + warning_level = self.get_option_for_target(OptionKey('warning_level'), target) ET.SubElement(clconf, 'WarningLevel').text = 'Level' + str(1 + int(warning_level)) - if self.get_option_for_target('werror', target): + if self.get_option_for_target(OptionKey('werror'), target): ET.SubElement(clconf, 'TreatWarningAsError').text = 'true' # Optimization flags o_flags = split_o_flags_args(build_args) @@ -1077,7 +1075,7 @@ def gen_vcxproj(self, target, ofname, guid): ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed' # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default pch_sources = {} - if self.environment.coredata.base_options.get('b_pch', False): + if self.environment.coredata.options.get(OptionKey('b_pch')): for lang in ['c', 'cpp']: pch = target.get_pch(lang) if not pch: @@ -1113,7 +1111,7 @@ def gen_vcxproj(self, target, ofname, guid): ET.SubElement(link, 'GenerateDebugInformation').text = 'false' if not isinstance(target, build.StaticLibrary): if isinstance(target, build.SharedModule): - options = self.environment.coredata.base_options + options = self.environment.coredata.options extra_link_args += compiler.get_std_shared_module_link_args(options) # Add link args added using add_project_link_arguments() extra_link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine) @@ -1146,8 +1144,7 @@ def gen_vcxproj(self, target, ofname, guid): # to be after all internal and external libraries so that unresolved # symbols from those can be found here. This is needed when the # *_winlibs that we want to link to are static mingw64 libraries. - extra_link_args += compiler.get_option_link_args( - self.environment.coredata.compiler_options[compiler.for_machine][comp.language]) + extra_link_args += compiler.get_option_link_args(self.environment.coredata.options) (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native()) # Add more libraries to be linked if needed @@ -1226,7 +1223,7 @@ def gen_vcxproj(self, target, ofname, guid): # /nologo ET.SubElement(link, 'SuppressStartupBanner').text = 'true' # /release - if not self.environment.coredata.get_builtin_option('debug'): + if not self.environment.coredata.get_option(OptionKey('debug')): ET.SubElement(link, 'SetChecksum').text = 'true' meson_file_group = ET.SubElement(root, 'ItemGroup') @@ -1426,9 +1423,9 @@ def gen_testproj(self, target_name, ofname): ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c' # FIXME: No benchmarks? test_command = self.environment.get_build_command() + ['test', '--no-rebuild'] - if not self.environment.coredata.get_builtin_option('stdsplit'): + if not self.environment.coredata.get_option(OptionKey('stdsplit')): test_command += ['--no-stdsplit'] - if self.environment.coredata.get_builtin_option('errorlogs'): + if self.environment.coredata.get_option(OptionKey('errorlogs')): test_command += ['--print-errorlogs'] self.serialize_tests() self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command))) diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py index ef0c956e9d9c..0e39c65b0d28 100644 --- a/mesonbuild/backend/xcodebackend.py +++ b/mesonbuild/backend/xcodebackend.py @@ -61,7 +61,7 @@ def gen_id(self): return str(uuid.uuid4()).upper().replace('-', '')[:24] def get_target_dir(self, target): - dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_builtin_option('buildtype')) + dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_option(mesonlib.OptionKey('buildtype'))) os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True) return dirname diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 091bfc8f8b20..dacf68b9072c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from collections import OrderedDict, defaultdict +from collections import OrderedDict from functools import lru_cache import copy import hashlib @@ -28,16 +28,18 @@ from .mesonlib import ( File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, extract_as_list, typeslistify, stringlistify, classify_unity_sources, - get_filenames_templates_dict, substitute_values, has_path_sep, unholder + get_filenames_templates_dict, substitute_values, has_path_sep, unholder, + OptionKey, ) from .compilers import ( - Compiler, all_languages, is_object, clink_langs, sort_clink, lang_suffixes, + Compiler, is_object, clink_langs, sort_clink, lang_suffixes, is_known_suffix ) from .linkers import StaticLinker from .interpreterbase import FeatureNew if T.TYPE_CHECKING: + from .coredata import KeyedOptionDictType, OptionDictType from .interpreter import Test from .mesonlib import FileMode, FileOrString @@ -402,7 +404,7 @@ def get_env(self, full_env: T.Dict[str, str]) -> T.Dict[str, str]: return env class Target: - def __init__(self, name, subdir, subproject, build_by_default, for_machine: MachineChoice): + def __init__(self, name: str, subdir: str, subproject: str, build_by_default: bool, for_machine: MachineChoice): if has_path_sep(name): # Fix failing test 53 when this becomes an error. mlog.warning('''Target "{}" has a path separator in its name. @@ -415,8 +417,8 @@ def __init__(self, name, subdir, subproject, build_by_default, for_machine: Mach self.for_machine = for_machine self.install = False self.build_always_stale = False - self.option_overrides_base = {} - self.option_overrides_compiler = defaultdict(dict) + self.option_overrides_base: T.Dict[OptionKey, str] = {} + self.option_overrides_compiler: T.Dict[OptionKey, str] = {} self.extra_files = [] # type: T.List[File] if not hasattr(self, 'typename'): raise RuntimeError('Target type is not set for target class "{}". This is a bug'.format(type(self).__name__)) @@ -497,7 +499,7 @@ def get_id(self) -> str: return self.construct_id_from_path( self.subdir, self.name, self.type_suffix()) - def process_kwargs_base(self, kwargs): + def process_kwargs_base(self, kwargs: T.Dict[str, T.Any]) -> None: if 'build_by_default' in kwargs: self.build_by_default = kwargs['build_by_default'] if not isinstance(self.build_by_default, bool): @@ -510,23 +512,22 @@ def process_kwargs_base(self, kwargs): option_overrides = self.parse_overrides(kwargs) for k, v in option_overrides.items(): - if '_' in k: - lang, k2 = k.split('_', 1) - if lang in all_languages: - self.option_overrides_compiler[lang][k2] = v - continue + if k.lang: + self.option_overrides_compiler[k.evolve(machine=self.for_machine)] = v + continue self.option_overrides_base[k] = v - def parse_overrides(self, kwargs) -> dict: - result = {} + @staticmethod + def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[OptionKey, str]: + result: T.Dict[OptionKey, str] = {} overrides = stringlistify(kwargs.get('override_options', [])) for o in overrides: if '=' not in o: raise InvalidArguments('Overrides must be of form "key=value"') k, v = o.split('=', 1) - k = k.strip() + key = OptionKey.from_string(k.strip()) v = v.strip() - result[k] = v + result[key] = v return result def is_linkable_target(self) -> bool: @@ -544,7 +545,7 @@ class BuildTarget(Target): def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, sources: T.List[File], objects, environment: environment.Environment, kwargs): super().__init__(name, subdir, subproject, True, for_machine) - unity_opt = environment.coredata.get_builtin_option('unity') + unity_opt = environment.coredata.get_option(OptionKey('unity')) self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '') self.environment = environment self.sources = [] @@ -1064,17 +1065,18 @@ def validate_win_subsystem(self, value: str) -> str: raise InvalidArguments('Invalid value for win_subsystem: {}.'.format(value)) return value - def _extract_pic_pie(self, kwargs, arg, environment, option): + def _extract_pic_pie(self, kwargs, arg: str, environment, option: str): # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags all_flags = self.extra_args['c'] + self.extra_args['cpp'] if '-f' + arg.lower() in all_flags or '-f' + arg.upper() in all_flags: mlog.warning("Use the '{}' kwarg instead of passing '{}' manually to {!r}".format(arg, '-f' + arg, self.name)) return True + k = OptionKey(option) if arg in kwargs: val = kwargs[arg] - elif option in environment.coredata.base_options: - val = environment.coredata.base_options[option].value + elif k in environment.coredata.options: + val = environment.coredata.options[k].value else: val = False @@ -1595,8 +1597,9 @@ class Executable(BuildTarget): def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, sources: T.List[File], objects, environment: environment.Environment, kwargs): self.typename = 'executable' - if 'pie' not in kwargs and 'b_pie' in environment.coredata.base_options: - kwargs['pie'] = environment.coredata.base_options['b_pie'].value + key = OptionKey('b_pie') + if 'pie' not in kwargs and key in environment.coredata.options: + kwargs['pie'] = environment.coredata.options[key].value super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) # Unless overridden, executables have no suffix or prefix. Except on # Windows and with C#/Mono executables where the suffix is 'exe' diff --git a/mesonbuild/cmake/executor.py b/mesonbuild/cmake/executor.py index 19971e37a271..674b854c83e2 100644 --- a/mesonbuild/cmake/executor.py +++ b/mesonbuild/cmake/executor.py @@ -23,7 +23,7 @@ import os from .. import mlog -from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice, is_windows +from ..mesonlib import PerMachine, Popen_safe, version_compare, MachineChoice, is_windows, OptionKey from ..envconfig import get_env_var if T.TYPE_CHECKING: @@ -62,7 +62,7 @@ def __init__(self, environment: 'Environment', version: str, for_machine: Machin self.cmakebin = None return - self.prefix_paths = self.environment.coredata.builtins_per_machine[self.for_machine]['cmake_prefix_path'].value + self.prefix_paths = self.environment.coredata.options[OptionKey('cmake_prefix_path', machine=self.for_machine)].value env_pref_path_raw = get_env_var( self.for_machine, self.environment.is_cross_build(), diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 8aaeb190b167..1a533db8602b 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -22,7 +22,7 @@ from .toolchain import CMakeToolchain, CMakeExecScope from .traceparser import CMakeTraceParser, CMakeGeneratorTarget from .. import mlog, mesonlib -from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible +from ..mesonlib import MachineChoice, OrderedSet, version_compare, path_is_in_root, relative_to_if_possible, OptionKey from ..mesondata import mesondata from ..compilers.compilers import lang_suffixes, header_suffixes, obj_suffixes, lib_suffixes, is_header from enum import Enum @@ -383,7 +383,7 @@ def postprocess(self, output_target_map: OutputTargetMap, root_src_dir: Path, su cfgs += [x for x in tgt.properties['CONFIGURATIONS'] if x] cfg = cfgs[0] - is_debug = self.env.coredata.get_builtin_option('debug'); + is_debug = self.env.coredata.get_option(OptionKey('debug')); if is_debug: if 'DEBUG' in cfgs: cfg = 'DEBUG' @@ -598,10 +598,10 @@ def _all_source_suffixes(self) -> T.List[str]: @lru_cache(maxsize=None) def _all_lang_stds(self, lang: str) -> T.List[str]: - lang_opts = self.env.coredata.compiler_options.build.get(lang, None) - if not lang_opts or 'std' not in lang_opts: + try: + res = self.env.coredata.options[OptionKey('std', machine=MachineChoice.BUILD, lang=lang)].choices + except KeyError: return [] - res = lang_opts['std'].choices # TODO: Get rid of this once we have propper typing for options assert isinstance(res, list) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 985f6f3ae916..311e65a4164e 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -16,7 +16,7 @@ import typing as T from .. import coredata -from ..mesonlib import MachineChoice, MesonException, mlog, version_compare +from ..mesonlib import MachineChoice, MesonException, mlog, version_compare, OptionKey from ..linkers import LinkerEnvVarsMixin from .c_function_attributes import C_FUNC_ATTRIBUTES from .mixins.clike import CLikeCompiler @@ -39,7 +39,7 @@ ) if T.TYPE_CHECKING: - from ..coredata import OptionDictType + from ..coredata import KeyedOptionDictType from ..dependencies import Dependency, ExternalProgram from ..envconfig import MachineInfo from ..environment import Environment @@ -95,10 +95,10 @@ def has_header_symbol(self, hname: str, symbol: str, prefix: str, return self.compiles(t.format(**fargs), env, extra_args=extra_args, dependencies=dependencies) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() opts.update({ - 'std': coredata.UserComboOption( + OptionKey('std', machine=self.for_machine, lang=self.language): coredata.UserComboOption( 'C langauge standard to use', ['none'], 'none', @@ -119,7 +119,7 @@ class _ClangCStds(CompilerMixinBase): _C18_VERSION = '>=8.0.0' _C2X_VERSION = '>=9.0.0' - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() c_stds = ['c89', 'c99', 'c11'] g_stds = ['gnu89', 'gnu99', 'gnu11'] @@ -134,7 +134,7 @@ def get_options(self) -> 'OptionDictType': if version_compare(self.version, self._C2X_VERSION): c_stds += ['c2x'] g_stds += ['gnu2x'] - opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds return opts @@ -153,28 +153,28 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() if self.info.is_windows() or self.info.is_cygwin(): opts.update({ - 'winlibs': coredata.UserArrayOption( + OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption( 'Standard Win libraries to link against', gnu_winlibs, ), }) return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] if std.value != 'none': args.append('-std=' + std.value) return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - libs = options['winlibs'].value.copy() + libs = options[OptionKey('winlibs', machine=self.for_machine, lang=self.language)].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -223,19 +223,20 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c90', 'c99', 'c11', 'gnu90', 'gnu99', 'gnu11'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] if std.value != 'none': args.append('-std=' + std.value) return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] @@ -257,7 +258,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) c_stds = ['c89', 'c99', 'c11'] g_stds = ['gnu89', 'gnu99', 'gnu11'] @@ -267,27 +268,28 @@ def get_options(self) -> 'OptionDictType': if version_compare(self.version, self._C2X_VERSION): c_stds += ['c2x'] g_stds += ['gnu2x'] - opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + c_stds + g_stds if self.info.is_windows() or self.info.is_cygwin(): opts.update({ - 'winlibs': coredata.UserArrayOption( + key.evolve('winlibs'): coredata.UserArrayOption( 'Standard Win libraries to link against', gnu_winlibs, ), }) return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + std = options[OptionKey('std', lang=self.language, machine=self.for_machine)] if std.value != 'none': args.append('-std=' + std.value) return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typeddict mypy can't figure this out - libs = options['winlibs'].value.copy() + libs: T.List[str] = options[OptionKey('winlibs', lang=self.language, machine=self.for_machine)].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -331,9 +333,9 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic ElbrusCompiler.__init__(self) # It does support some various ISO standards and c/gnu 90, 9x, 1x in addition to those which GNU CC supports. - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = [ # type: ignore + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = [ 'none', 'c89', 'c90', 'c9x', 'c99', 'c1x', 'c11', 'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11', 'iso9899:2011', 'iso9899:1990', 'iso9899:199409', 'iso9899:1999', @@ -368,18 +370,18 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) c_stds = ['c89', 'c99'] g_stds = ['gnu89', 'gnu99'] if version_compare(self.version, '>=16.0.0'): c_stds += ['c11'] - opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + opts[OptionKey('std', machine=self.for_machine, lang=self.language)].choices = ['none'] + c_stds + g_stds return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] if std.value != 'none': args.append('-std=' + std.value) return args @@ -389,19 +391,20 @@ class VisualStudioLikeCCompilerMixin(CompilerMixinBase): """Shared methods that apply to MSVC-like C compilers.""" - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() opts.update({ - 'winlibs': coredata.UserArrayOption( + OptionKey('winlibs', machine=self.for_machine, lang=self.language): coredata.UserArrayOption( 'Windows libs to link against.', msvc_winlibs, ), }) return opts - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a TypeDict to make this work - libs = options['winlibs'].value.copy() + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -423,7 +426,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version) MSVCCompiler.__init__(self, target) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() c_stds = ['c89', 'c99'] # Need to have these to be compatible with projects @@ -436,12 +439,13 @@ def get_options(self) -> 'OptionDictType': if version_compare(self.version, self._C17_VERSION): c_stds += ['c17', 'c18'] g_stds += ['gnu17', 'gnu18'] - opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + c_stds + g_stds return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + std = options[OptionKey('std', machine=self.for_machine, lang=self.language)] if std.value.startswith('gnu'): mlog.log_once( 'cl.exe does not actually support gnu standards, and meson ' @@ -466,8 +470,9 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version) ClangClCompiler.__init__(self, target) - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: - std = options['std'].value + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key].value if std != "none": return ['/clang:-std={}'.format(std)] return [] @@ -487,14 +492,16 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version) IntelVisualStudioLikeCompiler.__init__(self, target) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() - opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value == 'c89': mlog.log_once("ICL doesn't explicitly implement c89, setting the standard to 'none', which is close.") elif std.value != 'none': @@ -513,14 +520,16 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version) ArmCompiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('--' + std.value) return args @@ -540,17 +549,19 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_always_args(self) -> T.List[str]: return ['-nologo'] - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c89', 'c99'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99'] return opts def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value == 'c89': args.append('-lang=c') elif std.value == 'c99': @@ -585,17 +596,19 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic info, exe_wrapper, linker=linker, full_version=full_version) Xc16Compiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'gnu89', 'gnu99'] return opts def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['c_std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('-ansi') args.append('-std=' + std.value) @@ -628,12 +641,13 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic info, exe_wrapper, linker=linker, full_version=full_version) CompCertCompiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c89', 'c99'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_no_optimization_args(self) -> T.List[str]: @@ -664,17 +678,19 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_always_args(self) -> T.List[str]: return [] - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CCompiler.get_options(self) - opts['std'].choices = ['none', 'c89', 'c99', 'c11'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c89', 'c99', 'c11'] return opts def get_no_stdinc_args(self) -> T.List[str]: return [] - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['c_std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('--' + std.value) return args diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 0bd2b4c8110c..234ce06e93ae 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -25,7 +25,7 @@ from ..linkers import LinkerEnvVarsMixin from ..mesonlib import ( EnvironmentException, MachineChoice, MesonException, - Popen_safe, split_args, LibType, TemporaryDirectoryWinProof + Popen_safe, split_args, LibType, TemporaryDirectoryWinProof, OptionKey, ) from ..envconfig import ( get_env_var @@ -35,7 +35,7 @@ if T.TYPE_CHECKING: from ..build import BuildTarget - from ..coredata import OptionDictType + from ..coredata import OptionDictType, KeyedOptionDictType from ..envconfig import MachineInfo from ..environment import Environment from ..linkers import DynamicLinker # noqa: F401 @@ -265,36 +265,32 @@ class CompileCheckMode(enum.Enum): clike_debug_args = {False: [], True: ['-g']} # type: T.Dict[bool, T.List[str]] -base_options = {'b_pch': coredata.UserBooleanOption('Use precompiled headers', True), - 'b_lto': coredata.UserBooleanOption('Use link time optimization', False), - 'b_sanitize': coredata.UserComboOption('Code sanitizer to use', - ['none', 'address', 'thread', 'undefined', 'memory', 'address,undefined'], - 'none'), - 'b_lundef': coredata.UserBooleanOption('Use -Wl,--no-undefined when linking', True), - 'b_asneeded': coredata.UserBooleanOption('Use -Wl,--as-needed when linking', True), - 'b_pgo': coredata.UserComboOption('Use profile guided optimization', - ['off', 'generate', 'use'], - 'off'), - 'b_coverage': coredata.UserBooleanOption('Enable coverage tracking.', - False), - 'b_colorout': coredata.UserComboOption('Use colored output', - ['auto', 'always', 'never'], - 'always'), - 'b_ndebug': coredata.UserComboOption('Disable asserts', - ['true', 'false', 'if-release'], 'false'), - 'b_staticpic': coredata.UserBooleanOption('Build static libraries as position independent', - True), - 'b_pie': coredata.UserBooleanOption('Build executables as position independent', - False), - 'b_bitcode': coredata.UserBooleanOption('Generate and embed bitcode (only macOS/iOS/tvOS)', - False), - 'b_vscrt': coredata.UserComboOption('VS run-time library type to use.', - ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype', 'static_from_buildtype'], - 'from_buildtype'), - } # type: OptionDictType - -def option_enabled(boptions: T.List[str], options: 'OptionDictType', - option: str) -> bool: +base_options: 'KeyedOptionDictType' = { + OptionKey('b_pch'): coredata.UserBooleanOption('Use precompiled headers', True), + OptionKey('b_lto'): coredata.UserBooleanOption('Use link time optimization', False), + OptionKey('b_sanitize'): coredata.UserComboOption('Code sanitizer to use', + ['none', 'address', 'thread', 'undefined', 'memory', 'address,undefined'], + 'none'), + OptionKey('b_lundef'): coredata.UserBooleanOption('Use -Wl,--no-undefined when linking', True), + OptionKey('b_asneeded'): coredata.UserBooleanOption('Use -Wl,--as-needed when linking', True), + OptionKey('b_pgo'): coredata.UserComboOption('Use profile guided optimization', + ['off', 'generate', 'use'], + 'off'), + OptionKey('b_coverage'): coredata.UserBooleanOption('Enable coverage tracking.', False), + OptionKey('b_colorout'): coredata.UserComboOption('Use colored output', + ['auto', 'always', 'never'], + 'always'), + OptionKey('b_ndebug'): coredata.UserComboOption('Disable asserts', ['true', 'false', 'if-release'], 'false'), + OptionKey('b_staticpic'): coredata.UserBooleanOption('Build static libraries as position independent', True), + OptionKey('b_pie'): coredata.UserBooleanOption('Build executables as position independent', False), + OptionKey('b_bitcode'): coredata.UserBooleanOption('Generate and embed bitcode (only macOS/iOS/tvOS)', False), + OptionKey('b_vscrt'): coredata.UserComboOption('VS run-time library type to use.', + ['none', 'md', 'mdd', 'mt', 'mtd', 'from_buildtype', 'static_from_buildtype'], + 'from_buildtype'), +} + +def option_enabled(boptions: T.Set[OptionKey], options: 'KeyedOptionDictType', + option: OptionKey) -> bool: try: if option not in boptions: return False @@ -304,23 +300,23 @@ def option_enabled(boptions: T.List[str], options: 'OptionDictType', except KeyError: return False -def get_base_compile_args(options: 'OptionDictType', compiler: 'Compiler') -> T.List[str]: +def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler') -> T.List[str]: args = [] # type T.List[str] try: - if options['b_lto'].value: + if options[OptionKey('b_lto')].value: args.extend(compiler.get_lto_compile_args()) except KeyError: pass try: - args += compiler.get_colorout_args(options['b_colorout'].value) + args += compiler.get_colorout_args(options[OptionKey('b_colorout')].value) except KeyError: pass try: - args += compiler.sanitizer_compile_args(options['b_sanitize'].value) + args += compiler.sanitizer_compile_args(options[OptionKey('b_sanitize')].value) except KeyError: pass try: - pgo_val = options['b_pgo'].value + pgo_val = options[OptionKey('b_pgo')].value if pgo_val == 'generate': args.extend(compiler.get_profile_generate_args()) elif pgo_val == 'use': @@ -328,23 +324,23 @@ def get_base_compile_args(options: 'OptionDictType', compiler: 'Compiler') -> T. except KeyError: pass try: - if options['b_coverage'].value: + if options[OptionKey('b_coverage')].value: args += compiler.get_coverage_args() except KeyError: pass try: - if (options['b_ndebug'].value == 'true' or - (options['b_ndebug'].value == 'if-release' and - options['buildtype'].value in {'release', 'plain'})): + if (options[OptionKey('b_ndebug')].value == 'true' or + (options[OptionKey('b_ndebug')].value == 'if-release' and + options[OptionKey('buildtype')].value in {'release', 'plain'})): args += compiler.get_disable_assert_args() except KeyError: pass # This does not need a try...except - if option_enabled(compiler.base_options, options, 'b_bitcode'): + if option_enabled(compiler.base_options, options, OptionKey('b_bitcode')): args.append('-fembed-bitcode') try: - crt_val = options['b_vscrt'].value - buildtype = options['buildtype'].value + crt_val = options[OptionKey('b_vscrt')].value + buildtype = options[OptionKey('buildtype')].value try: args += compiler.get_crt_compile_args(crt_val, buildtype) except AttributeError: @@ -353,20 +349,20 @@ def get_base_compile_args(options: 'OptionDictType', compiler: 'Compiler') -> T. pass return args -def get_base_link_args(options: 'OptionDictType', linker: 'Compiler', +def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler', is_shared_module: bool) -> T.List[str]: args = [] # type: T.List[str] try: - if options['b_lto'].value: + if options[OptionKey('b_lto')].value: args.extend(linker.get_lto_link_args()) except KeyError: pass try: - args += linker.sanitizer_link_args(options['b_sanitize'].value) + args += linker.sanitizer_link_args(options[OptionKey('b_sanitize')].value) except KeyError: pass try: - pgo_val = options['b_pgo'].value + pgo_val = options[OptionKey('b_pgo')].value if pgo_val == 'generate': args.extend(linker.get_profile_generate_args()) elif pgo_val == 'use': @@ -374,13 +370,13 @@ def get_base_link_args(options: 'OptionDictType', linker: 'Compiler', except KeyError: pass try: - if options['b_coverage'].value: + if options[OptionKey('b_coverage')].value: args += linker.get_coverage_link_args() except KeyError: pass - as_needed = option_enabled(linker.base_options, options, 'b_asneeded') - bitcode = option_enabled(linker.base_options, options, 'b_bitcode') + as_needed = option_enabled(linker.base_options, options, OptionKey('b_asneeded')) + bitcode = option_enabled(linker.base_options, options, OptionKey('b_bitcode')) # Shared modules cannot be built with bitcode_bundle because # -bitcode_bundle is incompatible with -undefined and -bundle if bitcode and not is_shared_module: @@ -394,14 +390,14 @@ def get_base_link_args(options: 'OptionDictType', linker: 'Compiler', if not bitcode: args.extend(linker.headerpad_args()) if (not is_shared_module and - option_enabled(linker.base_options, options, 'b_lundef')): + option_enabled(linker.base_options, options, OptionKey('b_lundef'))): args.extend(linker.no_undefined_link_args()) else: args.extend(linker.get_allow_undefined_link_args()) try: - crt_val = options['b_vscrt'].value - buildtype = options['buildtype'].value + crt_val = options[OptionKey('b_vscrt')].value + buildtype = options[OptionKey('buildtype')].value try: args += linker.get_crt_link_args(crt_val, buildtype) except AttributeError: @@ -477,7 +473,7 @@ def __init__(self, exelist: T.List[str], version: str, self.version = version self.full_version = full_version self.for_machine = for_machine - self.base_options = [] # type: T.List[str] + self.base_options: T.Set[OptionKey] = set() self.linker = linker self.info = info self.is_cross = is_cross @@ -596,13 +592,13 @@ def get_linker_args_from_envvars(self, is_cross: bool) -> T.List[str]: return self.linker.get_args_from_envvars(for_machine, is_cross) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': return {} - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return self.linker.get_option_args(options) def check_header(self, hname: str, prefix: str, env: 'Environment', *, @@ -826,7 +822,7 @@ def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: def get_std_shared_lib_link_args(self) -> T.List[str]: return self.linker.get_std_shared_lib_args() - def get_std_shared_module_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_std_shared_module_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return self.linker.get_std_shared_module_args(options) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: @@ -1243,17 +1239,19 @@ def get_args_from_envvars(lang: str, def get_global_options(lang: str, comp: T.Type[Compiler], for_machine: MachineChoice, - is_cross: bool) -> 'OptionDictType': + is_cross: bool) -> 'KeyedOptionDictType': """Retreive options that apply to all compilers for a given language.""" description = 'Extra arguments passed to the {}'.format(lang) - opts = { - 'args': coredata.UserArrayOption( + argkey = OptionKey('args', lang=lang, machine=for_machine) + largkey = argkey.evolve('link_args') + opts: 'KeyedOptionDictType' = { + argkey: coredata.UserArrayOption( description + ' compiler', [], split_args=True, user_input=True, allow_dups=True), - 'link_args': coredata.UserArrayOption( + largkey: coredata.UserArrayOption( description + ' linker', [], split_args=True, user_input=True, allow_dups=True), - } # type: OptionDictType + } # Get from env vars. compile_args, link_args = get_args_from_envvars( @@ -1262,10 +1260,7 @@ def get_global_options(lang: str, is_cross, comp.INVOKES_LINKER) - for k, o in opts.items(): - if k == 'args': - o.set_value(compile_args) - elif k == 'link_args': - o.set_value(link_args) + opts[argkey].set_value(compile_args) + opts[largkey].set_value(link_args) return opts diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 607bea7ff86b..2e94e483e205 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -19,7 +19,7 @@ from .. import coredata from .. import mlog -from ..mesonlib import MesonException, MachineChoice, version_compare +from ..mesonlib import MesonException, MachineChoice, version_compare, OptionKey from ..linkers import LinkerEnvVarsMixin from .compilers import ( @@ -42,7 +42,7 @@ from .mixins.emscripten import EmscriptenMixin if T.TYPE_CHECKING: - from ..coredata import OptionDictType + from ..coredata import KeyedOptionDictType from ..dependencies import Dependency, ExternalProgram from ..envconfig import MachineInfo from ..environment import Environment @@ -169,10 +169,11 @@ def _find_best_cpp_std(self, cpp_std: str) -> str: raise MesonException('C++ Compiler does not support -std={}'.format(cpp_std)) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'std': coredata.UserComboOption( + key: coredata.UserComboOption( 'C++ language standard to use', ['none'], 'none', @@ -196,47 +197,50 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) + key = OptionKey('key', machine=self.for_machine, lang=self.language) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'rtti': coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), }) - opts['std'].choices = [ # type: ignore + opts[key.evolve('std')].choices = [ 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a', 'gnu++20', ] if self.info.is_windows() or self.info.is_cygwin(): opts.update({ - 'winlibs': coredata.UserArrayOption( + key.evolve('winlibs'): coredata.UserArrayOption( 'Standard Win libraries to link against', gnu_winlibs, ), }) return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) - non_msvc_eh_options(options['eh'].value, args) + non_msvc_eh_options(options[key.evolve('eh')].value, args) - if not options['rtti'].value: + if not options[key.evolve('rtti')].value: args.append('-fno-rtti') return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - libs = options['winlibs'].value.copy() + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -265,9 +269,10 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic defines=defines, full_version=full_version) self.id = 'emscripten' - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) return args @@ -287,32 +292,34 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), }) - opts['std'].choices = [ # type: ignore + opts[key].choices = [ 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'gnu++98', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', ] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('-std=' + std.value) - non_msvc_eh_options(options['eh'].value, args) + non_msvc_eh_options(options[key.evolve('eh')].value, args) return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] @@ -331,53 +338,56 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts = CPPCompiler.get_options(self) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'debugstl': coredata.UserBooleanOption( + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('debugstl'): coredata.UserBooleanOption( 'STL debug mode', False, ) }) - opts['std'].choices = [ # type: ignore + opts[key].choices = [ 'none', 'c++98', 'c++03', 'c++11', 'c++14', 'c++17', 'c++1z', 'c++2a', 'c++20', 'gnu++03', 'gnu++11', 'gnu++14', 'gnu++17', 'gnu++1z', 'gnu++2a', 'gnu++20', ] if self.info.is_windows() or self.info.is_cygwin(): opts.update({ - 'winlibs': coredata.UserArrayOption( + key.evolve('winlibs'): coredata.UserArrayOption( 'Standard Win libraries to link against', gnu_winlibs, ), }) return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) - non_msvc_eh_options(options['eh'].value, args) + non_msvc_eh_options(options[key.evolve('eh')].value, args) - if not options['rtti'].value: + if not options[key.evolve('rtti')].value: args.append('-fno-rtti') - if options['debugstl'].value: + if options[key.evolve('debugstl')].value: args.append('-D_GLIBCXX_DEBUG=1') return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: if self.info.is_windows() or self.info.is_cygwin(): # without a typedict mypy can't understand this. - libs = options['winlibs'].value.copy() + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + libs = options[key].value.copy() assert isinstance(libs, list) for l in libs: assert isinstance(l, str) @@ -424,7 +434,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version, defines=defines) ElbrusCompiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) cpp_stds = [ @@ -438,18 +448,19 @@ def get_options(self) -> 'OptionDictType': if version_compare(self.version, '>=1.25.00'): cpp_stds += [ 'c++2a', 'gnu++2a' ] + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'debugstl': coredata.UserBooleanOption( + key.evolve('debugstl'): coredata.UserBooleanOption( 'STL debug mode', False, ), }) - opts['std'].choices = cpp_stds # type: ignore + opts[key].choices = cpp_stds return opts # Elbrus C++ compiler does not have lchmod, but there is only linker warning, not compiler error. @@ -465,15 +476,16 @@ def has_function(self, funcname: str, prefix: str, env: 'Environment', *, dependencies=dependencies) # Elbrus C++ compiler does not support RTTI, so don't check for it. - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append(self._find_best_cpp_std(std.value)) - non_msvc_eh_options(options['eh'].value, args) + non_msvc_eh_options(options[key.evolve('eh')].value, args) - if options['debugstl'].value: + if options[key.evolve('debugstl')].value: args.append('-D_GLIBCXX_DEBUG=1') return args @@ -494,7 +506,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) # Every Unix compiler under the sun seems to accept -std=c++03, # with the exception of ICC. Instead of preventing the user from @@ -511,36 +523,40 @@ def get_options(self) -> 'OptionDictType': if version_compare(self.version, '>=19.1.0'): c_stds += ['c++2a'] g_stds += ['gnu++2a'] + + + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'debugstl': coredata.UserBooleanOption('STL debug mode', False), + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('debugstl'): coredata.UserBooleanOption('STL debug mode', False), }) - opts['std'].choices = ['none'] + c_stds + g_stds # type: ignore + opts[key].choices = ['none'] + c_stds + g_stds return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': remap_cpp03 = { 'c++03': 'c++98', 'gnu++03': 'gnu++98' } args.append('-std=' + remap_cpp03.get(std.value, std.value)) - if options['eh'].value == 'none': + if options[key.evolve('eh')].value == 'none': args.append('-fno-exceptions') - if not options['rtti'].value: + if not options[key.evolve('rtti')].value: args.append('-fno-rtti') - if options['debugstl'].value: + if options[key.evolve('debugstl')].value: args.append('-D_GLIBCXX_DEBUG=1') return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] @@ -560,30 +576,33 @@ class VisualStudioLikeCPPCompilerMixin(CompilerMixinBase): 'c++latest': (False, "latest"), } - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # need a typeddict for this - return T.cast(T.List[str], options['winlibs'].value[:]) + key = OptionKey('winlibs', machine=self.for_machine, lang=self.language) + return T.cast(T.List[str], options[key].value[:]) - def _get_options_impl(self, opts: 'OptionDictType', cpp_stds: T.List[str]) -> 'OptionDictType': + def _get_options_impl(self, opts: 'KeyedOptionDictType', cpp_stds: T.List[str]) -> 'KeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'eh': coredata.UserComboOption( + key.evolve('eh'): coredata.UserComboOption( 'C++ exception handling type.', ['none', 'default', 'a', 's', 'sc'], 'default', ), - 'rtti': coredata.UserBooleanOption('Enable RTTI', True), - 'winlibs': coredata.UserArrayOption( + key.evolve('rtti'): coredata.UserBooleanOption('Enable RTTI', True), + key.evolve('winlibs'): coredata.UserArrayOption( 'Windows libs to link against.', msvc_winlibs, ), }) - opts['std'].choices = cpp_stds # type: ignore + opts[key.evolve('std')].choices = cpp_stds return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] + key = OptionKey('std', machine=self.for_machine, lang=self.language) - eh = options['eh'] + eh = options[key.evolve('eh')] if eh.value == 'default': args.append('/EHsc') elif eh.value == 'none': @@ -591,10 +610,10 @@ def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: else: args.append('/EH' + eh.value) - if not options['rtti'].value: + if not options[key.evolve('rtti')].value: args.append('/GR-') - permissive, ver = self.VC_VERSION_MAP[options['std'].value] + permissive, ver = self.VC_VERSION_MAP[options[key].value] if ver is not None: args.append('/std:c++{}'.format(ver)) @@ -616,22 +635,23 @@ class CPP11AsCPP14Mixin(CompilerMixinBase): This is a limitation of Clang and MSVC that ICL doesn't share. """ - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: # Note: there is no explicit flag for supporting C++11; we attempt to do the best we can # which means setting the C++ standard version to C++14, in compilers that support it # (i.e., after VS2015U3) # if one is using anything before that point, one cannot set the standard. - if options['std'].value in {'vc++11', 'c++11'}: + key = OptionKey('std', machine=self.for_machine, lang=self.language) + if options[key].value in {'vc++11', 'c++11'}: mlog.warning(self.id, 'does not support C++11;', 'attempting best effort; setting the standard to C++14', once=True) # Don't mutate anything we're going to change, we need to use # deepcopy since we're messing with members, and we can't simply # copy the members because the option proxy doesn't support it. options = copy.deepcopy(options) - if options['std'].value == 'vc++11': - options['std'].value = 'vc++14' + if options[key].value == 'vc++11': + options[key].value = 'vc++14' else: - options['std'].value = 'c++14' + options[key].value = 'c++14' return super().get_option_compile_args(options) @@ -644,10 +664,10 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic CPPCompiler.__init__(self, exelist, version, for_machine, is_cross, info, exe_wrapper, linker=linker, full_version=full_version) MSVCCompiler.__init__(self, target) - self.base_options = ['b_pch', 'b_vscrt', 'b_ndebug'] # FIXME add lto, pgo and the like + self.base_options = {OptionKey(o) for o in ['b_pch', 'b_vscrt', 'b_ndebug']} # FIXME add lto, pgo and the like self.id = 'msvc' - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': cpp_stds = ['none', 'c++11', 'vc++11'] # Visual Studio 2015 and later if version_compare(self.version, '>=19'): @@ -657,11 +677,12 @@ def get_options(self) -> 'OptionDictType': cpp_stds.extend(['vc++14', 'c++17', 'vc++17']) return self._get_options_impl(super().get_options(), cpp_stds) - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: - if options['std'].value != 'none' and version_compare(self.version, '<19.00.24210'): + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: + key = OptionKey('std', machine=self.for_machine, lang=self.language) + if options[key].value != 'none' and version_compare(self.version, '<19.00.24210'): mlog.warning('This version of MSVC does not support cpp_std arguments') options = copy.copy(options) - options['std'].value = 'none' + options[key].value = 'none' args = super().get_option_compile_args(options) @@ -684,7 +705,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic ClangClCompiler.__init__(self, target) self.id = 'clang-cl' - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest'] return self._get_options_impl(super().get_options(), cpp_stds) @@ -700,7 +721,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic info, exe_wrapper, linker=linker, full_version=full_version) IntelVisualStudioLikeCompiler.__init__(self, target) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': # This has only been tested with version 19.0, cpp_stds = ['none', 'c++11', 'vc++11', 'c++14', 'vc++14', 'c++17', 'vc++17', 'c++latest'] return self._get_options_impl(super().get_options(), cpp_stds) @@ -719,21 +740,23 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic info, exe_wrapper, linker=linker, full_version=full_version) ArmCompiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) - opts['std'].choices = ['none', 'c++03', 'c++11'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c++03', 'c++11'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value == 'c++11': args.append('--cpp11') elif std.value == 'c++03': args.append('--cpp') return args - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: @@ -753,7 +776,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic def get_always_args(self) -> T.List[str]: return ['-nologo', '-lang=cpp'] - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_compile_only_args(self) -> T.List[str]: @@ -762,7 +785,7 @@ def get_compile_only_args(self) -> T.List[str]: def get_output_args(self, target: str) -> T.List[str]: return ['-output=obj=%s' % target] - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: @@ -777,15 +800,16 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic info, exe_wrapper, linker=linker, full_version=full_version) C2000Compiler.__init__(self) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = CPPCompiler.get_options(self) - opts['std'].choices = ['none', 'c++03'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'c++03'] return opts def get_always_args(self) -> T.List[str]: return ['-nologo', '-lang=cpp'] - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_compile_only_args(self) -> T.List[str]: @@ -794,7 +818,7 @@ def get_compile_only_args(self) -> T.List[str]: def get_output_args(self, target: str) -> T.List[str]: return ['-output=obj=%s' % target] - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 89fcf400f0d0..7fa3e4f87bc1 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -18,13 +18,16 @@ from .. import coredata from .. import mlog -from ..mesonlib import EnvironmentException, MachineChoice, Popen_safe, OptionOverrideProxy, is_windows, LibType +from ..mesonlib import ( + EnvironmentException, MachineChoice, Popen_safe, OptionOverrideProxy, + is_windows, LibType, OptionKey, +) from .compilers import (Compiler, cuda_buildtype_args, cuda_optimization_args, cuda_debug_args) if T.TYPE_CHECKING: from ..build import BuildTarget - from ..coredata import OptionDictType + from ..coredata import KeyedOptionDictType from ..dependencies import Dependency, ExternalProgram from ..environment import Environment # noqa: F401 from ..envconfig import MachineInfo @@ -195,24 +198,26 @@ def has_header_symbol(self, hname: str, symbol: str, prefix: str, }}''' return self.compiles(t.format_map(fargs), env, extra_args=extra_args, dependencies=dependencies) - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() - opts.update({'std': coredata.UserComboOption('C++ language standard to use with cuda', + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts.update({key: coredata.UserComboOption('C++ language standard to use with cuda', ['none', 'c++03', 'c++11', 'c++14'], 'none')}) return opts - def _to_host_compiler_options(self, options: 'OptionDictType') -> 'OptionDictType': + def _to_host_compiler_options(self, options: 'KeyedOptionDictType') -> 'KeyedOptionDictType': overrides = {name: opt.value for name, opt in options.items()} return OptionOverrideProxy(overrides, self.host_compiler.get_options()) - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] # On Windows, the version of the C++ standard used by nvcc is dictated by # the combination of CUDA version and MSVC version; the --std= is thus ignored # and attempting to use it will result in a warning: https://stackoverflow.com/a/51272091/741027 if not is_windows(): - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('--std=' + std.value) @@ -229,7 +234,7 @@ def _cook_link_args(cls, args: T.List[str]) -> T.List[str]: cooked.append(arg) return cls._to_host_flags(cooked, _Phase.LINKER) - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return self._cook_link_args(self.host_compiler.get_option_link_args(self._to_host_compiler_options(options))) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index ca6de38888a3..eac2aa73a7d6 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -18,7 +18,7 @@ import typing as T from ..mesonlib import ( - EnvironmentException, MachineChoice, version_compare, + EnvironmentException, MachineChoice, version_compare, OptionKey, ) from ..arglist import CompilerArgs @@ -653,8 +653,10 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '1': default_warn_args, '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic']} - self.base_options = ['b_colorout', 'b_sanitize', 'b_staticpic', - 'b_vscrt', 'b_coverage', 'b_pgo', 'b_ndebug'] + self.base_options = { + OptionKey(o) for o in [ + 'b_colorout', 'b_sanitize', 'b_staticpic', 'b_vscrt', + 'b_coverage', 'b_pgo', 'b_ndebug']} self._has_color_support = version_compare(self.version, '>=4.9') # dependencies were implemented before, but broken - support was fixed in GCC 7.1+ @@ -724,7 +726,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version, is_cross=is_cross) DmdLikeCompilerMixin.__init__(self, dmd_frontend_version=find_ldc_dmd_frontend_version(version_output)) self.id = 'llvm' - self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] + self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} def get_colorout_args(self, colortype: str) -> T.List[str]: if colortype == 'always': @@ -782,7 +784,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version, is_cross=is_cross) DmdLikeCompilerMixin.__init__(self, version) self.id = 'dmd' - self.base_options = ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug'] + self.base_options = {OptionKey(o) for o in ['b_coverage', 'b_colorout', 'b_vscrt', 'b_ndebug']} def get_colorout_args(self, colortype: str) -> T.List[str]: if colortype == 'always': diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 4b49e365871b..d65d5853c86b 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -31,11 +31,12 @@ from .mixins.pgi import PGICompiler from mesonbuild.mesonlib import ( - version_compare, EnvironmentException, MesonException, MachineChoice, LibType + version_compare, EnvironmentException, MesonException, MachineChoice, + LibType, OptionKey, ) if T.TYPE_CHECKING: - from ..coredata import OptionDictType + from ..coredata import KeyedOptionDictType from ..dependencies import Dependency, ExternalProgram from ..envconfig import MachineInfo from ..environment import Environment @@ -71,7 +72,7 @@ def sanity_check(self, work_dir_: str, environment: 'Environment') -> None: source_name.write_text('print *, "Fortran compilation is working."; end') - extra_flags = [] + extra_flags: T.List[str] = [] extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) extra_flags += self.get_always_args() @@ -150,10 +151,11 @@ def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[ def has_multi_link_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: return self._has_multi_link_arguments(args, env, 'stop; end program') - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = super().get_options() + key = OptionKey('std', machine=self.for_machine, lang=self.language) opts.update({ - 'std': coredata.UserComboOption( + key: coredata.UserComboOption( 'Fortran language standard to use', ['none'], 'none', @@ -179,19 +181,21 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-Wextra'], '3': default_warn_args + ['-Wextra', '-Wpedantic', '-fimplicit-none']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = FortranCompiler.get_options(self) fortran_stds = ['legacy', 'f95', 'f2003'] if version_compare(self.version, '>=4.4.0'): fortran_stds += ['f2008'] if version_compare(self.version, '>=8.0.0'): fortran_stds += ['f2018'] - opts['std'].choices = ['none'] + fortran_stds # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none'] + fortran_stds return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('-std=' + std.value) return args @@ -313,14 +317,16 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['-warn', 'unused'], '3': ['-warn', 'all']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = FortranCompiler.get_options(self) - opts['std'].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} if std.value != 'none': args.append('-stand=' + stds[std.value]) @@ -363,14 +369,16 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic '2': default_warn_args + ['/warn:unused'], '3': ['/warn:all']} - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': opts = FortranCompiler.get_options(self) - opts['std'].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] # type: ignore + key = OptionKey('std', machine=self.for_machine, lang=self.language) + opts[key].choices = ['none', 'legacy', 'f95', 'f2003', 'f2008', 'f2018'] return opts - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] stds = {'legacy': 'none', 'f95': 'f95', 'f2003': 'f03', 'f2008': 'f08', 'f2018': 'f18'} if std.value != 'none': args.append('/stand:' + stds[std.value]) diff --git a/mesonbuild/compilers/mixins/arm.py b/mesonbuild/compilers/mixins/arm.py index ee7d3375310b..beb5fd570ccc 100644 --- a/mesonbuild/compilers/mixins/arm.py +++ b/mesonbuild/compilers/mixins/arm.py @@ -19,6 +19,7 @@ from ... import mesonlib from ...linkers import ArmClangDynamicLinker +from ...mesonlib import OptionKey from ..compilers import clike_debug_args from .clang import clang_color_args @@ -145,8 +146,10 @@ def __init__(self) -> None: if not mesonlib.version_compare(self.version, '==' + self.linker.version): raise mesonlib.EnvironmentException('armlink version does not match with compiler version') self.id = 'armclang' - self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', - 'b_ndebug', 'b_staticpic', 'b_colorout'] + self.base_options = { + OptionKey(o) for o in + ['b_pch', 'b_lto', 'b_pgo', 'b_sanitize', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_colorout']} # Assembly self.can_compile_suffixes.add('s') diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index 2e50577aa127..fcb22258ea1f 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -20,6 +20,7 @@ from ... import mesonlib from ...linkers import AppleDynamicLinker +from ...mesonlib import OptionKey from ..compilers import CompileCheckMode from .gnu import GnuLikeCompiler @@ -48,11 +49,11 @@ def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.id = 'clang' self.defines = defines or {} - self.base_options.append('b_colorout') + self.base_options.add(OptionKey('b_colorout')) # TODO: this really should be part of the linker base_options, but # linkers don't have base_options. if isinstance(self.linker, AppleDynamicLinker): - self.base_options.append('b_bitcode') + self.base_options.add(OptionKey('b_bitcode')) # All Clang backends can also do LLVM IR self.can_compile_suffixes.add('ll') @@ -108,7 +109,7 @@ def openmp_flags(self) -> T.List[str]: else: # Shouldn't work, but it'll be checked explicitly in the OpenMP dependency. return [] - + @classmethod def use_linker_args(cls, linker: str) -> T.List[str]: # Clang additionally can use a linker specified as a path, which GCC diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index dca09ea78fd1..3288c00f901b 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -35,6 +35,7 @@ from ... import mlog from ...linkers import GnuLikeDynamicLinkerMixin, SolarisDynamicLinker, CompCertDynamicLinker from ...mesonlib import LibType +from ...coredata import OptionKey from .. import compilers from ..compilers import CompileCheckMode from .visualstudio import VisualStudioLikeCompiler @@ -393,14 +394,16 @@ def _get_basic_compiler_args(self, env: 'Environment', mode: CompileCheckMode) - # linking with static libraries since MSVC won't select a CRT for # us in that case and will error out asking us to pick one. try: - crt_val = env.coredata.base_options['b_vscrt'].value - buildtype = env.coredata.builtins['buildtype'].value + crt_val = env.coredata.options[OptionKey('b_vscrt')].value + buildtype = env.coredata.options[OptionKey('buildtype')].value cargs += self.get_crt_compile_args(crt_val, buildtype) except (KeyError, AttributeError): pass # Add CFLAGS/CXXFLAGS/OBJCFLAGS/OBJCXXFLAGS and CPPFLAGS from the env sys_args = env.coredata.get_external_args(self.for_machine, self.language) + if isinstance(sys_args, str): + sys_args = [sys_args] # Apparently it is a thing to inject linker flags both # via CFLAGS _and_ LDFLAGS, even though the former are # also used during linking. These flags can break diff --git a/mesonbuild/compilers/mixins/elbrus.py b/mesonbuild/compilers/mixins/elbrus.py index 2ea359950654..16f621005821 100644 --- a/mesonbuild/compilers/mixins/elbrus.py +++ b/mesonbuild/compilers/mixins/elbrus.py @@ -21,7 +21,7 @@ from .gnu import GnuLikeCompiler from .gnu import gnu_optimization_args -from ...mesonlib import Popen_safe +from ...mesonlib import Popen_safe, OptionKey if T.TYPE_CHECKING: from ...environment import Environment @@ -34,9 +34,7 @@ class ElbrusCompiler(GnuLikeCompiler): def __init__(self) -> None: super().__init__() self.id = 'lcc' - self.base_options = ['b_pgo', 'b_coverage', - 'b_ndebug', 'b_staticpic', - 'b_lundef', 'b_asneeded'] + self.base_options = {OptionKey(o) for o in ['b_pgo', 'b_coverage', 'b_ndebug', 'b_staticpic', 'b_lundef', 'b_asneeded']} # FIXME: use _build_wrapper to call this so that linker flags from the env # get applied diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index cd18b35a53ad..fc0b21e39410 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -18,6 +18,7 @@ import typing as T from ... import coredata +from ...mesonlib import OptionKey if T.TYPE_CHECKING: from ...environment import Environment @@ -50,15 +51,16 @@ def thread_flags(self, env: 'Environment') -> T.List[str]: def thread_link_flags(self, env: 'Environment') -> T.List[str]: args = ['-s', 'USE_PTHREADS=1'] - count = env.coredata.compiler_options[self.for_machine][self.language]['thread_count'].value # type: int + count: int = env.coredata.options[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value if count: args.extend(['-s', 'PTHREAD_POOL_SIZE={}'.format(count)]) return args - def get_options(self) -> 'coredata.OptionDictType': + def get_options(self) -> 'coredata.KeyedOptionDictType': opts = super().get_options() + key = OptionKey('thread_count', machine=self.for_machine, lang=self.language) opts.update({ - 'thread_count': coredata.UserIntegerOption( + key: coredata.UserIntegerOption( '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/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 3d43162f250d..95bcd7cc345e 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -24,6 +24,7 @@ from ... import mesonlib from ... import mlog +from ...mesonlib import OptionKey if T.TYPE_CHECKING: from ...environment import Environment @@ -146,14 +147,15 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): LINKER_PREFIX = '-Wl,' def __init__(self) -> None: - self.base_options = ['b_pch', 'b_lto', 'b_pgo', 'b_coverage', - 'b_ndebug', 'b_staticpic', 'b_pie'] + self.base_options = { + OptionKey(o) for o in ['b_pch', 'b_lto', 'b_pgo', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_pie']} if not (self.info.is_windows() or self.info.is_cygwin() or self.info.is_openbsd()): - self.base_options.append('b_lundef') + self.base_options.add(OptionKey('b_lundef')) if not self.info.is_windows() or self.info.is_cygwin(): - self.base_options.append('b_asneeded') + self.base_options.add(OptionKey('b_asneeded')) if not self.info.is_hurd(): - self.base_options.append('b_sanitize') + self.base_options.add(OptionKey('b_sanitize')) # All GCC-like backends can do assembly self.can_compile_suffixes.add('s') @@ -328,7 +330,7 @@ def __init__(self, defines: T.Optional[T.Dict[str, str]]): super().__init__() self.id = 'gcc' self.defines = defines or {} - self.base_options.append('b_colorout') + self.base_options.add(OptionKey('b_colorout')) def get_colorout_args(self, colortype: str) -> T.List[str]: if mesonlib.version_compare(self.version, '>=4.9.0'): diff --git a/mesonbuild/compilers/mixins/intel.py b/mesonbuild/compilers/mixins/intel.py index 442e8c752074..5bca25433448 100644 --- a/mesonbuild/compilers/mixins/intel.py +++ b/mesonbuild/compilers/mixins/intel.py @@ -79,8 +79,9 @@ def __init__(self) -> None: # It does have IPO, which serves much the same purpose as LOT, but # there is an unfortunate rule for using IPO (you can't control the # name of the output file) which break assumptions meson makes - self.base_options = ['b_pch', 'b_lundef', 'b_asneeded', 'b_pgo', - 'b_coverage', 'b_ndebug', 'b_staticpic', 'b_pie'] + self.base_options = {mesonlib.OptionKey(o) for o in [ + 'b_pch', 'b_lundef', 'b_asneeded', 'b_pgo', 'b_coverage', + 'b_ndebug', 'b_staticpic', 'b_pie']} self.id = 'intel' self.lang_header = 'none' diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index 2445eecb7fa5..3fe338215cbe 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -25,7 +25,7 @@ from ... import mesonlib if T.TYPE_CHECKING: - from ...coredata import OptionDictType + from ...coredata import KeyedOptionDictType from ...environment import Environment from ...compilers.compilers import Compiler else: @@ -66,7 +66,7 @@ def get_linker_always_args(self) -> T.List[str]: def get_linker_lib_prefix(self) -> str: return '' - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def has_multi_link_args(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: @@ -78,7 +78,7 @@ def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: def get_std_shared_lib_link_args(self) -> T.List[str]: return [] - def get_std_shared_module_args(self, options: 'OptionDictType') -> T.List[str]: + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return self.get_std_shared_lib_link_args() def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/pgi.py b/mesonbuild/compilers/mixins/pgi.py index 61dee8d5af0e..8461574cb8f9 100644 --- a/mesonbuild/compilers/mixins/pgi.py +++ b/mesonbuild/compilers/mixins/pgi.py @@ -19,6 +19,7 @@ from pathlib import Path from ..compilers import clike_debug_args, clike_optimization_args +from ...mesonlib import OptionKey if T.TYPE_CHECKING: from ...environment import Environment @@ -43,7 +44,7 @@ class PGICompiler(Compiler): def __init__(self) -> None: - self.base_options = ['b_pch'] + self.base_options = {OptionKey('b_pch')} self.id = 'pgi' default_warn_args = ['-Minform=inform'] diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index c38d59a9004d..92f4fcd19d2d 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -129,7 +129,7 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): INVOKES_LINKER = False def __init__(self, target: str): - self.base_options = ['b_pch', 'b_ndebug', 'b_vscrt'] # FIXME add lto, pgo and the like + self.base_options = {mesonlib.OptionKey(o) for o in ['b_pch', 'b_ndebug', 'b_vscrt']} # FIXME add lto, pgo and the like self.target = target self.is_64 = ('x64' in target) or ('x86_64' in target) # do some canonicalization of target machine diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index 1b280eb830bf..e47bf2fea5f7 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -51,7 +51,7 @@ def sanity_check(self, work_dir: str, environment: 'Environment') -> None: # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjc.m') binary_name = os.path.join(work_dir, 'sanitycheckobjc') - extra_flags = [] + extra_flags: T.List[str] = [] extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 16ba77e0d924..c0f93d70d318 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -50,7 +50,7 @@ def sanity_check(self, work_dir: str, environment: 'Environment') -> None: # TODO try to use sanity_check_impl instead of duplicated code source_name = os.path.join(work_dir, 'sanitycheckobjcpp.mm') binary_name = os.path.join(work_dir, 'sanitycheckobjcpp') - extra_flags = [] + extra_flags: T.List[str] = [] extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 312b3b6451ff..fd588192885b 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -17,11 +17,14 @@ import typing as T from .. import coredata -from ..mesonlib import EnvironmentException, MachineChoice, MesonException, Popen_safe +from ..mesonlib import ( + EnvironmentException, MachineChoice, MesonException, Popen_safe, + OptionKey, +) from .compilers import Compiler, rust_buildtype_args, clike_debug_args if T.TYPE_CHECKING: - from ..coredata import OptionDictType + from ..coredata import KeyedOptionDictType from ..dependencies import ExternalProgram from ..envconfig import MachineInfo from ..environment import Environment # noqa: F401 @@ -52,9 +55,9 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic linker=linker) self.exe_wrapper = exe_wrapper self.id = 'rustc' - self.base_options.append('b_colorout') + self.base_options.add(OptionKey('b_colorout')) if 'link' in self.linker.id: - self.base_options.append('b_vscrt') + self.base_options.add(OptionKey('b_vscrt')) def needs_static_linker(self) -> bool: return False @@ -133,18 +136,20 @@ def use_linker_args(cls, linker: str) -> T.List[str]: # C compiler for dynamic linking, as such we invoke the C compiler's # use_linker_args method instead. - def get_options(self) -> 'OptionDictType': + def get_options(self) -> 'KeyedOptionDictType': + key = OptionKey('std', machine=self.for_machine, lang=self.language) return { - 'std': coredata.UserComboOption( + key: coredata.UserComboOption( 'Rust Eddition to use', ['none', '2015', '2018'], 'none', ), } - def get_option_compile_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_compile_args(self, options: 'KeyedOptionDictType') -> T.List[str]: args = [] - std = options['std'] + key = OptionKey('std', machine=self.for_machine, lang=self.language) + std = options[key] if std.value != 'none': args.append('--edition=' + std.value) return args diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 8682e54a9219..7b1859199478 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -101,7 +101,7 @@ def sanity_check(self, work_dir: str, environment: 'Environment') -> None: src = 'swifttest.swift' source_name = os.path.join(work_dir, src) output_name = os.path.join(work_dir, 'swifttest') - extra_flags = [] + extra_flags: T.List[str] = [] extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index af800c247e32..80e91f6933f3 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -16,7 +16,7 @@ import typing as T from .. import mlog -from ..mesonlib import EnvironmentException, MachineChoice, version_compare +from ..mesonlib import EnvironmentException, MachineChoice, version_compare, OptionKey from .compilers import Compiler, LibType @@ -33,7 +33,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic super().__init__(exelist, version, for_machine, info, is_cross=is_cross) self.version = version self.id = 'valac' - self.base_options = ['b_colorout'] + self.base_options = {OptionKey('b_colorout')} def needs_static_linker(self) -> bool: return False # Because compiles into C. @@ -92,7 +92,7 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], def sanity_check(self, work_dir: str, environment: 'Environment') -> None: code = 'class MesonSanityCheck : Object { }' - extra_flags = [] + extra_flags: T.List[str] = [] extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) if self.is_cross: extra_flags += self.get_compile_only_args() @@ -116,7 +116,7 @@ def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str] # no extra dirs are specified. if not extra_dirs: code = 'class MesonFindLibrary : Object { }' - args = [] + args: T.List[str] = [] args += env.coredata.get_external_args(self.for_machine, self.language) vapi_args = ['--pkg', libname] args += vapi_args diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 310174f48634..cda0566cea89 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -1,4 +1,4 @@ -# Copyrigh 2012-2020 The Meson development team +# Copyright 2012-2020 The Meson development team # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,10 +17,11 @@ import sys from itertools import chain from pathlib import PurePath -from collections import OrderedDict, defaultdict +from collections import OrderedDict from .mesonlib import ( MesonException, EnvironmentException, MachineChoice, PerMachine, - default_libdir, default_libexecdir, default_prefix, split_args + default_libdir, default_libexecdir, default_prefix, split_args, + OptionKey, OptionType, ) from .wrap import WrapMode import ast @@ -37,6 +38,7 @@ from .mesonlib import OptionOverrideProxy OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], OptionOverrideProxy] + KeyedOptionDictType = T.Union[T.Dict['OptionKey', 'UserOption[T.Any]'], OptionOverrideProxy] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, str, T.Tuple[str, ...], str] version = '0.56.99' @@ -47,6 +49,7 @@ # Can't bind this near the class method it seems, sadly. _T = T.TypeVar('_T') + class MesonVersionMismatchException(MesonException): '''Build directory generated with Meson version is incompatible with current version''' def __init__(self, old_version: str, current_version: str) -> None: @@ -298,16 +301,17 @@ class DependencyCache: successfully lookup by providing a simple get/put interface. """ - def __init__(self, builtins_per_machine: PerMachine[T.Dict[str, UserOption[T.Any]]], for_machine: MachineChoice): + def __init__(self, builtins: 'KeyedOptionDictType', for_machine: MachineChoice): self.__cache = OrderedDict() # type: T.MutableMapping[CacheKeyType, DependencySubCache] - self.__builtins_per_machine = builtins_per_machine - self.__for_machine = for_machine + self.__builtins = builtins + self.__pkg_conf_key = OptionKey('pkg_config_path', machine=for_machine) + self.__cmake_key = OptionKey('cmake_prefix_path', machine=for_machine) def __calculate_subkey(self, type_: DependencyCacheType) -> T.Tuple[T.Any, ...]: if type_ is DependencyCacheType.PKG_CONFIG: - return tuple(self.__builtins_per_machine[self.__for_machine]['pkg_config_path'].value) + return tuple(self.__builtins[self.__pkg_conf_key].value) elif type_ is DependencyCacheType.CMAKE: - return tuple(self.__builtins_per_machine[self.__for_machine]['cmake_prefix_path'].value) + return tuple(self.__builtins[self.__cmake_key].value) assert type_ is DependencyCacheType.OTHER, 'Someone forgot to update subkey calculations for a new type' return tuple() @@ -381,20 +385,12 @@ def __init__(self, options: argparse.Namespace, scratch_dir: str, meson_command: self.meson_command = meson_command self.target_guids = {} self.version = version - self.builtins = {} # type: OptionDictType - self.builtins_per_machine = PerMachine({}, {}) - self.backend_options = {} # type: OptionDictType - self.user_options = {} # type: OptionDictType - self.compiler_options = PerMachine( - defaultdict(dict), - defaultdict(dict), - ) # type: PerMachine[T.defaultdict[str, OptionDictType]] - self.base_options = {} # type: OptionDictType + self.options: 'KeyedOptionDictType' = {} self.cross_files = self.__load_config_files(options, scratch_dir, 'cross') self.compilers = PerMachine(OrderedDict(), OrderedDict()) # type: PerMachine[T.Dict[str, Compiler]] - build_cache = DependencyCache(self.builtins_per_machine, MachineChoice.BUILD) - host_cache = DependencyCache(self.builtins_per_machine, MachineChoice.BUILD) + build_cache = DependencyCache(self.options, MachineChoice.BUILD) + host_cache = DependencyCache(self.options, MachineChoice.BUILD) self.deps = PerMachine(build_cache, host_cache) # type: PerMachine[DependencyCache] self.compiler_check_cache = OrderedDict() # type: T.Dict[CompilerCheckCacheKey, compiler.CompileResult] @@ -466,7 +462,7 @@ def builtin_options_libdir_cross_fixup(self): # 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['libdir'].default = 'lib' + BUILTIN_OPTIONS[OptionKey('libdir')].default = 'lib' def sanitize_prefix(self, prefix): prefix = os.path.expanduser(prefix) @@ -486,7 +482,7 @@ def sanitize_prefix(self, prefix): prefix = prefix[:-1] return prefix - def sanitize_dir_option_value(self, prefix: str, option: str, value: T.Any) -> T.Any: + def sanitize_dir_option_value(self, prefix: str, option: OptionKey, value: T.Any) -> T.Any: ''' If the option is an installation directory option and the value is an absolute path, check that it resides within prefix and return the value @@ -501,13 +497,13 @@ def sanitize_dir_option_value(self, prefix: str, option: str, value: T.Any) -> T value = PurePath(value) except TypeError: return value - if option.endswith('dir') and value.is_absolute() and \ - option not in builtin_dir_noprefix_options: + if option.name.endswith('dir') and value.is_absolute() and \ + option not in BULITIN_DIR_NOPREFIX_OPTIONS: # Value must be a subdir of the prefix # commonpath will always return a path in the native format, so we # must use pathlib.PurePath to do the same conversion before # comparing. - msg = ('The value of the {!r} option is \'{!s}\' which must be a ' + msg = ('The value of the \'{!s}\' option is \'{!s}\' which must be a ' 'subdir of the prefix {!r}.\nNote that if you pass a ' 'relative path, it is assumed to be a subdir of prefix.') # os.path.commonpath doesn't understand case-insensitive filesystems, @@ -520,81 +516,76 @@ def sanitize_dir_option_value(self, prefix: str, option: str, value: T.Any) -> T raise MesonException(msg.format(option, value, prefix)) return value.as_posix() - def init_builtins(self, subproject: str): + def init_builtins(self, subproject: str) -> None: # Create builtin options with default values for key, opt in BUILTIN_OPTIONS.items(): - self.add_builtin_option(self.builtins, key, opt, subproject) + 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(): - self.add_builtin_option(self.builtins_per_machine[for_machine], key, opt, subproject) + self.add_builtin_option(self.options, key.evolve(subproject=subproject, machine=for_machine), opt) - def add_builtin_option(self, opts_map, key, opt, subproject): - if subproject: + @staticmethod + def add_builtin_option(opts_map: 'KeyedOptionDictType', key: OptionKey, + opt: 'BuiltinOption') -> None: + if key.subproject: if opt.yielding: # This option is global and not per-subproject return - optname = subproject + ':' + key - value = opts_map[key].value + value = opts_map[key.as_root()].value else: - optname = key value = None - opts_map[optname] = opt.init_option(key, value, default_prefix()) + opts_map[key] = opt.init_option(key, value, default_prefix()) def init_backend_options(self, backend_name: str) -> None: if backend_name == 'ninja': - self.backend_options['backend_max_links'] = \ - UserIntegerOption( - 'Maximum number of linker processes to run or 0 for no ' - 'limit', - (0, None, 0)) + self.options[OptionKey('backend_max_links')] = UserIntegerOption( + 'Maximum number of linker processes to run or 0 for no ' + 'limit', + (0, None, 0)) elif backend_name.startswith('vs'): - self.backend_options['backend_startup_project'] = \ - UserStringOption( - 'Default project to execute in Visual Studio', - '') + self.options[OptionKey('backend_startup_project')] = UserStringOption( + 'Default project to execute in Visual Studio', + '') - def get_builtin_option(self, optname: str, subproject: str = '') -> T.Union[str, int, bool]: - raw_optname = optname - if subproject: - optname = subproject + ':' + optname - for opts in self._get_all_builtin_options(): - v = opts.get(optname) - if v is None or v.yielding: - v = opts.get(raw_optname) - if v is None: - continue - if raw_optname == 'wrap_mode': - return WrapMode.from_string(v.value) - return v.value - raise RuntimeError('Tried to get unknown builtin option %s.' % raw_optname) - - def _try_set_builtin_option(self, optname, value): - for opts in self._get_all_builtin_options(): - opt = opts.get(optname) - if opt is None: - continue - if optname == 'prefix': + def get_option(self, key: OptionKey) -> T.Union[str, int, bool, WrapMode]: + try: + v = self.options[key].value + if key.name == 'wrap_mode': + return WrapMode[v] + return v + except KeyError: + pass + + try: + v = self.options[key.as_root()] + if v.yielding: + if key.name == 'wrap_mode': + return WrapMode[v.value] + return v.value + except KeyError: + pass + + raise MesonException(f'Tried to get unknown builtin option {str(key)}') + + def set_option(self, key: OptionKey, value) -> None: + if key.is_builtin(): + if key.name == 'prefix': value = self.sanitize_prefix(value) else: - prefix = self.builtins['prefix'].value - value = self.sanitize_dir_option_value(prefix, optname, value) - break - else: - return False - opt.set_value(value) - # Make sure that buildtype matches other settings. - if optname == 'buildtype': - self.set_others_from_buildtype(value) - else: - self.set_buildtype_from_others() - return True + prefix = self.options[OptionKey('prefix')].value + value = self.sanitize_dir_option_value(prefix, key, value) + + try: + self.options[key].set_value(value) + except KeyError: + raise MesonException(f'Tried to set unknown builtin option {str(key)}') - def set_builtin_option(self, optname, value): - res = self._try_set_builtin_option(optname, value) - if not res: - raise RuntimeError('Tried to set unknown builtin option %s.' % optname) + if key.name == 'buildtype': + self._set_others_from_buildtype(value) + elif key.name in {'debug', 'optimization'}: + self._set_buildtype_from_others() - def set_others_from_buildtype(self, value): + def _set_others_from_buildtype(self, value: str) -> None: if value == 'plain': opt = '0' debug = False @@ -613,12 +604,12 @@ def set_others_from_buildtype(self, value): else: assert(value == 'custom') return - self.builtins['optimization'].set_value(opt) - self.builtins['debug'].set_value(debug) + self.options[OptionKey('optimization')].set_value(opt) + self.options[OptionKey('debug')].set_value(debug) - def set_buildtype_from_others(self): - opt = self.builtins['optimization'].value - debug = self.builtins['debug'].value + def _set_buildtype_from_others(self) -> None: + opt = self.options[OptionKey('optimization')].value + debug = self.options[OptionKey('debug')].value if opt == '0' and not debug: mode = 'plain' elif opt == '0' and debug: @@ -631,214 +622,144 @@ def set_buildtype_from_others(self): mode = 'minsize' else: mode = 'custom' - self.builtins['buildtype'].set_value(mode) - - @classmethod - def get_prefixed_options_per_machine( - cls, - options_per_machine # : PerMachine[T.Dict[str, _V]]] - ) -> T.Iterable[T.Tuple[str, _V]]: - return cls._flatten_pair_iterator( - (for_machine.get_prefix(), options_per_machine[for_machine]) - for for_machine in iter(MachineChoice) - ) - - @classmethod - def flatten_lang_iterator( - cls, - outer # : T.Iterable[T.Tuple[str, T.Dict[str, _V]]] - ) -> T.Iterable[T.Tuple[str, _V]]: - return cls._flatten_pair_iterator((lang + '_', opts) for lang, opts in outer) + self.options[OptionKey('buildtype')].set_value(mode) @staticmethod - def _flatten_pair_iterator( - outer # : T.Iterable[T.Tuple[str, T.Dict[str, _V]]] - ) -> T.Iterable[T.Tuple[str, _V]]: - for k0, v0 in outer: - for k1, v1 in v0.items(): - yield (k0 + k1, v1) - - @classmethod - def insert_build_prefix(cls, k): - idx = k.find(':') - if idx < 0: - return 'build.' + k - return k[:idx + 1] + 'build.' + k[idx + 1:] - - @classmethod - def is_per_machine_option(cls, optname): - if optname in BUILTIN_OPTIONS_PER_MACHINE: + def is_per_machine_option(optname: OptionKey) -> bool: + if optname.name in BUILTIN_OPTIONS_PER_MACHINE: return True - from .compilers import compilers - for lang_prefix in [lang + '_' for lang in compilers.all_languages]: - if optname.startswith(lang_prefix): - return True - return False - - def _get_all_nonbuiltin_options(self) -> T.Iterable[T.Dict[str, UserOption]]: - yield self.backend_options - yield self.user_options - yield dict(self.flatten_lang_iterator(self.get_prefixed_options_per_machine(self.compiler_options))) - yield self.base_options - - def _get_all_builtin_options(self) -> T.Iterable[T.Dict[str, UserOption]]: - yield dict(self.get_prefixed_options_per_machine(self.builtins_per_machine)) - yield self.builtins - - def get_all_options(self) -> T.Iterable[T.Dict[str, UserOption]]: - yield from self._get_all_nonbuiltin_options() - yield from self._get_all_builtin_options() - - def validate_option_value(self, option_name, override_value): - for opts in self.get_all_options(): - opt = opts.get(option_name) - if opt is not None: - try: - return opt.validate_value(override_value) - except MesonException as e: - raise type(e)(('Validation failed for option %s: ' % option_name) + str(e)) \ - .with_traceback(sys.exc_info()[2]) - raise MesonException('Tried to validate unknown option %s.' % option_name) + return optname.lang is not None + + def validate_option_value(self, option_name: OptionKey, override_value): + try: + opt = self.options[option_name] + except KeyError: + raise MesonException(f'Tried to validate unknown option {str(option_name)}') + try: + return opt.validate_value(override_value) + except MesonException as e: + raise type(e)(('Validation failed for option %s: ' % option_name) + str(e)) \ + .with_traceback(sys.exc_info()[2]) - def get_external_args(self, for_machine: MachineChoice, lang): - return self.compiler_options[for_machine][lang]['args'].value + def get_external_args(self, for_machine: MachineChoice, lang: str) -> T.Union[str, T.List[str]]: + return self.options[OptionKey('args', machine=for_machine, lang=lang)].value - def get_external_link_args(self, for_machine: MachineChoice, lang): - return self.compiler_options[for_machine][lang]['link_args'].value + def get_external_link_args(self, for_machine: MachineChoice, lang: str) -> T.Union[str, T.List[str]]: + return self.options[OptionKey('link_args', machine=for_machine, lang=lang)].value - def merge_user_options(self, options: T.Dict[str, UserOption[T.Any]]) -> None: - for (name, value) in options.items(): - if name not in self.user_options: - self.user_options[name] = value + def update_project_options(self, options: 'KeyedOptionDictType') -> None: + for key, value in options.items(): + if not key.is_project(): + continue + if key not in self.options: + self.options[key] = value continue - oldval = self.user_options[name] + oldval = self.options[key] if type(oldval) != type(value): - self.user_options[name] = value + self.options[key] = value elif oldval.choices != value.choices: # If the choices have changed, use the new value, but attempt # to keep the old options. If they are not valid keep the new # defaults but warn. - self.user_options[name] = value + self.options[key] = value try: value.set_value(oldval.value) except MesonException as e: - mlog.warning('Old value(s) of {} are no longer valid, resetting to default ({}).'.format(name, value.value)) + mlog.warning('Old value(s) of {} are no longer valid, resetting to default ({}).'.format(key, value.value)) def is_cross_build(self, when_building_for: MachineChoice = MachineChoice.HOST) -> bool: if when_building_for == MachineChoice.BUILD: return False return len(self.cross_files) > 0 - def strip_build_option_names(self, options): - res = OrderedDict() - for k, v in options.items(): - if k.startswith('build.'): - k = k.split('.', 1)[1] - res.setdefault(k, v) - else: - res[k] = v - return res - - def copy_build_options_from_regular_ones(self): - assert(not self.is_cross_build()) - for k, o in self.builtins_per_machine.host.items(): - self.builtins_per_machine.build[k].set_value(o.value) - for lang, host_opts in self.compiler_options.host.items(): - build_opts = self.compiler_options.build[lang] - for k, o in host_opts.items(): - if k in build_opts: - build_opts[k].set_value(o.value) - - def set_options(self, options: T.Dict[str, T.Any], subproject: str = '', warn_unknown: bool = True) -> None: + def copy_build_options_from_regular_ones(self) -> None: + assert not self.is_cross_build() + for k in BUILTIN_OPTIONS_PER_MACHINE: + o = self.options[k] + self.options[k.as_build()].set_value(o.value) + for bk, bv in self.options.items(): + if bk.machine is MachineChoice.BUILD: + hk = bk.as_host() + try: + hv = self.options[hk] + bv.set_value(hv.value) + except KeyError: + continue + + def set_options(self, options: T.Dict[OptionKey, T.Any], subproject: str = '', warn_unknown: bool = True) -> None: if not self.is_cross_build(): - options = self.strip_build_option_names(options) + options = {k: v for k, v in options.items() if k.machine is not MachineChoice.BUILD} # Set prefix first because it's needed to sanitize other options - if 'prefix' in options: - prefix = self.sanitize_prefix(options['prefix']) - self.builtins['prefix'].set_value(prefix) - for key in builtin_dir_noprefix_options: + pfk = OptionKey('prefix') + if pfk in options: + prefix = self.sanitize_prefix(options[pfk]) + self.options[OptionKey('prefix')].set_value(prefix) + for key in BULITIN_DIR_NOPREFIX_OPTIONS: if key not in options: - self.builtins[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) + self.options[key].set_value(BUILTIN_OPTIONS[key].prefixed_default(key, prefix)) - unknown_options = [] + unknown_options: T.List[OptionKey] = [] for k, v in options.items(): - if k == 'prefix': - continue - if self._try_set_builtin_option(k, v): + if k == pfk: continue - for opts in self._get_all_nonbuiltin_options(): - tgt = opts.get(k) - if tgt is None: - continue - tgt.set_value(v) - break - else: + elif k not in self.options: unknown_options.append(k) + else: + self.set_option(k, v) if unknown_options and warn_unknown: - unknown_options = ', '.join(sorted(unknown_options)) + unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options)) sub = 'In subproject {}: '.format(subproject) if subproject else '' - mlog.warning('{}Unknown options: "{}"'.format(sub, unknown_options)) + mlog.warning('{}Unknown options: "{}"'.format(sub, unknown_options_str)) mlog.log('The value of new options can be set with:') mlog.log(mlog.bold('meson setup --reconfigure -Dnew_option=new_value ...')) if not self.is_cross_build(): self.copy_build_options_from_regular_ones() - def set_default_options(self, default_options: 'T.OrderedDict[str, str]', subproject: str, env: 'Environment') -> None: - # Preserve order: if env.raw_options has 'buildtype' it must come after + def set_default_options(self, default_options: T.MutableMapping[OptionKey, str], subproject: str, env: 'Environment') -> None: + # Preserve order: if env.options has 'buildtype' it must come after # 'optimization' if it is in default_options. - raw_options = OrderedDict() - for k, v in default_options.items(): - if subproject: - k = subproject + ':' + k - raw_options[k] = v - raw_options.update(env.raw_options) - env.raw_options = raw_options - - # Create a subset of raw_options, keeping only project and builtin + options: T.MutableMapping[OptionKey, T.Any] + if not subproject: + options = OrderedDict(default_options) + options.update(env.options) + env.options = options + + # Create a subset of options, keeping only project and builtin # options for this subproject. # Language and backend specific options will be set later when adding # languages and setting the backend (builtin options must be set first # to know which backend we'll use). options = OrderedDict() - from . import optinterpreter - for k, v in env.raw_options.items(): - raw_optname = k - if subproject: - # Subproject: skip options for other subprojects - if not k.startswith(subproject + ':'): - continue - raw_optname = k.split(':')[1] - elif ':' in k: - # Main prject: skip options for subprojects + for k, v in chain(default_options.items(), env.options.items()): + # If this is a subproject, don't use other subproject options + if k.subproject and k.subproject != subproject: + continue + # If the option is a builtin and is yielding then it's not allowed per subproject. + if subproject and k.is_builtin() and self.options[k.as_root()].yielding: continue # Skip base, compiler, and backend options, they are handled when # adding languages and setting backend. - if (k not in self.builtins and - k not in self.get_prefixed_options_per_machine(self.builtins_per_machine) and - optinterpreter.is_invalid_name(raw_optname, log=False)): + if k.type in {OptionType.COMPILER, OptionType.BACKEND, OptionType.BASE}: continue options[k] = v self.set_options(options, subproject=subproject) - def add_compiler_options(self, options, lang, for_machine, env): - # prefixed compiler options affect just this machine - opt_prefix = for_machine.get_prefix() + def add_compiler_options(self, options: 'KeyedOptionDictType', lang: str, for_machine: MachineChoice, + env: 'Environment') -> None: for k, o in options.items(): - optname = opt_prefix + lang + '_' + k - value = env.raw_options.get(optname) + value = env.options.get(k) if value is not None: o.set_value(value) - self.compiler_options[for_machine][lang].setdefault(k, o) + self.options.setdefault(k, o) def add_lang_args(self, lang: str, comp: T.Type['Compiler'], for_machine: MachineChoice, env: 'Environment') -> None: """Add global language arguments that are needed before compiler/linker detection.""" from .compilers import compilers - options = compilers.get_global_options(lang, comp, for_machine, - env.is_cross_build()) + options = compilers.get_global_options(lang, comp, for_machine, env.is_cross_build()) self.add_compiler_options(options, lang, for_machine, env) def process_new_compiler(self, lang: str, comp: 'Compiler', env: 'Environment') -> None: @@ -847,19 +768,19 @@ def process_new_compiler(self, lang: str, comp: 'Compiler', env: 'Environment') self.compilers[comp.for_machine][lang] = comp self.add_compiler_options(comp.get_options(), lang, comp.for_machine, env) - enabled_opts = [] - for optname in comp.base_options: - if optname in self.base_options: + enabled_opts: T.List[OptionKey] = [] + for key in comp.base_options: + if key in self.options: continue - oobj = compilers.base_options[optname] - if optname in env.raw_options: - oobj.set_value(env.raw_options[optname]) - enabled_opts.append(optname) - self.base_options[optname] = oobj + oobj = compilers.base_options[key] + if key in env.options: + oobj.set_value(env.options[key]) + enabled_opts.append(key) + self.options[key] = oobj self.emit_base_options_warnings(enabled_opts) - def emit_base_options_warnings(self, enabled_opts: list): - if 'b_bitcode' in enabled_opts: + def emit_base_options_warnings(self, enabled_opts: T.List[OptionKey]) -> None: + if OptionKey('b_bitcode') in enabled_opts: mlog.warning('Base option \'b_bitcode\' is enabled, which is incompatible with many linker options. Incompatible options such as \'b_asneeded\' have been disabled.', fatal=False) mlog.warning('Please see https://mesonbuild.com/Builtin-options.html#Notes_about_Apple_Bitcode_support for more details.', fatal=False) @@ -949,7 +870,7 @@ def read_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: # Do a copy because config is not really a dict. options.cmd_line_options # overrides values from the file. - d = dict(config['options']) + d = {OptionKey.from_string(k): v for k, v in config['options'].items()} d.update(options.cmd_line_options) options.cmd_line_options = d @@ -961,9 +882,6 @@ def read_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: # literal_eval to get it into the list of strings. options.native_file = ast.literal_eval(properties.get('native_file', '[]')) -def cmd_line_options_to_string(options: argparse.Namespace) -> T.Dict[str, str]: - return {k: str(v) for k, v in options.cmd_line_options.items()} - def write_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: filename = get_cmd_line_file(build_dir) config = CmdLineFileParser() @@ -974,7 +892,7 @@ def write_cmd_line_file(build_dir: str, options: argparse.Namespace) -> None: if options.native_file: properties['native_file'] = [os.path.abspath(f) for f in options.native_file] - config['options'] = cmd_line_options_to_string(options) + config['options'] = {str(k): str(v) for k, v in options.cmd_line_options.items()} config['properties'] = properties with open(filename, 'w') as f: config.write(f) @@ -983,14 +901,14 @@ def update_cmd_line_file(build_dir: str, options: argparse.Namespace): filename = get_cmd_line_file(build_dir) config = CmdLineFileParser() config.read(filename) - config['options'].update(cmd_line_options_to_string(options)) + config['options'].update({str(k): str(v) for k, v in options.cmd_line_options.items()}) with open(filename, 'w') as f: config.write(f) def get_cmd_line_options(build_dir: str, options: argparse.Namespace) -> str: copy = argparse.Namespace(**vars(options)) read_cmd_line_file(build_dir, copy) - cmdline = ['-D{}={}'.format(k, v) for k, v in copy.cmd_line_options.items()] + cmdline = ['-D{}={}'.format(str(k), v) for k, v in copy.cmd_line_options.items()] if options.cross_file: cmdline += ['--cross-file {}'.format(f) for f in options.cross_file] if options.native_file: @@ -1038,39 +956,43 @@ def save(obj: CoreData, build_dir: str) -> str: def register_builtin_arguments(parser: argparse.ArgumentParser) -> None: for n, b in BUILTIN_OPTIONS.items(): - b.add_to_argparse(n, parser, '', '') + b.add_to_argparse(str(n), parser, '') for n, b in BUILTIN_OPTIONS_PER_MACHINE.items(): - b.add_to_argparse(n, parser, '', ' (just for host machine)') - b.add_to_argparse(n, parser, 'build.', ' (just for build machine)') + 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", help='Set the value of an option, can be used several times to set multiple options.') -def create_options_dict(options: T.List[str]) -> T.Dict[str, str]: - result = OrderedDict() +def create_options_dict(options: T.List[str], subproject: str = '') -> T.Dict[OptionKey, str]: + result: T.OrderedDict[OptionKey, str] = OrderedDict() for o in options: try: (key, value) = o.split('=', 1) except ValueError: raise MesonException('Option {!r} must have a value separated by equals sign.'.format(o)) - result[key] = value + k = OptionKey.from_string(key) + if subproject: + k = k.evolve(subproject=subproject) + result[k] = value return result def parse_cmd_line_options(args: argparse.Namespace) -> None: args.cmd_line_options = create_options_dict(args.projectoptions) # Merge builtin options set with --option into the dict. - for name in chain( + for key in chain( BUILTIN_OPTIONS.keys(), - ('build.' + k for k in BUILTIN_OPTIONS_PER_MACHINE.keys()), + (k.as_build() for k in BUILTIN_OPTIONS_PER_MACHINE.keys()), BUILTIN_OPTIONS_PER_MACHINE.keys(), ): + name = str(key) value = getattr(args, name, None) if value is not None: - if name in args.cmd_line_options: + if key in args.cmd_line_options: cmdline_name = BuiltinOption.argparse_name_to_arg(name) raise MesonException( 'Got argument {0} as both -D{0} and {1}. Pick one.'.format(name, cmdline_name)) - args.cmd_line_options[name] = value + args.cmd_line_options[key] = value delattr(args, name) @@ -1091,7 +1013,7 @@ def __init__(self, opt_type: T.Type[_U], description: str, default: T.Any, yield self.choices = choices self.yielding = yielding - def init_option(self, name: str, value: T.Optional[T.Any], prefix: str) -> _U: + 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) @@ -1122,16 +1044,16 @@ def argparse_name_to_arg(name: str) -> str: else: return '--' + name.replace('_', '-') - def prefixed_default(self, name: str, prefix: str = '') -> T.Any: + 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] + return BULITIN_DIR_NOPREFIX_OPTIONS[name][prefix] except KeyError: pass return self.default - def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, prefix: str, help_suffix: str) -> None: + def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, help_suffix: str) -> None: kwargs = OrderedDict() c = self._argparse_choices() @@ -1144,64 +1066,65 @@ def add_to_argparse(self, name: str, parser: argparse.ArgumentParser, prefix: st if c and not b: kwargs['choices'] = c kwargs['default'] = argparse.SUPPRESS - kwargs['dest'] = prefix + name + kwargs['dest'] = name - cmdline_name = self.argparse_name_to_arg(prefix + 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 -BUILTIN_DIR_OPTIONS = OrderedDict([ - ('prefix', BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), - ('bindir', BuiltinOption(UserStringOption, 'Executable directory', 'bin')), - ('datadir', BuiltinOption(UserStringOption, 'Data file directory', 'share')), - ('includedir', BuiltinOption(UserStringOption, 'Header file directory', 'include')), - ('infodir', BuiltinOption(UserStringOption, 'Info page directory', 'share/info')), - ('libdir', BuiltinOption(UserStringOption, 'Library directory', default_libdir())), - ('libexecdir', BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), - ('localedir', BuiltinOption(UserStringOption, 'Locale data directory', 'share/locale')), - ('localstatedir', BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), - ('mandir', BuiltinOption(UserStringOption, 'Manual page directory', 'share/man')), - ('sbindir', BuiltinOption(UserStringOption, 'System executable directory', 'sbin')), - ('sharedstatedir', BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), - ('sysconfdir', BuiltinOption(UserStringOption, 'Sysconf data directory', 'etc')), -]) # type: OptionDictType - -BUILTIN_CORE_OPTIONS = OrderedDict([ - ('auto_features', BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), - ('backend', BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist)), - ('buildtype', BuiltinOption(UserComboOption, 'Build type to use', 'debug', - choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])), - ('debug', BuiltinOption(UserBooleanOption, 'Debug', True)), - ('default_library', BuiltinOption(UserComboOption, 'Default library type', 'shared', choices=['shared', 'static', 'both'], - yielding=False)), - ('errorlogs', BuiltinOption(UserBooleanOption, "Whether to print the logs from failing tests", True)), - ('install_umask', BuiltinOption(UserUmaskOption, 'Default umask to apply on permissions of installed files', '022')), - ('layout', BuiltinOption(UserComboOption, 'Build directory layout', 'mirror', choices=['mirror', 'flat'])), - ('optimization', BuiltinOption(UserComboOption, 'Optimization level', '0', choices=['0', 'g', '1', '2', '3', 's'])), - ('stdsplit', BuiltinOption(UserBooleanOption, 'Split stdout and stderr in test logs', True)), - ('strip', BuiltinOption(UserBooleanOption, 'Strip targets on install', False)), - ('unity', BuiltinOption(UserComboOption, 'Unity build', 'off', choices=['on', 'off', 'subprojects'])), - ('unity_size', BuiltinOption(UserIntegerOption, 'Unity block size', (2, None, 4))), - ('warning_level', BuiltinOption(UserComboOption, 'Compiler warning level to use', '1', choices=['0', '1', '2', '3'], yielding=False)), - ('werror', BuiltinOption(UserBooleanOption, 'Treat warnings as errors', False, yielding=False)), - ('wrap_mode', BuiltinOption(UserComboOption, 'Wrap mode', 'default', choices=['default', 'nofallback', 'nodownload', 'forcefallback'])), - ('force_fallback_for', BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), -]) # type: OptionDictType +# Also update mesonlib._BUILTIN_NAMES. See the comment there for why this is required. +BUILTIN_DIR_OPTIONS: 'KeyedOptionDictType' = OrderedDict([ + (OptionKey('prefix'), BuiltinOption(UserStringOption, 'Installation prefix', default_prefix())), + (OptionKey('bindir'), BuiltinOption(UserStringOption, 'Executable directory', 'bin')), + (OptionKey('datadir'), BuiltinOption(UserStringOption, 'Data file directory', 'share')), + (OptionKey('includedir'), BuiltinOption(UserStringOption, 'Header file directory', 'include')), + (OptionKey('infodir'), BuiltinOption(UserStringOption, 'Info page directory', 'share/info')), + (OptionKey('libdir'), BuiltinOption(UserStringOption, 'Library directory', default_libdir())), + (OptionKey('libexecdir'), BuiltinOption(UserStringOption, 'Library executable directory', default_libexecdir())), + (OptionKey('localedir'), BuiltinOption(UserStringOption, 'Locale data directory', 'share/locale')), + (OptionKey('localstatedir'), BuiltinOption(UserStringOption, 'Localstate data directory', 'var')), + (OptionKey('mandir'), BuiltinOption(UserStringOption, 'Manual page directory', 'share/man')), + (OptionKey('sbindir'), BuiltinOption(UserStringOption, 'System executable directory', 'sbin')), + (OptionKey('sharedstatedir'), BuiltinOption(UserStringOption, 'Architecture-independent data directory', 'com')), + (OptionKey('sysconfdir'), BuiltinOption(UserStringOption, 'Sysconf data directory', 'etc')), +]) + +BUILTIN_CORE_OPTIONS: 'KeyedOptionDictType' = OrderedDict([ + (OptionKey('auto_features'), BuiltinOption(UserFeatureOption, "Override value of all 'auto' features", 'auto')), + (OptionKey('backend'), BuiltinOption(UserComboOption, 'Backend to use', 'ninja', choices=backendlist)), + (OptionKey('buildtype'), BuiltinOption(UserComboOption, 'Build type to use', 'debug', + choices=['plain', 'debug', 'debugoptimized', 'release', 'minsize', 'custom'])), + (OptionKey('debug'), BuiltinOption(UserBooleanOption, 'Debug', 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=['0', 'g', '1', '2', '3', 's'])), + (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'], 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'])), + (OptionKey('force_fallback_for'), BuiltinOption(UserArrayOption, 'Force fallback for those subprojects', [])), +]) BUILTIN_OPTIONS = OrderedDict(chain(BUILTIN_DIR_OPTIONS.items(), BUILTIN_CORE_OPTIONS.items())) -BUILTIN_OPTIONS_PER_MACHINE = OrderedDict([ - ('pkg_config_path', BuiltinOption(UserArrayOption, 'List of additional paths for pkg-config to search', [])), - ('cmake_prefix_path', BuiltinOption(UserArrayOption, 'List of additional prefixes for cmake to search', [])), +BUILTIN_OPTIONS_PER_MACHINE: 'KeyedOptionDictType' = 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 = { - 'sysconfdir': {'/usr': '/etc'}, - 'localstatedir': {'/usr': '/var', '/usr/local': '/var/local'}, - 'sharedstatedir': {'/usr': '/var/lib', '/usr/local': '/var/local/lib'}, +BULITIN_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'}, } FORBIDDEN_TARGET_NAMES = {'clean': None, diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 6f568d37e956..e3592193e86f 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -37,7 +37,7 @@ from ..cmake import CMakeExecutor, CMakeTraceParser, CMakeException, CMakeToolchain, CMakeExecScope, check_cmake_args from ..mesonlib import MachineChoice, MesonException, OrderedSet, PerMachine from ..mesonlib import Popen_safe, version_compare_many, version_compare, listify, stringlistify, extract_as_list, split_args -from ..mesonlib import Version, LibType +from ..mesonlib import Version, LibType, OptionKey from ..mesondata import mesondata if T.TYPE_CHECKING: @@ -656,8 +656,9 @@ def _call_pkgbin_real(self, args, env): return rc, out, err @staticmethod - def setup_env(env, environment, for_machine, extra_path=None): - extra_paths = environment.coredata.builtins_per_machine[for_machine]['pkg_config_path'].value + def setup_env(env: T.MutableMapping[str, str], environment: 'Environment', for_machine: MachineChoice, + extra_path: T.Optional[str] = None) -> None: + extra_paths: T.List[str] = environment.coredata.options[OptionKey('pkg_config_path', machine=for_machine)].value if extra_path: extra_paths.append(extra_path) sysroot = environment.properties[for_machine].get_sys_root() @@ -1484,12 +1485,12 @@ def _detect_dep(self, name: str, package_version: str, modules: T.List[T.Tuple[s cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x] cfg = cfgs[0] - if 'b_vscrt' in self.env.coredata.base_options: - is_debug = self.env.coredata.get_builtin_option('buildtype') == 'debug' - if self.env.coredata.base_options['b_vscrt'].value in ('mdd', 'mtd'): + if OptionKey('b_vscrt') in self.env.coredata.options: + is_debug = self.env.coredata.get_option(OptionKey('buildtype')) == 'debug' + if self.env.coredata.options[OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: is_debug = True else: - is_debug = self.env.coredata.get_builtin_option('debug') + is_debug = self.env.coredata.get_option(OptionKey('debug')) if is_debug: if 'DEBUG' in cfgs: cfg = 'DEBUG' diff --git a/mesonbuild/dependencies/boost.py b/mesonbuild/dependencies/boost.py index 370fa72d13c7..622ee37746a1 100644 --- a/mesonbuild/dependencies/boost.py +++ b/mesonbuild/dependencies/boost.py @@ -341,7 +341,7 @@ def get_link_args(self) -> T.List[str]: class BoostDependency(ExternalDependency): def __init__(self, environment: Environment, kwargs: T.Dict[str, T.Any]) -> None: super().__init__('boost', environment, kwargs, language='cpp') - buildtype = environment.coredata.get_builtin_option('buildtype') + buildtype = environment.coredata.get_option(mesonlib.OptionKey('buildtype')) assert isinstance(buildtype, str) self.debug = buildtype.startswith('debug') self.multithreading = kwargs.get('threading', 'multi') == 'multi' @@ -616,8 +616,8 @@ def filter_libraries(self, libs: T.List[BoostLibraryFile], lib_vers: str) -> T.L # MSVC is very picky with the library tags vscrt = '' try: - crt_val = self.env.coredata.base_options['b_vscrt'].value - buildtype = self.env.coredata.builtins['buildtype'].value + crt_val = self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value + buildtype = self.env.coredata.options[mesonlib.OptionKey('buildtype')].value vscrt = self.clib_compiler.get_crt_compile_args(crt_val, buildtype)[0] except (KeyError, IndexError, AttributeError): pass diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py index baf8e94c0ed3..0b0d96df6b29 100644 --- a/mesonbuild/dependencies/ui.py +++ b/mesonbuild/dependencies/ui.py @@ -382,9 +382,9 @@ def _qmake_detect(self, mods, kwargs): # Use the buildtype by default, but look at the b_vscrt option if the # compiler supports it. - is_debug = self.env.coredata.get_builtin_option('buildtype') == 'debug' - if 'b_vscrt' in self.env.coredata.base_options: - if self.env.coredata.base_options['b_vscrt'].value in ('mdd', 'mtd'): + is_debug = self.env.coredata.get_option(mesonlib.OptionKey('buildtype')) == 'debug' + if mesonlib.OptionKey('b_vscrt') in self.env.coredata.options: + if self.env.coredata.options[mesonlib.OptionKey('b_vscrt')].value in {'mdd', 'mtd'}: is_debug = True modules_lib_suffix = self._get_modules_lib_suffix(is_debug) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 74d8bde1c567..59675ff9acf0 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -23,7 +23,7 @@ from . import mesonlib from .mesonlib import ( MesonException, EnvironmentException, MachineChoice, Popen_safe, - PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg + PerMachineDefaultable, PerThreeMachineDefaultable, split_args, quote_arg, OptionKey ) from . import mlog @@ -130,6 +130,8 @@ ) if T.TYPE_CHECKING: + from configparser import ConfigParser + from .dependencies import ExternalProgram build_filename = 'meson.build' @@ -599,12 +601,11 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti binaries.build = BinaryTable() properties.build = Properties() - # Unparsed options as given by the user in machine files, command line, - # and project()'s default_options. Keys are in the command line format: - # "[:][build.]option_name". + # Options with the key parsed into an OptionKey type. + # # Note that order matters because of 'buildtype', if it is after # 'optimization' and 'debug' keys, it override them. - self.raw_options = collections.OrderedDict() # type: collections.OrderedDict[str, str] + self.options: T.MutableMapping[OptionKey, str] = collections.OrderedDict() ## Read in native file(s) to override build machine configuration @@ -613,7 +614,7 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti binaries.build = BinaryTable(config.get('binaries', {})) properties.build = Properties(config.get('properties', {})) cmakevars.build = CMakeVariables(config.get('cmake', {})) - self.load_machine_file_options(config, properties.build) + self.load_machine_file_options(config, properties.build, MachineChoice.BUILD) ## Read in cross file(s) to override host machine configuration @@ -626,10 +627,19 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti machines.host = MachineInfo.from_literal(config['host_machine']) if 'target_machine' in config: machines.target = MachineInfo.from_literal(config['target_machine']) - # Keep only per machine options from the native file and prefix them - # with "build.". The cross file takes precedence over all other options. - self.keep_per_machine_options() - self.load_machine_file_options(config, properties.host) + # Keep only per machine options from the native file. The cross + # file takes precedence over all other options. + for key, value in list(self.options.items()): + if self.coredata.is_per_machine_option(key): + self.options[key.as_build()] = value + self.load_machine_file_options(config, properties.host, MachineChoice.HOST) + else: + # IF we aren't cross compiling, but we hav ea native file, the + # native file is for the host. This is due to an mismatch between + # meson internals which talk about build an host, and external + # interfaces which talk about native and cross. + self.options = {k.as_host(): v for k, v in self.options.items()} + ## "freeze" now initialized configuration, and "save" to the class. @@ -639,15 +649,17 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti self.cmakevars = cmakevars.default_missing() # Command line options override those from cross/native files - self.raw_options.update(options.cmd_line_options) + self.options.update(options.cmd_line_options) # Take default value from env if not set in cross/native files or command line. self.set_default_options_from_env() # Warn if the user is using two different ways of setting build-type # options that override each other - if 'buildtype' in self.raw_options and \ - ('optimization' in self.raw_options or 'debug' in self.raw_options): + bt = OptionKey('buildtype') + db = OptionKey('debug') + op = OptionKey('optimization') + if bt in self.options and (db in self.options or op in self.options): mlog.warning('Recommend using either -Dbuildtype or -Doptimization + -Ddebug. ' 'Using both is redundant since they override each other. ' 'See: https://mesonbuild.com/Builtin-options.html#build-type-options') @@ -706,11 +718,13 @@ def __init__(self, source_dir: T.Optional[str], build_dir: T.Optional[str], opti self.default_pkgconfig = ['pkg-config'] self.wrap_resolver = None - def load_machine_file_options(self, config, properties): + def load_machine_file_options(self, config: 'ConfigParser', properties: Properties, machine: MachineChoice) -> None: + """Read the contents of a Machine file and put it in the options store.""" paths = config.get('paths') if paths: mlog.deprecation('The [paths] section is deprecated, use the [built-in options] section instead.') - self.raw_options.update(paths) + for k, v in paths.items(): + self.options[OptionKey.from_string(k).evolve(machine=machine)] = v deprecated_properties = set() for lang in compilers.all_languages: deprecated_properties.add(lang + '_args') @@ -718,44 +732,40 @@ def load_machine_file_options(self, config, properties): for k, v in properties.properties.copy().items(): if k in deprecated_properties: mlog.deprecation('{} in the [properties] section of the machine file is deprecated, use the [built-in options] section.'.format(k)) - self.raw_options[k] = v + self.options[OptionKey.from_string(k).evolve(machine=machine)] = v del properties.properties[k] for section, values in config.items(): - prefix = '' if ':' in section: subproject, section = section.split(':') - prefix = subproject + ':' - if section in ['project options', 'built-in options']: - self.raw_options.update({prefix + k: v for k, v in values.items()}) - - def keep_per_machine_options(self): - per_machine_options = {} - for optname, value in self.raw_options.items(): - if self.coredata.is_per_machine_option(optname): - build_optname = self.coredata.insert_build_prefix(optname) - per_machine_options[build_optname] = value - self.raw_options = per_machine_options - - def set_default_options_from_env(self): + else: + subproject = '' + if section == 'built-in options': + for k, v in values.items(): + key = OptionKey.from_string(k).evolve(subproject=subproject, machine=machine) + self.options[key] = v + elif section == 'project options': + for k, v in values.items(): + # Project options are always for the machine machine + key = OptionKey.from_string(k).evolve(subproject=subproject) + self.options[key] = v + + def set_default_options_from_env(self) -> None: for for_machine in MachineChoice: - p_env_pair = get_env_var_pair(for_machine, self.is_cross_build(), 'PKG_CONFIG_PATH') - if p_env_pair is not None: - p_env_var, p_env = p_env_pair - - # PKG_CONFIG_PATH may contain duplicates, which must be - # removed, else a duplicates-in-array-option warning arises. - p_list = list(mesonlib.OrderedSet(p_env.split(':'))) - - key = 'pkg_config_path' - if for_machine == MachineChoice.BUILD: - key = 'build.' + key - - # Take env vars only on first invocation, if the env changes when - # reconfiguring it gets ignored. - # FIXME: We should remember if we took the value from env to warn - # if it changes on future invocations. - if self.first_invocation: - self.raw_options.setdefault(key, p_list) + for evar, keyname in [('PKG_CONFIG_PATH', 'pkg_config_path')]: + p_env_pair = get_env_var_pair(for_machine, self.is_cross_build(), evar) + if p_env_pair is not None: + _, p_env = p_env_pair + + # PKG_CONFIG_PATH may contain duplicates, which must be + # removed, else a duplicates-in-array-option warning arises. + p_list = list(mesonlib.OrderedSet(p_env.split(':'))) + # Take env vars only on first invocation, if the env changes when + # reconfiguring it gets ignored. + # FIXME: We should remember if we took the value from env to warn + # if it changes on future invocations. + if self.first_invocation: + key = OptionKey(keyname, machine=for_machine) + self.options.setdefault(key, p_list) def create_new_coredata(self, options: 'argparse.Namespace') -> None: # WARNING: Don't use any values from coredata in __init__. It gets @@ -929,7 +939,7 @@ def _guess_win_linker(self, compiler: T.List[str], comp_class: Compiler, elif isinstance(comp_class.LINKER_PREFIX, list): check_args = comp_class.LINKER_PREFIX + ['/logo'] + comp_class.LINKER_PREFIX + ['--version'] - check_args += self.coredata.compiler_options[for_machine][comp_class.language]['args'].value + check_args += self.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value override = [] # type: T.List[str] value = self.lookup_binary_entry(for_machine, comp_class.language + '_ld') @@ -995,7 +1005,7 @@ def _guess_nix_linker(self, compiler: T.List[str], comp_class: T.Type[Compiler], """ self.coredata.add_lang_args(comp_class.language, comp_class, for_machine, self) extra_args = extra_args or [] - extra_args += self.coredata.compiler_options[for_machine][comp_class.language]['args'].value + extra_args += self.coredata.options[OptionKey('args', lang=comp_class.language, machine=for_machine)].value if isinstance(comp_class.LINKER_PREFIX, str): check_args = [comp_class.LINKER_PREFIX + '--version'] + extra_args @@ -2000,25 +2010,25 @@ def get_static_lib_dir(self) -> str: return self.get_libdir() def get_prefix(self) -> str: - return self.coredata.get_builtin_option('prefix') + return self.coredata.get_option(OptionKey('prefix')) def get_libdir(self) -> str: - return self.coredata.get_builtin_option('libdir') + return self.coredata.get_option(OptionKey('libdir')) def get_libexecdir(self) -> str: - return self.coredata.get_builtin_option('libexecdir') + return self.coredata.get_option(OptionKey('libexecdir')) def get_bindir(self) -> str: - return self.coredata.get_builtin_option('bindir') + return self.coredata.get_option(OptionKey('bindir')) def get_includedir(self) -> str: - return self.coredata.get_builtin_option('includedir') + return self.coredata.get_option(OptionKey('includedir')) def get_mandir(self) -> str: - return self.coredata.get_builtin_option('mandir') + return self.coredata.get_option(OptionKey('mandir')) def get_datadir(self) -> str: - return self.coredata.get_builtin_option('datadir') + return self.coredata.get_option(OptionKey('datadir')) def get_compiler_system_dirs(self, for_machine: MachineChoice): for comp in self.coredata.compilers[for_machine].values(): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index a4a9fb2fa1a3..c20c20524b0d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -21,7 +21,7 @@ from . import compilers from .wrap import wrap, WrapMode from . import mesonlib -from .mesonlib import FileMode, MachineChoice, Popen_safe, listify, extract_as_list, has_path_sep, unholder +from .mesonlib import FileMode, MachineChoice, OptionKey, Popen_safe, listify, extract_as_list, has_path_sep, unholder from .dependencies import ExternalProgram from .dependencies import InternalDependency, Dependency, NotFoundDependency, DependencyException from .depfile import DepFile @@ -51,6 +51,7 @@ import importlib if T.TYPE_CHECKING: + from .compilers import Compiler from .envconfig import MachineInfo from .environment import Environment from .modules import ExtensionModule @@ -77,11 +78,11 @@ class OverrideProgram(dependencies.ExternalProgram): class FeatureOptionHolder(InterpreterObject, ObjectHolder): - def __init__(self, env, name, option): + def __init__(self, env: 'Environment', name, option): InterpreterObject.__init__(self) ObjectHolder.__init__(self, option) if option.is_auto(): - self.held_object = env.coredata.builtins['auto_features'] + self.held_object = env.coredata.options[OptionKey('auto_features')] self.name = name self.methods.update({'enabled': self.enabled_method, 'disabled': self.disabled_method, @@ -1057,7 +1058,7 @@ def get_variable_method(self, args, kwargs): find_library_permitted_kwargs |= set(['header_' + k for k in header_permitted_kwargs]) class CompilerHolder(InterpreterObject): - def __init__(self, compiler, env, subproject): + def __init__(self, compiler: 'Compiler', env: 'Environment', subproject: str): InterpreterObject.__init__(self) self.compiler = compiler self.environment = env @@ -1141,8 +1142,7 @@ def determine_args(self, kwargs, mode='link'): i.held_object.get_curdir(), idir) args += self.compiler.get_include_args(idir, False) if not nobuiltins: - for_machine = Interpreter.machine_from_native_kwarg(kwargs) - opts = self.environment.coredata.compiler_options[for_machine][self.compiler.language] + opts = self.environment.coredata.options args += self.compiler.get_option_compile_args(opts) if mode == 'link': args += self.compiler.get_option_link_args(opts) @@ -2150,7 +2150,7 @@ def get_compiler_method(self, args, kwargs): @noPosargs @permittedKwargs({}) def is_unity_method(self, args, kwargs): - optval = self.interpreter.environment.coredata.get_builtin_option('unity') + optval = self.interpreter.environment.coredata.get_option(OptionKey('unity')) if optval == 'on' or (optval == 'subprojects' and self.interpreter.is_subproject()): return True return False @@ -2473,12 +2473,11 @@ def _redetect_machines(self): def get_non_matching_default_options(self) -> T.Iterator[T.Tuple[str, str, coredata.UserOption]]: env = self.environment for def_opt_name, def_opt_value in self.project_default_options.items(): - for opts in env.coredata.get_all_options(): - cur_opt_value = opts.get(def_opt_name) - if cur_opt_value is not None: - def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value) - if def_opt_value != cur_opt_value.value: - yield (def_opt_name, def_opt_value, cur_opt_value) + cur_opt_value = self.coredata.options.get(def_opt_name) + if cur_opt_value is not None: + def_opt_value = env.coredata.validate_option_value(def_opt_name, def_opt_value) + if def_opt_value != cur_opt_value.value: + yield (str(def_opt_name), def_opt_value, cur_opt_value) def build_func_dict(self): self.funcs.update({'add_global_arguments': self.func_add_global_arguments, @@ -2905,7 +2904,7 @@ def do_subproject(self, subp_name: str, method: str, kwargs): return self.disabled_subproject(subp_name, disabled_feature=feature) default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - default_options = coredata.create_options_dict(default_options) + default_options = coredata.create_options_dict(default_options, subp_name) if subp_name == '': raise InterpreterException('Subproject name must not be empty.') @@ -3008,7 +3007,7 @@ def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwa def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, kwargs): with mlog.nested(): new_build = self.build.copy() - prefix = self.coredata.builtins['prefix'].value + prefix = self.coredata.options[OptionKey('prefix')].value from .modules.cmake import CMakeSubprojectOptions options = kwargs.get('options', CMakeSubprojectOptions()) @@ -3049,28 +3048,21 @@ def _do_subproject_cmake(self, subp_name, subdir, subdir_abs, default_options, k mlog.log() return result - def get_option_internal(self, optname): - raw_optname = optname - if self.is_subproject(): - optname = self.subproject + ':' + optname - + def get_option_internal(self, optname: str): + key = OptionKey.from_string(optname).evolve(subproject=self.subproject) - for opts in [ - self.coredata.base_options, compilers.base_options, self.coredata.builtins, - dict(self.coredata.get_prefixed_options_per_machine(self.coredata.builtins_per_machine)), - dict(self.coredata.flatten_lang_iterator( - self.coredata.get_prefixed_options_per_machine(self.coredata.compiler_options))), - ]: - v = opts.get(optname) - if v is None or v.yielding: - v = opts.get(raw_optname) - if v is not None: - return v + if not key.is_project(): + for opts in [self.coredata.options, compilers.base_options]: + v = opts.get(key) + if v is None or v.yielding: + v = opts.get(key.as_root()) + if v is not None: + return v try: - opt = self.coredata.user_options[optname] - if opt.yielding and ':' in optname and raw_optname in self.coredata.user_options: - popt = self.coredata.user_options[raw_optname] + opt = self.coredata.options[key] + if opt.yielding and key.subproject and key.as_root() in self.coredata.options: + popt = self.coredata.options[key.as_root()] if type(opt) is type(popt): opt = popt else: @@ -3082,7 +3074,7 @@ def get_option_internal(self, optname): mlog.warning('Option {0!r} of type {1!r} in subproject {2!r} cannot yield ' 'to parent option of type {3!r}, ignoring parent value. ' 'Use -D{2}:{0}=value to set the value for this option manually' - '.'.format(raw_optname, opt_type, self.subproject, popt_type), + '.'.format(optname, opt_type, self.subproject, popt_type), location=self.current_node) return opt except KeyError: @@ -3124,7 +3116,7 @@ def set_backend(self): # The backend is already set when parsing subprojects if self.backend is not None: return - backend = self.coredata.get_builtin_option('backend') + backend = self.coredata.get_option(OptionKey('backend')) from .backend import backends self.backend = backends.get_backend_from_name(backend, self.build, self) @@ -3133,14 +3125,14 @@ def set_backend(self): if backend != self.backend.name: if self.backend.name.startswith('vs'): mlog.log('Auto detected Visual Studio backend:', mlog.bold(self.backend.name)) - self.coredata.set_builtin_option('backend', self.backend.name) + self.coredata.set_option(OptionKey('backend'), self.backend.name) # Only init backend options on first invocation otherwise it would # override values previously set from command line. if self.environment.first_invocation: self.coredata.init_backend_options(backend) - options = {k: v for k, v in self.environment.raw_options.items() if k.startswith('backend_')} + options = {k: v for k, v in self.environment.options.items() if k.is_backend()} self.coredata.set_options(options) @stringArgs @@ -3164,7 +3156,7 @@ def func_project(self, node, args, kwargs): if os.path.exists(self.option_file): oi = optinterpreter.OptionInterpreter(self.subproject) oi.process(self.option_file) - self.coredata.merge_user_options(oi.options) + self.coredata.update_project_options(oi.options) self.add_build_def_file(self.option_file) # Do not set default_options on reconfigure otherwise it would override @@ -3172,7 +3164,7 @@ def func_project(self, node, args, kwargs): # default_options in a project will trigger a reconfigure but won't # have any effect. self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - self.project_default_options = coredata.create_options_dict(self.project_default_options) + self.project_default_options = coredata.create_options_dict(self.project_default_options, self.subproject) if self.environment.first_invocation: default_options = self.project_default_options.copy() default_options.update(self.default_project_options) @@ -3214,7 +3206,7 @@ def func_project(self, node, args, kwargs): self.build.subproject_dir = self.subproject_dir # Load wrap files from this (sub)project. - wrap_mode = self.coredata.get_builtin_option('wrap_mode') + wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) if not self.is_subproject() or wrap_mode != WrapMode.nopromote: subdir = os.path.join(self.subdir, spdirname) r = wrap.Resolver(self.environment.get_source_dir(), subdir, wrap_mode) @@ -3526,7 +3518,7 @@ def program_lookup(self, args, for_machine, required, search_dirs, extra_info): return progobj fallback = None - wrap_mode = self.coredata.get_builtin_option('wrap_mode') + wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) if wrap_mode != WrapMode.nofallback and self.environment.wrap_resolver: fallback = self.environment.wrap_resolver.find_program_provider(args) if fallback and wrap_mode == WrapMode.forcefallback: @@ -3830,8 +3822,8 @@ def dependency_impl(self, name, display_name, kwargs, force_fallback=False): if self.get_subproject(subp_name): return self.get_subproject_dep(name, display_name, subp_name, varname, kwargs) - wrap_mode = self.coredata.get_builtin_option('wrap_mode') - force_fallback_for = self.coredata.get_builtin_option('force_fallback_for') + wrap_mode = self.coredata.get_option(OptionKey('wrap_mode')) + force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) force_fallback = (force_fallback or wrap_mode == WrapMode.forcefallback or name in force_fallback_for or @@ -3877,11 +3869,11 @@ def dependency_fallback(self, name, display_name, fallback, kwargs): # Explicitly listed fallback preferences for specific subprojects # take precedence over wrap-mode - force_fallback_for = self.coredata.get_builtin_option('force_fallback_for') + force_fallback_for = self.coredata.get_option(OptionKey('force_fallback_for')) if name in force_fallback_for or subp_name in force_fallback_for: mlog.log('Looking for a fallback subproject for the dependency', mlog.bold(display_name), 'because:\nUse of fallback was forced for that specific subproject') - elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.nofallback: + elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.nofallback: mlog.log('Not looking for a fallback subproject for the dependency', mlog.bold(display_name), 'because:\nUse of fallback ' 'dependencies is disabled.') @@ -3889,7 +3881,7 @@ def dependency_fallback(self, name, display_name, fallback, kwargs): m = 'Dependency {!r} not found and fallback is disabled' raise DependencyException(m.format(display_name)) return self.notfound_dependency() - elif self.coredata.get_builtin_option('wrap_mode') == WrapMode.forcefallback: + elif self.coredata.get_option(OptionKey('wrap_mode')) == WrapMode.forcefallback: mlog.log('Looking for a fallback subproject for the dependency', mlog.bold(display_name), 'because:\nUse of fallback dependencies is forced.') else: @@ -4778,15 +4770,15 @@ def print_extra_warnings(self) -> None: break def check_clang_asan_lundef(self) -> None: - if 'b_lundef' not in self.coredata.base_options: + if OptionKey('b_lundef') not in self.coredata.options: return - if 'b_sanitize' not in self.coredata.base_options: + if OptionKey('b_sanitize') not in self.coredata.options: return - if (self.coredata.base_options['b_lundef'].value and - self.coredata.base_options['b_sanitize'].value != 'none'): + if (self.coredata.options[OptionKey('b_lundef')].value and + self.coredata.options[OptionKey('b_sanitize')].value != 'none'): mlog.warning('''Trying to use {} sanitizer on Clang with b_lundef. This will probably not work. -Try setting b_lundef to false instead.'''.format(self.coredata.base_options['b_sanitize'].value), +Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey('b_sanitize')].value), location=self.current_node) def evaluate_subproject_info(self, path_from_source_root, subproject_dir): @@ -4881,10 +4873,11 @@ def build_both_libraries(self, node, args, kwargs): # Check if user forces non-PIC static library. pic = True + key = OptionKey('b_staticpic') if 'pic' in kwargs: pic = kwargs['pic'] - elif 'b_staticpic' in self.environment.coredata.base_options: - pic = self.environment.coredata.base_options['b_staticpic'].value + elif key in self.environment.coredata.options: + pic = self.environment.coredata.options[key].value if pic: # Exclude sources from args and kwargs to avoid building them twice @@ -4901,7 +4894,7 @@ def build_both_libraries(self, node, args, kwargs): return BothLibrariesHolder(shared_holder, static_holder, self) def build_library(self, node, args, kwargs): - default_library = self.coredata.get_builtin_option('default_library', self.subproject) + default_library = self.coredata.get_option(OptionKey('default_library', subproject=self.subproject)) if default_library == 'shared': return self.build_target(node, args, kwargs, SharedLibraryHolder) elif default_library == 'static': diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 0f05fefe8b7e..141c8fdaa714 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -21,7 +21,7 @@ from .envconfig import get_env_var if T.TYPE_CHECKING: - from .coredata import OptionDictType + from .coredata import KeyedOptionDictType from .envconfig import MachineChoice from .environment import Environment @@ -40,7 +40,7 @@ def can_linker_accept_rsp(self) -> bool: """ return mesonlib.is_windows() - def get_base_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_base_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: """Like compilers.get_base_link_args, but for the static linker.""" return [] @@ -70,7 +70,7 @@ def thread_link_flags(self, env: 'Environment') -> T.List[str]: def openmp_flags(self) -> T.List[str]: return [] - def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_link_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] @classmethod @@ -378,7 +378,7 @@ def get_lib_prefix(self) -> str: # XXX: is use_ldflags a compiler or a linker attribute? - def get_option_args(self, options: 'OptionDictType') -> T.List[str]: + def get_option_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return [] def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: @@ -401,7 +401,7 @@ def get_debugfile_args(self, targetfile: str) -> T.List[str]: def get_std_shared_lib_args(self) -> T.List[str]: return [] - def get_std_shared_module_args(self, options: 'OptionDictType') -> T.List[str]: + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return self.get_std_shared_lib_args() def get_pie_args(self) -> T.List[str]: @@ -693,7 +693,7 @@ def get_asneeded_args(self) -> T.List[str]: def get_allow_undefined_args(self) -> T.List[str]: return self._apply_prefix('-undefined,dynamic_lookup') - def get_std_shared_module_args(self, options: 'OptionDictType') -> T.List[str]: + def get_std_shared_module_args(self, options: 'KeyedOptionDictType') -> T.List[str]: return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') def get_pie_args(self) -> T.List[str]: diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py index 41ed1b79abde..140e88f745a6 100644 --- a/mesonbuild/mcompile.py +++ b/mesonbuild/mcompile.py @@ -47,7 +47,7 @@ def get_backend_from_coredata(builddir: Path) -> str: """ Gets `backend` option value from coredata """ - backend = coredata.load(str(builddir)).get_builtin_option('backend') + backend = coredata.load(str(builddir)).get_option(mesonlib.OptionKey('backend')) assert isinstance(backend, str) return backend diff --git a/mesonbuild/mconf.py b/mesonbuild/mconf.py index 774dc5a07461..686a3367deed 100644 --- a/mesonbuild/mconf.py +++ b/mesonbuild/mconf.py @@ -17,8 +17,11 @@ from .ast import AstIDGenerator import typing as T +from .mesonlib import MachineChoice, OptionKey + if T.TYPE_CHECKING: import argparse + from .coredata import UserOption def add_arguments(parser: 'argparse.ArgumentParser') -> None: coredata.register_builtin_arguments(parser) @@ -26,7 +29,6 @@ def add_arguments(parser: 'argparse.ArgumentParser') -> None: parser.add_argument('--clearcache', action='store_true', default=False, help='Clear cached state (e.g. found dependencies)') - def make_lower_case(val: T.Any) -> T.Union[str, T.List[T.Any]]: # T.Any because of recursion... if isinstance(val, bool): return str(val).lower() @@ -52,8 +54,8 @@ def __init__(self, build_dir): self.choices_col = [] self.descr_col = [] self.has_choices = False - self.all_subprojects = set() - self.yielding_options = set() + self.all_subprojects: T.Set[str] = set() + self.yielding_options: T.Set[OptionKey] = set() if os.path.isdir(os.path.join(self.build_dir, 'meson-private')): self.build = build.load(self.build_dir) @@ -101,20 +103,20 @@ def print_aligned(self): else: print('{0:{width[0]}} {1:{width[1]}} {3}'.format(*line, width=col_widths)) - def split_options_per_subproject(self, options): - result = {} + def split_options_per_subproject(self, options: 'coredata.KeyedOptionDictType') -> T.Dict[str, T.Dict[str, 'UserOption']]: + result: T.Dict[str, T.Dict[str, 'UserOption']] = {} for k, o in options.items(): - subproject = '' - if ':' in k: - subproject, optname = k.split(':') - if o.yielding and optname in options: + subproject = k.subproject + if k.subproject: + k = k.as_root() + if o.yielding and k in options: self.yielding_options.add(k) self.all_subprojects.add(subproject) - result.setdefault(subproject, {})[k] = o + result.setdefault(subproject, {})[str(k)] = o return result - def _add_line(self, name, value, choices, descr): - self.name_col.append(' ' * self.print_margin + name) + def _add_line(self, name: OptionKey, value, choices, descr) -> None: + self.name_col.append(' ' * self.print_margin + str(name)) self.value_col.append(value) self.choices_col.append(choices) self.descr_col.append(descr) @@ -163,7 +165,7 @@ def add_section(self, section): self._add_line(section + ':', '', '', '') self.print_margin = 2 - def print_options(self, title, options): + def print_options(self, title: str, options: 'coredata.KeyedOptionDictType') -> None: if not options: return if title: @@ -188,33 +190,34 @@ def print_default_values_warning(): if not self.default_values_only: print(' Build dir ', self.build_dir) - dir_option_names = list(coredata.BUILTIN_DIR_OPTIONS) - test_option_names = ['errorlogs', - 'stdsplit'] - core_option_names = [k for k in self.coredata.builtins if k not in dir_option_names + test_option_names] - - dir_options = {k: o for k, o in self.coredata.builtins.items() if k in dir_option_names} - test_options = {k: o for k, o in self.coredata.builtins.items() if k in test_option_names} - core_options = {k: o for k, o in self.coredata.builtins.items() if k in core_option_names} - - core_options = self.split_options_per_subproject(core_options) - host_compiler_options = self.split_options_per_subproject( - dict(self.coredata.flatten_lang_iterator( - self.coredata.compiler_options.host.items()))) - build_compiler_options = self.split_options_per_subproject( - dict(self.coredata.flatten_lang_iterator( - (self.coredata.insert_build_prefix(k), o) - for k, o in self.coredata.compiler_options.build.items()))) - project_options = self.split_options_per_subproject(self.coredata.user_options) + dir_option_names = set(coredata.BUILTIN_DIR_OPTIONS) + test_option_names = {OptionKey('errorlogs'), + OptionKey('stdsplit')} + + dir_options: 'coredata.KeyedOptionDictType' = {} + test_options: 'coredata.KeyedOptionDictType' = {} + core_options: 'coredata.KeyedOptionDictType' = {} + for k, v in self.coredata.options.items(): + if k in dir_option_names: + dir_options[k] = v + elif k in test_option_names: + test_options[k] = v + elif k.is_builtin(): + core_options[k] = v + + host_core_options = self.split_options_per_subproject({k: v for k, v in core_options.items() if k.machine is MachineChoice.HOST}) + build_core_options = self.split_options_per_subproject({k: v for k, v in core_options.items() if k.machine is MachineChoice.BUILD}) + host_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_compiler() and k.machine is MachineChoice.HOST}) + build_compiler_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_compiler() and k.machine is MachineChoice.BUILD}) + project_options = self.split_options_per_subproject({k: v for k, v in self.coredata.options.items() if k.is_project()}) show_build_options = self.default_values_only or self.build.environment.is_cross_build() self.add_section('Main project options') - self.print_options('Core options', core_options['']) - self.print_options('', self.coredata.builtins_per_machine.host) + self.print_options('Core options', host_core_options['']) if show_build_options: - self.print_options('', {self.coredata.insert_build_prefix(k): o for k, o in self.coredata.builtins_per_machine.build.items()}) - self.print_options('Backend options', self.coredata.backend_options) - self.print_options('Base options', self.coredata.base_options) + self.print_options('', build_core_options['']) + self.print_options('Backend options', {str(k): v for k, v in self.coredata.options.items()}) + self.print_options('Base options', {str(k): v for k, v in self.coredata.options.items()}) self.print_options('Compiler options', host_compiler_options.get('', {})) if show_build_options: self.print_options('', build_compiler_options.get('', {})) @@ -251,7 +254,7 @@ def run(options): return 0 save = False - if len(options.cmd_line_options) > 0: + if options.cmd_line_options: c.set_options(options.cmd_line_options) coredata.update_cmd_line_file(builddir, options) save = True diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index f73778e9b3d2..2c1727b6b32a 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -14,13 +14,13 @@ """A library of random helper functionality.""" from pathlib import Path +import enum import sys import stat import time import platform, subprocess, operator, os, shlex, shutil, re import collections -from enum import IntEnum -from functools import lru_cache, wraps +from functools import lru_cache, wraps, total_ordering from itertools import tee, filterfalse from tempfile import TemporaryDirectory import typing as T @@ -31,7 +31,7 @@ if T.TYPE_CHECKING: from .build import ConfigurationData - from .coredata import OptionDictType, UserOption + from .coredata import KeyedOptionDictType, UserOption from .compilers.compilers import CompilerType from .interpreterbase import ObjectHolder @@ -347,7 +347,7 @@ def classify_unity_sources(compilers: T.Iterable['CompilerType'], sources: T.Ite return compsrclist -class MachineChoice(IntEnum): +class MachineChoice(enum.IntEnum): """Enum class representing one of the two abstract machine names used in most places: the build, and host, machines. @@ -1616,7 +1616,7 @@ def relative_to_if_possible(path: Path, root: Path, resolve: bool = False) -> Pa except ValueError: return path -class LibType(IntEnum): +class LibType(enum.IntEnum): """Enumeration for library types.""" @@ -1738,8 +1738,13 @@ def wrapper(*args: T.Any, **kwargs: T.Any) -> _T: class OptionProxy(T.Generic[_T]): - def __init__(self, value: _T): + def __init__(self, value: _T, choices: T.Optional[T.List[str]] = None): self.value = value + self.choices = choices + + def set_value(self, v: _T) -> None: + # XXX: should this be an error + self.value = v class OptionOverrideProxy(collections.abc.MutableMapping): @@ -1751,27 +1756,27 @@ class OptionOverrideProxy(collections.abc.MutableMapping): # TODO: the typing here could be made more explicit using a TypeDict from # python 3.8 or typing_extensions - def __init__(self, overrides: T.Dict[str, T.Any], *options: 'OptionDictType'): + def __init__(self, overrides: T.Dict['OptionKey', T.Any], *options: 'KeyedOptionDictType'): self.overrides = overrides.copy() - self.options = {} # type: T.Dict[str, UserOption] + self.options: T.Dict['OptionKey', UserOption] = {} for o in options: self.options.update(o) - def __getitem__(self, key: str) -> T.Union['UserOption', OptionProxy]: + def __getitem__(self, key: 'OptionKey') -> T.Union['UserOption', OptionProxy]: if key in self.options: opt = self.options[key] if key in self.overrides: - return OptionProxy(opt.validate_value(self.overrides[key])) + return OptionProxy(opt.validate_value(self.overrides[key]), getattr(opt, 'choices', None)) return opt raise KeyError('Option not found', key) - def __setitem__(self, key: str, value: T.Union['UserOption', OptionProxy]) -> None: + def __setitem__(self, key: 'OptionKey', value: T.Union['UserOption', OptionProxy]) -> None: self.overrides[key] = value.value - def __delitem__(self, key: str) -> None: + def __delitem__(self, key: 'OptionKey') -> None: del self.overrides[key] - def __iter__(self) -> T.Iterator[str]: + def __iter__(self) -> T.Iterator['OptionKey']: return iter(self.options) def __len__(self) -> int: @@ -1779,3 +1784,244 @@ def __len__(self) -> int: def copy(self) -> 'OptionOverrideProxy': return OptionOverrideProxy(self.overrides.copy(), self.options.copy()) + + +class OptionType(enum.Enum): + + """Enum used to specify what kind of argument a thing is.""" + + BUILTIN = 0 + BASE = 1 + COMPILER = 2 + PROJECT = 3 + BACKEND = 4 + +# This is copied from coredata. There is no way to share this, because this +# is used in the OptionKey constructor, and the coredata lists are +# OptionKeys... +_BUILTIN_NAMES = { + 'prefix', + 'bindir', + 'datadir', + 'includedir', + 'infodir', + 'libdir', + 'libexecdir', + 'localedir', + 'localstatedir', + 'mandir', + 'sbindir', + 'sharedstatedir', + 'sysconfdir', + 'auto_features', + 'backend', + 'buildtype', + 'debug', + 'default_library', + 'errorlogs', + 'install_umask', + 'layout', + 'optimization', + 'stdsplit', + 'strip', + 'unity', + 'unity_size', + 'warning_level', + 'werror', + 'wrap_mode', + 'force_fallback_for', + 'pkg_config_path', + 'cmake_prefix_path', +} + + +def _classify_argument(key: 'OptionKey') -> OptionType: + """Classify arguments into groups so we know which dict to assign them to.""" + + if key.name.startswith('b_'): + assert key.machine is MachineChoice.HOST, str(key) + return OptionType.BASE + elif key.lang is not None: + return OptionType.COMPILER + elif key.name in _BUILTIN_NAMES: + return OptionType.BUILTIN + elif key.name.startswith('backend_'): + assert key.machine is MachineChoice.HOST, str(key) + return OptionType.BACKEND + else: + assert key.machine is MachineChoice.HOST, str(key) + return OptionType.PROJECT + + +@total_ordering +class OptionKey: + + """Represents an option key in the various option dictionaries. + + This provides a flexible, powerful way to map option names from their + external form (things like subproject:build.option) to something that + internally easier to reason about and produce. + """ + + __slots__ = ['name', 'subproject', 'machine', 'lang', '_hash', 'type'] + + name: str + subproject: str + machine: MachineChoice + lang: T.Optional[str] + _hash: int + type: OptionType + + def __init__(self, name: str, subproject: str = '', + machine: MachineChoice = MachineChoice.HOST, + lang: T.Optional[str] = None, _type: T.Optional[OptionType] = None): + # the _type option to the constructor is kinda private. We want to be + # able tos ave the state and avoid the lookup function when + # pickling/unpickling, but we need to be able to calculate it when + # constructing a new OptionKey + object.__setattr__(self, 'name', name) + object.__setattr__(self, 'subproject', subproject) + object.__setattr__(self, 'machine', machine) + object.__setattr__(self, 'lang', lang) + object.__setattr__(self, '_hash', hash((name, subproject, machine, lang))) + if _type is None: + _type = _classify_argument(self) + object.__setattr__(self, 'type', _type) + + def __setattr__(self, key: str, value: T.Any) -> None: + raise AttributeError('OptionKey instances do not support mutation.') + + def __getstate__(self) -> T.Dict[str, T.Any]: + return { + 'name': self.name, + 'subproject': self.subproject, + 'machine': self.machine, + 'lang': self.lang, + '_type': self.type, + } + + def __setstate__(self, state: T.Dict[str, T.Any]) -> None: + """De-serialize the state of a pickle. + + This is very clever. __init__ is not a constructor, it's an + initializer, therefore it's safe to call more than once. We create a + state in the custom __getstate__ method, which is valid to pass + splatted to the initializer. + """ + # Mypy doesn't like this, because it's so clever. + self.__init__(**state) # type: ignore + + def __hash__(self) -> int: + return self._hash + + def __eq__(self, other: object) -> bool: + if isinstance(other, OptionKey): + return ( + self.name == other.name and + self.subproject == other.subproject and + self.machine is other.machine and + self.lang == other.lang) + return NotImplemented + + def __lt__(self, other: object) -> bool: + if isinstance(other, OptionKey): + return ( + self.name < other.name and + self.subproject < other.subproject and + self.machine < other.machine and + self.lang < other.lang) + return NotImplemented + + def __str__(self) -> str: + out = self.name + if self.lang: + out = f'{self.lang}_{out}' + if self.machine is MachineChoice.BUILD: + out = f'build.{out}' + if self.subproject: + out = f'{self.subproject}:{out}' + return out + + def __repr__(self) -> str: + return f'OptionKey({repr(self.name)}, {repr(self.subproject)}, {repr(self.machine)}, {repr(self.lang)})' + + @classmethod + def from_string(cls, raw: str) -> 'OptionKey': + """Parse the raw command line format into a three part tuple. + + This takes strings like `mysubproject:build.myoption` and Creates an + OptionKey out of them. + """ + + try: + subproject, raw2 = raw.split(':') + except ValueError: + subproject, raw2 = '', raw + + if raw2.startswith('build.'): + raw3 = raw2.lstrip('build.') + for_machine = MachineChoice.BUILD + else: + raw3 = raw2 + for_machine = MachineChoice.HOST + + from .compilers import all_languages + if any(raw3.startswith(f'{l}_') for l in all_languages): + lang, opt = raw3.split('_', 1) + else: + lang, opt = None, raw3 + assert ':' not in opt + assert 'build.' not in opt + + return cls(opt, subproject, for_machine, lang) + + def evolve(self, name: T.Optional[str] = None, subproject: T.Optional[str] = None, + machine: T.Optional[MachineChoice] = None, lang: T.Optional[str] = '') -> 'OptionKey': + """Create a new copy of this key, but with alterted members. + + For example: + >>> a = OptionKey('foo', '', MachineChoice.Host) + >>> b = OptionKey('foo', 'bar', MachineChoice.Host) + >>> b == a.evolve(subproject='bar') + True + """ + # We have to be a little clever with lang here, because lang is valid + # as None, for non-compiler options + return OptionKey( + name if name is not None else self.name, + subproject if subproject is not None else self.subproject, + machine if machine is not None else self.machine, + lang if lang != '' else self.lang, + ) + + def as_root(self) -> 'OptionKey': + """Convenience method for key.evolve(subproject='').""" + return self.evolve(subproject='') + + def as_build(self) -> 'OptionKey': + """Convenience method for key.evolve(machine=MachinceChoice.BUILD).""" + return self.evolve(machine=MachineChoice.BUILD) + + def as_host(self) -> 'OptionKey': + """Convenience method for key.evolve(machine=MachinceChoice.HOST).""" + return self.evolve(machine=MachineChoice.HOST) + + def is_backend(self) -> bool: + """Convenience method to check if this is a backend option.""" + return self.type is OptionType.BACKEND + + def is_builtin(self) -> bool: + """Convenience method to check if this is a builtin option.""" + return self.type is OptionType.BUILTIN + + def is_compiler(self) -> bool: + """Convenience method to check if this is a builtin option.""" + return self.type is OptionType.COMPILER + + def is_project(self) -> bool: + """Convenience method to check if this is a project option.""" + return self.type is OptionType.PROJECT + + def is_base(self) -> bool: + """Convenience method to check if this is a base option.""" + return self.type is OptionType.BASE \ No newline at end of file diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index f6262c365c86..52f4ac0cbf60 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -21,6 +21,7 @@ import collections import json +from mesonbuild.compilers import d from . import build, coredata as cdata from . import mesonlib from .ast import IntrospectionInterpreter, build_target_functions, AstConditionLevel, AstIDGenerator, AstIndentationGenerator, AstJSONPrinter @@ -33,6 +34,8 @@ import os import argparse +from .mesonlib import OptionKey + def get_meson_info_file(info_dir: str) -> str: return os.path.join(info_dir, 'meson-info.json') @@ -167,8 +170,8 @@ def nodes_to_paths(node_list: T.List[BaseNode]) -> T.List[Path]: return tlist -def list_targets(builddata: build.Build, installdata: backends.InstallData, backend: backends.Backend) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]: - tlist = [] # type: T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]] +def list_targets(builddata: build.Build, installdata: backends.InstallData, backend: backends.Backend) -> T.List[T.Any]: + tlist = [] # type: T.List[T.Any] build_dir = builddata.environment.get_build_dir() src_dir = builddata.environment.get_source_dir() @@ -197,8 +200,8 @@ def list_targets(builddata: build.Build, installdata: backends.InstallData, back if installdata and target.should_install(): t['installed'] = True - t['install_filename'] = [install_lookuptable.get(x, [None]) for x in target.get_outputs()] - t['install_filename'] = [x for sublist in t['install_filename'] for x in sublist] # flatten the list + ifn = [install_lookuptable.get(x, [None]) for x in target.get_outputs()] + t['install_filename'] = [x for sublist in ifn for x in sublist] # flatten the list else: t['installed'] = False tlist.append(t) @@ -210,30 +213,30 @@ def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Di def list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]: optlist = [] # type: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] - - dir_option_names = list(cdata.BUILTIN_DIR_OPTIONS) - test_option_names = ['errorlogs', - 'stdsplit'] - core_option_names = [k for k in coredata.builtins if k not in dir_option_names + test_option_names] - - dir_options = {k: o for k, o in coredata.builtins.items() if k in dir_option_names} - test_options = {k: o for k, o in coredata.builtins.items() if k in test_option_names} - core_options = {k: o for k, o in coredata.builtins.items() if k in core_option_names} - - if subprojects: - # Add per subproject built-in options - sub_core_options = {} - for sub in subprojects: - for k, o in core_options.items(): - if o.yielding: - continue - sub_core_options[sub + ':' + k] = o - core_options.update(sub_core_options) - - def add_keys(options: 'cdata.OptionDictType', section: str, machine: str = 'any') -> None: - for key in sorted(options.keys()): - opt = options[key] - optdict = {'name': key, 'value': opt.value, 'section': section, 'machine': machine} + subprojects = subprojects or [] + + dir_option_names = set(cdata.BUILTIN_DIR_OPTIONS) + test_option_names = {OptionKey('errorlogs'), + OptionKey('stdsplit')} + + dir_options: 'cdata.KeyedOptionDictType' = {} + test_options: 'cdata.KeyedOptionDictType' = {} + core_options: 'cdata.KeyedOptionDictType' = {} + for k, v in coredata.options.items(): + if k in dir_option_names: + dir_options[k] = v + elif k in test_option_names: + test_options[k] = v + elif k.is_builtin(): + core_options[k] = v + if not v.yielding: + 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()): + 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): typestr = 'string' elif isinstance(opt, cdata.UserBooleanOption): @@ -252,27 +255,14 @@ def add_keys(options: 'cdata.OptionDictType', section: str, machine: str = 'any' optlist.append(optdict) add_keys(core_options, 'core') - add_keys(coredata.builtins_per_machine.host, 'core', machine='host') - add_keys( - {'build.' + k: o for k, o in coredata.builtins_per_machine.build.items()}, - 'core', - machine='build', - ) - add_keys(coredata.backend_options, 'backend') - add_keys(coredata.base_options, 'base') - add_keys( - dict(coredata.flatten_lang_iterator(coredata.compiler_options.host.items())), - 'compiler', - machine='host', - ) - tmp_dict = dict(coredata.flatten_lang_iterator(coredata.compiler_options.build.items())) # type: T.Dict[str, cdata.UserOption] + add_keys({k: v for k, v in coredata.options.items() if k.is_backend()}, 'backend') + add_keys({k: v for k, v in coredata.options.items() if k.is_base()}, 'base') add_keys( - {'build.' + k: o for k, o in tmp_dict.items()}, + {k: v for k, v in sorted(coredata.options.items(), key=lambda i: i[0].machine) if k.is_compiler()}, 'compiler', - machine='build', ) add_keys(dir_options, 'directory') - add_keys(coredata.user_options, 'user') + add_keys({k: v for k, v in coredata.options.items() if k.is_project()}, 'user') add_keys(test_options, 'test') return optlist diff --git a/mesonbuild/modules/cmake.py b/mesonbuild/modules/cmake.py index 35c85a790411..f6afaf35ceb1 100644 --- a/mesonbuild/modules/cmake.py +++ b/mesonbuild/modules/cmake.py @@ -269,7 +269,7 @@ def write_basic_package_version_file(self, state, _args, kwargs): pkgroot = kwargs.get('install_dir', None) if pkgroot is None: - pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'cmake', name) + pkgroot = os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('libdir')), 'cmake', name) if not isinstance(pkgroot, str): raise mesonlib.MesonException('Install_dir must be a string.') @@ -342,7 +342,7 @@ def configure_package_config_file(self, interpreter, state, args, kwargs): (ofile_path, ofile_fname) = os.path.split(os.path.join(state.subdir, '{}Config.cmake'.format(name))) ofile_abs = os.path.join(state.environment.build_dir, ofile_path, ofile_fname) - install_dir = kwargs.get('install_dir', os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'cmake', name)) + install_dir = kwargs.get('install_dir', os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('libdir')), 'cmake', name)) if not isinstance(install_dir, str): raise mesonlib.MesonException('"install_dir" must be a string.') @@ -352,7 +352,7 @@ def configure_package_config_file(self, interpreter, state, args, kwargs): if not isinstance(conf, ConfigurationDataHolder): raise mesonlib.MesonException('Argument "configuration" is not of type configuration_data') - prefix = state.environment.coredata.get_builtin_option('prefix') + prefix = state.environment.coredata.get_option(mesonlib.OptionKey('prefix')) abs_install_dir = install_dir if not os.path.isabs(abs_install_dir): abs_install_dir = os.path.join(prefix, install_dir) diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 547aff17cd4e..21570bd8b7ef 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -19,6 +19,7 @@ import copy import subprocess import functools +import typing as T from .. import build from .. import mlog @@ -35,6 +36,9 @@ from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs +if T.TYPE_CHECKING: + from ..compilers import Compiler + # gresource compilation is broken due to the way # the resource compiler and Ninja clash about it # @@ -208,7 +212,7 @@ def compile_resources(self, state, args, kwargs): if install_header: h_kwargs['install'] = install_header h_kwargs['install_dir'] = kwargs.get('install_dir', - state.environment.coredata.get_builtin_option('includedir')) + state.environment.coredata.get_option(mesonlib.OptionKey('includedir'))) target_h = GResourceHeaderTarget(args[0] + '_h', state.subdir, state.subproject, h_kwargs) rv = [target_c, target_h] return ModuleReturnValue(rv, rv) @@ -574,8 +578,8 @@ def _scan_gir_targets(self, state, girtargets): return ret - def _get_girtargets_langs_compilers(self, girtargets): - ret = [] + def _get_girtargets_langs_compilers(self, girtargets: T.List[GirTarget]) -> T.List[T.Tuple[str, 'Compiler']]: + ret: T.List[T.Tuple[str, 'Compiler']] = [] for girtarget in girtargets: for lang, compiler in girtarget.compilers.items(): # XXX: Can you use g-i with any other language? @@ -598,7 +602,7 @@ def _get_gir_targets_inc_dirs(self, girtargets): ret += girtarget.get_include_dirs() return ret - def _get_langs_compilers_flags(self, state, langs_compilers): + def _get_langs_compilers_flags(self, state, langs_compilers: T.List[T.Tuple[str, 'Compiler']]): cflags = [] internal_ldflags = [] external_ldflags = [] @@ -608,8 +612,8 @@ def _get_langs_compilers_flags(self, state, langs_compilers): cflags += state.global_args[lang] if state.project_args.get(lang): cflags += state.project_args[lang] - if 'b_sanitize' in compiler.base_options: - sanitize = state.environment.coredata.base_options['b_sanitize'].value + if mesonlib.OptionKey('b_sanitize') in compiler.base_options: + sanitize = state.environment.coredata.options[mesonlib.OptionKey('b_sanitize')].value cflags += compiler.sanitizer_compile_args(sanitize) sanitize = sanitize.split(',') # These must be first in ldflags @@ -1171,7 +1175,7 @@ def gdbus_codegen(self, state, args, kwargs): targets = [] install_header = kwargs.get('install_header', False) - install_dir = kwargs.get('install_dir', state.environment.coredata.get_builtin_option('includedir')) + install_dir = kwargs.get('install_dir', state.environment.coredata.get_option(mesonlib.OptionKey('includedir'))) output = namebase + '.c' # Added in https://gitlab.gnome.org/GNOME/glib/commit/e4d68c7b3e8b01ab1a4231bf6da21d045cb5a816 (2.55.2) @@ -1328,7 +1332,7 @@ def mkenums(self, state, args, kwargs): custom_kwargs['install'] = install_header if 'install_dir' not in custom_kwargs: custom_kwargs['install_dir'] = \ - state.environment.coredata.get_builtin_option('includedir') + state.environment.coredata.get_option(mesonlib.OptionKey('includedir')) h_target = self._make_mkenum_custom_target(state, h_sources, h_output, h_cmd, custom_kwargs) @@ -1357,7 +1361,7 @@ def mkenums(self, state, args, kwargs): custom_kwargs['install'] = install_header if 'install_dir' not in custom_kwargs: custom_kwargs['install_dir'] = \ - state.environment.coredata.get_builtin_option('includedir') + state.environment.coredata.get_option(mesonlib.OptionKey('includedir')) target = self._make_mkenum_custom_target(state, sources, basename, generic_cmd, custom_kwargs) return ModuleReturnValue(target, [target]) @@ -1687,7 +1691,7 @@ def generate_vapi(self, state, args, kwargs): 'depends': vapi_depends, } install_dir = kwargs.get('install_dir', - os.path.join(state.environment.coredata.get_builtin_option('datadir'), + os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('datadir')), 'vala', 'vapi')) if kwargs.get('install'): custom_kwargs['install'] = kwargs['install'] diff --git a/mesonbuild/modules/hotdoc.py b/mesonbuild/modules/hotdoc.py index 5c04e2740adc..ee756e71c4a8 100644 --- a/mesonbuild/modules/hotdoc.py +++ b/mesonbuild/modules/hotdoc.py @@ -326,7 +326,7 @@ def make_targets(self): for path in self.include_paths.keys(): self.cmd.extend(['--include-path', path]) - if self.state.environment.coredata.get_builtin_option('werror', self.state.subproject): + if self.state.environment.coredata.get_option(mesonlib.OptionKey('werror', subproject=self.state.subproject)): self.cmd.append('--fatal-warning') self.generate_hotdoc_config() diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 2652e7d2a614..d48f83bc455b 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -172,7 +172,7 @@ def gettext(self, state, args, kwargs): install = kwargs.get('install', True) if install: - install_dir = kwargs.get('install_dir', state.environment.coredata.get_builtin_option('localedir')) + install_dir = kwargs.get('install_dir', state.environment.coredata.get_option(mesonlib.OptionKey('localedir'))) script = state.environment.get_build_command() args = ['--internal', 'gettext', 'install', '--subdir=' + state.subdir, diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index 7e19d71771a9..7d347a6f1baa 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -331,10 +331,10 @@ def generate_pkgconfig_file(self, state, deps, subdirs, name, description, srcdir = PurePath(state.environment.get_source_dir()) else: outdir = state.environment.scratch_dir - prefix = PurePath(coredata.get_builtin_option('prefix')) + prefix = PurePath(coredata.get_option(mesonlib.OptionKey('prefix'))) # These always return paths relative to prefix - libdir = PurePath(coredata.get_builtin_option('libdir')) - incdir = PurePath(coredata.get_builtin_option('includedir')) + libdir = PurePath(coredata.get_option(mesonlib.OptionKey('libdir'))) + incdir = PurePath(coredata.get_option(mesonlib.OptionKey('includedir'))) fname = os.path.join(outdir, pcfile) with open(fname, 'w', encoding='utf-8') as ofile: if not dataonly: @@ -531,9 +531,9 @@ def parse_variable_list(vardict): pkgroot = kwargs.get('install_dir', default_install_dir) if pkgroot is None: if mesonlib.is_freebsd(): - pkgroot = os.path.join(state.environment.coredata.get_builtin_option('prefix'), 'libdata', 'pkgconfig') + pkgroot = os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('prefix')), 'libdata', 'pkgconfig') else: - pkgroot = os.path.join(state.environment.coredata.get_builtin_option('libdir'), 'pkgconfig') + pkgroot = os.path.join(state.environment.coredata.get_option(mesonlib.OptionKey('libdir')), 'pkgconfig') if not isinstance(pkgroot, str): raise mesonlib.MesonException('Install_dir must be a string.') self.generate_pkgconfig_file(state, deps, subdirs, name, description, url, diff --git a/mesonbuild/modules/python.py b/mesonbuild/modules/python.py index ff0df2f9d9be..564d18143936 100644 --- a/mesonbuild/modules/python.py +++ b/mesonbuild/modules/python.py @@ -288,7 +288,7 @@ def __init__(self, interpreter, python, info): ExternalProgramHolder.__init__(self, python, interpreter.subproject) self.interpreter = interpreter self.subproject = self.interpreter.subproject - prefix = self.interpreter.environment.coredata.get_builtin_option('prefix') + prefix = self.interpreter.environment.coredata.get_option(mesonlib.OptionKey('prefix')) self.variables = info['variables'] self.paths = info['paths'] install_paths = info['install_paths'] diff --git a/mesonbuild/modules/unstable_external_project.py b/mesonbuild/modules/unstable_external_project.py index 7bb761f7dbaf..7249078ac1c7 100644 --- a/mesonbuild/modules/unstable_external_project.py +++ b/mesonbuild/modules/unstable_external_project.py @@ -26,6 +26,7 @@ from ..compilers.compilers import cflags_mapping, cexe_mapping from ..dependencies.base import InternalDependency, PkgConfigDependency from ..environment import Environment +from ..mesonlib import OptionKey class ExternalProject(InterpreterObject): def __init__(self, @@ -62,9 +63,9 @@ def __init__(self, self.src_dir = Path(self.env.get_source_dir(), self.subdir) self.build_dir = Path(self.env.get_build_dir(), self.subdir, 'build') self.install_dir = Path(self.env.get_build_dir(), self.subdir, 'dist') - self.prefix = Path(self.env.coredata.get_builtin_option('prefix')) - self.libdir = Path(self.env.coredata.get_builtin_option('libdir')) - self.includedir = Path(self.env.coredata.get_builtin_option('includedir')) + self.prefix = Path(self.env.coredata.get_option(OptionKey('prefix'))) + self.libdir = Path(self.env.coredata.get_option(OptionKey('libdir'))) + self.includedir = Path(self.env.coredata.get_option(OptionKey('includedir'))) # On Windows if the prefix is "c:/foo" and DESTDIR is "c:/bar", `make` # will install files into "c:/bar/c:/foo" which is an invalid path. diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py index d336a13fda96..11fe3ce4fd75 100644 --- a/mesonbuild/msetup.py +++ b/mesonbuild/msetup.py @@ -177,7 +177,7 @@ def generate(self) -> None: mlog.initialize(env.get_log_dir(), self.options.fatal_warnings) if self.options.profile: mlog.set_timestamp_start(time.monotonic()) - if env.coredata.builtins['backend'].value == 'xcode': + if env.coredata.options[mesonlib.OptionKey('backend')].value == 'xcode': mlog.warning('xcode backend is currently unmaintained, patches welcome') with mesonlib.BuildDirLock(self.build_dir): self._generate(env) diff --git a/mesonbuild/munstable_coredata.py b/mesonbuild/munstable_coredata.py index 5463f1625116..0ca8f3398c62 100644 --- a/mesonbuild/munstable_coredata.py +++ b/mesonbuild/munstable_coredata.py @@ -14,7 +14,7 @@ from . import coredata as cdata -from .mesonlib import MachineChoice +from .mesonlib import MachineChoice, OptionKey import os.path import pprint @@ -59,7 +59,7 @@ def run(options): print('') coredata = cdata.load(options.builddir) - backend = coredata.get_builtin_option('backend') + backend = coredata.get_option(OptionKey('backend')) for k, v in sorted(coredata.__dict__.items()): if k in ('backend_options', 'base_options', 'builtins', 'compiler_options', 'user_options'): # use `meson configure` to view these diff --git a/mesonbuild/optinterpreter.py b/mesonbuild/optinterpreter.py index 0b18f7e676c5..dc8c3ce5cbfa 100644 --- a/mesonbuild/optinterpreter.py +++ b/mesonbuild/optinterpreter.py @@ -137,7 +137,7 @@ def feature_parser(description: str, kwargs: T.Dict[str, T.Any]) -> coredata.Use class OptionInterpreter: def __init__(self, subproject: str) -> None: - self.options: T.Dict[str, coredata.UserOption] = {} + self.options: 'coredata.KeyedOptionDictType' = {} self.subproject = subproject def process(self, option_file: str) -> None: @@ -227,8 +227,7 @@ def evaluate_statement(self, node: mparser.BaseNode) -> None: raise OptionException('Option names can only contain letters, numbers or dashes.') if is_invalid_name(opt_name): raise OptionException('Option name %s is reserved.' % opt_name) - if self.subproject != '': - opt_name = self.subproject + ':' + opt_name + key = mesonlib.OptionKey(opt_name, self.subproject) if 'yield' in kwargs: FeatureNew.single_use('option yield', '0.45.0', self.subproject) @@ -248,4 +247,4 @@ def evaluate_statement(self, node: mparser.BaseNode) -> None: opt = option_types[opt_type](opt_name, description, kwargs) if opt.description == '': opt.description = opt_name - self.options[opt_name] = opt + self.options[key] = opt diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py index 857ba0e37c1e..6b7a98747390 100644 --- a/mesonbuild/rewriter.py +++ b/mesonbuild/rewriter.py @@ -464,14 +464,11 @@ def process_default_options(self, cmd): cdata = self.interpreter.coredata options = { - **cdata.builtins, - **cdata.builtins_per_machine.host, - **{'build.' + k: o for k, o in cdata.builtins_per_machine.build.items()}, - **cdata.backend_options, - **cdata.base_options, - **(dict(cdata.flatten_lang_iterator(cdata.compiler_options.host.items()))), - **{'build.' + k: o for k, o in cdata.flatten_lang_iterator(cdata.compiler_options.build.items())}, - **cdata.user_options, + **{str(k): v for k, v in cdata.options.items()}, + **{str(k): v for k, v in cdata.options.items()}, + **{str(k): v for k, v in cdata.options.items()}, + **{str(k): v for k, v in cdata.options.items()}, + **{str(k): v for k, v in cdata.options.items()}, } for key, val in sorted(cmd['options'].items()): diff --git a/mesonbuild/scripts/regen_checker.py b/mesonbuild/scripts/regen_checker.py index fa98f5989049..11877831f392 100644 --- a/mesonbuild/scripts/regen_checker.py +++ b/mesonbuild/scripts/regen_checker.py @@ -17,6 +17,7 @@ import typing as T from ..coredata import CoreData from ..backend.vs2010backend import RegenInfo +from ..mesonlib import OptionKey # This could also be used for XCode. @@ -52,7 +53,7 @@ def run(args: T.List[str]) -> int: with open(coredata_file, 'rb') as f: coredata = pickle.load(f) assert isinstance(coredata, CoreData) - backend = coredata.get_builtin_option('backend') + backend = coredata.get_option(OptionKey('backend')) assert isinstance(backend, str) regen_timestamp = os.stat(dumpfile).st_mtime if need_regen(regeninfo, regen_timestamp): diff --git a/run_tests.py b/run_tests.py index 0f02636f53fd..da894c055567 100755 --- a/run_tests.py +++ b/run_tests.py @@ -35,6 +35,7 @@ from mesonbuild import mlog from mesonbuild.environment import Environment, detect_ninja from mesonbuild.coredata import backendlist, version as meson_version +from mesonbuild.mesonlib import OptionKey NINJA_1_9_OR_NEWER = False NINJA_CMD = None @@ -127,7 +128,7 @@ def get_fake_env(sdir='', bdir=None, prefix='', opts=None): if opts is None: opts = get_fake_options(prefix) env = Environment(sdir, bdir, opts) - env.coredata.compiler_options.host['c']['args'] = FakeCompilerOptions() + env.coredata.options[OptionKey('args', lang='c')] = FakeCompilerOptions() env.machines.host.cpu_family = 'x86_64' # Used on macOS inside find_library return env diff --git a/run_unittests.py b/run_unittests.py index 4b14a8732650..1ff54eb370eb 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -30,8 +30,6 @@ import io import operator import threading -import urllib.error -import urllib.request import zipfile import hashlib from itertools import chain @@ -61,7 +59,7 @@ quote_arg, relpath, is_linux, git, GIT ) from mesonbuild.environment import detect_ninja -from mesonbuild.mesonlib import MesonException, EnvironmentException +from mesonbuild.mesonlib import MesonException, EnvironmentException, OptionKey from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram import mesonbuild.dependencies.base from mesonbuild.build import Target, ConfigurationData @@ -265,7 +263,8 @@ def actual(f): def wrapped(*args, **kwargs): env = get_fake_env() cc = env.detect_c_compiler(MachineChoice.HOST) - if feature not in cc.base_options: + key = OptionKey(feature) + if key not in cc.base_options: raise unittest.SkipTest( '{} not available with {}'.format(feature, cc.id)) return f(*args, **kwargs) @@ -874,7 +873,7 @@ def create_static_lib(name): env = get_fake_env() compiler = env.detect_c_compiler(MachineChoice.HOST) env.coredata.compilers.host = {'c': compiler} - env.coredata.compiler_options.host['c']['link_args'] = FakeCompilerOptions() + env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions() p1 = Path(tmpdir) / '1' p2 = Path(tmpdir) / '2' p1.mkdir() @@ -1336,10 +1335,10 @@ def test_compiler_options_documented(self): cc = env.detect_c_compiler(MachineChoice.HOST) cpp = env.detect_cpp_compiler(MachineChoice.HOST) for comp in (cc, cpp): - for opt in comp.get_options().keys(): - self.assertIn(opt, md) + for opt in comp.get_options(): + self.assertIn(str(opt), md) for opt in comp.base_options: - self.assertIn(opt, md) + self.assertIn(str(opt), md) self.assertNotIn('b_unknown', md) @staticmethod @@ -1392,8 +1391,8 @@ def test_builtin_options_documented(self): found_entries |= options self.assertEqual(found_entries, set([ - *mesonbuild.coredata.BUILTIN_OPTIONS.keys(), - *mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE.keys() + *[str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS], + *[str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE], ])) # Check that `buildtype` table inside `Core options` matches how @@ -1414,10 +1413,10 @@ def test_builtin_options_documented(self): debug = False else: raise RuntimeError('Invalid debug value {!r} in row:\n{}'.format(debug, m.group())) - env.coredata.set_builtin_option('buildtype', buildtype) - self.assertEqual(env.coredata.builtins['buildtype'].value, buildtype) - self.assertEqual(env.coredata.builtins['optimization'].value, opt) - self.assertEqual(env.coredata.builtins['debug'].value, debug) + env.coredata.set_option(OptionKey('buildtype'), buildtype) + self.assertEqual(env.coredata.options[OptionKey('buildtype')].value, buildtype) + self.assertEqual(env.coredata.options[OptionKey('optimization')].value, opt) + self.assertEqual(env.coredata.options[OptionKey('debug')].value, debug) def test_cpu_families_documented(self): with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: @@ -1905,11 +1904,14 @@ def test_default_options_prefix(self): https://github.com/mesonbuild/meson/issues/1349 ''' testdir = os.path.join(self.common_test_dir, '88 default options') - self.init(testdir, default_args=False) + self.init(testdir, default_args=False, inprocess=True) opts = self.introspect('--buildoptions') for opt in opts: if opt['name'] == 'prefix': prefix = opt['value'] + break + else: + raise self.fail('Did not find option "prefix"') self.assertEqual(prefix, '/absoluteprefix') def test_do_conf_file_preserve_newlines(self): @@ -3632,10 +3634,10 @@ def test_guessed_linker_dependencies(self): def test_conflicting_d_dash_option(self): testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') - with self.assertRaises(subprocess.CalledProcessError) as e: + with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as e: self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar']) # Just to ensure that we caught the correct error - self.assertIn('passed as both', e.stderr) + self.assertIn('as both', e.stderr) def _test_same_option_twice(self, arg, args): testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') @@ -3684,57 +3686,63 @@ def test_command_line(self): # Verify default values when passing no args that affect the # configuration, and as a bonus, test that --profile-self works. - self.init(testdir, extra_args=['--profile-self']) + self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['default_library'].value, 'static') - self.assertEqual(obj.builtins['warning_level'].value, '1') - self.assertEqual(obj.user_options['set_sub_opt'].value, True) - self.assertEqual(obj.user_options['subp:subp_opt'].value, 'default3') + self.assertEqual(obj.options[OptionKey('default_library')].value, 'static') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') + self.assertEqual(obj.options[OptionKey('set_sub_opt')].value, True) + self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'default3') self.wipe() # warning_level is special, it's --warnlevel instead of --warning-level # for historical reasons - self.init(testdir, extra_args=['--warnlevel=2']) + self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '2') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') self.setconf('--warnlevel=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '3') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') self.wipe() # But when using -D syntax, it should be 'warning_level' - self.init(testdir, extra_args=['-Dwarning_level=2']) + self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '2') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') self.setconf('-Dwarning_level=3') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '3') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') self.wipe() # Mixing --option and -Doption is forbidden - with self.assertRaises(subprocess.CalledProcessError) as cm: + with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3']) - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('as both', cm.exception.output) + if isinstance(cm.exception, subprocess.CalledProcessError): + self.assertNotEqual(0, cm.exception.returncode) + self.assertIn('as both', cm.exception.output) + else: + self.assertIn('as both', str(cm.exception)) self.init(testdir) - with self.assertRaises(subprocess.CalledProcessError) as cm: + with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: self.setconf(['--warnlevel=1', '-Dwarning_level=3']) - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('as both', cm.exception.output) + if isinstance(cm.exception, subprocess.CalledProcessError): + self.assertNotEqual(0, cm.exception.returncode) + self.assertIn('as both', cm.exception.output) + else: + self.assertIn('as both', str(cm.exception)) self.wipe() # --default-library should override default value from project() - self.init(testdir, extra_args=['--default-library=both']) + self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['default_library'].value, 'both') + self.assertEqual(obj.options[OptionKey('default_library')].value, 'both') self.setconf('--default-library=shared') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['default_library'].value, 'shared') + self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') if self.backend is Backend.ninja: # reconfigure target works only with ninja backend self.build('reconfigure') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['default_library'].value, 'shared') + self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') self.wipe() # Should warn on unknown options @@ -3743,15 +3751,22 @@ def test_command_line(self): self.wipe() # Should fail on malformed option - with self.assertRaises(subprocess.CalledProcessError) as cm: + msg = "Option 'foo' must have a value separated by equals sign." + with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: self.init(testdir, extra_args=['-Dfoo']) - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output) + if isinstance(cm.exception, subprocess.CalledProcessError): + self.assertNotEqual(0, cm.exception.returncode) + self.assertIn(msg, cm.exception.output) + else: + self.assertIn(msg, str(cm.exception)) self.init(testdir) - with self.assertRaises(subprocess.CalledProcessError) as cm: + with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: self.setconf('-Dfoo') - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output) + if isinstance(cm.exception, subprocess.CalledProcessError): + self.assertNotEqual(0, cm.exception.returncode) + self.assertIn(msg, cm.exception.output) + else: + self.assertIn(msg, str(cm.exception)) self.wipe() # It is not an error to set wrong option for unknown subprojects or @@ -3760,24 +3775,24 @@ def test_command_line(self): self.wipe() # Test we can set subproject option - self.init(testdir, extra_args=['-Dsubp:subp_opt=foo']) + self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.user_options['subp:subp_opt'].value, 'foo') + self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'foo') self.wipe() # c_args value should be parsed with split_args - self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"']) + self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) + self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) self.setconf('-Dc_args="foo bar" one two') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.compiler_options.host['c']['args'].value, ['foo bar', 'one', 'two']) + self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) self.wipe() - self.init(testdir, extra_args=['-Dset_percent_opt=myoption%']) + self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.user_options['set_percent_opt'].value, 'myoption%') + self.assertEqual(obj.options[OptionKey('set_percent_opt')].value, 'myoption%') self.wipe() # Setting a 2nd time the same option should override the first value @@ -3785,21 +3800,22 @@ def test_command_line(self): self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar', '-Dbuildtype=plain', '-Dbuildtype=release', '-Db_sanitize=address', '-Db_sanitize=thread', - '-Dc_args=-Dfoo', '-Dc_args=-Dbar']) + '-Dc_args=-Dfoo', '-Dc_args=-Dbar', + '-Db_lundef=false', '--fatal-meson-warnings']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['bindir'].value, 'bar') - self.assertEqual(obj.builtins['buildtype'].value, 'release') - self.assertEqual(obj.base_options['b_sanitize'].value, 'thread') - self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dbar']) + self.assertEqual(obj.options[OptionKey('bindir')].value, 'bar') + self.assertEqual(obj.options[OptionKey('buildtype')].value, 'release') + self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'thread') + self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', '-Db_sanitize=thread', '-Db_sanitize=address', '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['bindir'].value, 'foo') - self.assertEqual(obj.builtins['buildtype'].value, 'plain') - self.assertEqual(obj.base_options['b_sanitize'].value, 'address') - self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dfoo']) + self.assertEqual(obj.options[OptionKey('bindir')].value, 'foo') + self.assertEqual(obj.options[OptionKey('buildtype')].value, 'plain') + self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'address') + self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo']) self.wipe() except KeyError: # Ignore KeyError, it happens on CI for compilers that does not @@ -3813,25 +3829,25 @@ def test_warning_level_0(self): # Verify default values when passing no args self.init(testdir) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '0') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') self.wipe() # verify we can override w/ --warnlevel self.init(testdir, extra_args=['--warnlevel=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '1') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') self.setconf('--warnlevel=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '0') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') self.wipe() # verify we can override w/ -Dwarning_level self.init(testdir, extra_args=['-Dwarning_level=1']) obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '1') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') self.setconf('-Dwarning_level=0') obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.builtins['warning_level'].value, '0') + self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') self.wipe() def test_feature_check_usage_subprojects(self): @@ -4238,7 +4254,8 @@ def test_introspect_buildoptions_without_configured_build(self): self.init(testdir, default_args=False) res_wb = self.introspect('--buildoptions') self.maxDiff = None - self.assertListEqual(res_nb, res_wb) + # XXX: These now generate in a different order, is that okay? + self.assertListEqual(sorted(res_nb, key=lambda x: x['name']), sorted(res_wb, key=lambda x: x['name'])) def test_meson_configure_from_source_does_not_crash(self): testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') @@ -4489,20 +4506,20 @@ def test_introspect_config_update(self): with open(introfile, 'r') as fp: res1 = json.load(fp) - self.setconf('-Dcpp_std=c++14') - self.setconf('-Dbuildtype=release') - - for idx, i in enumerate(res1): + for i in res1: if i['name'] == 'cpp_std': - res1[idx]['value'] = 'c++14' + i['value'] = 'c++14' if i['name'] == 'build.cpp_std': - res1[idx]['value'] = 'c++14' + i['value'] = 'c++14' if i['name'] == 'buildtype': - res1[idx]['value'] = 'release' + i['value'] = 'release' if i['name'] == 'optimization': - res1[idx]['value'] = '3' + i['value'] = '3' if i['name'] == 'debug': - res1[idx]['value'] = False + i['value'] = False + + self.setconf('-Dcpp_std=c++14') + self.setconf('-Dbuildtype=release') with open(introfile, 'r') as fp: res2 = json.load(fp) @@ -5758,7 +5775,7 @@ def test_qt5dependency_vscrt(self): # Verify that the `b_vscrt` option is available env = get_fake_env() cc = env.detect_c_compiler(MachineChoice.HOST) - if 'b_vscrt' not in cc.base_options: + if OptionKey('b_vscrt') not in cc.base_options: raise unittest.SkipTest('Compiler does not support setting the VS CRT') # Verify that qmake is for Qt5 if not shutil.which('qmake-qt5'): @@ -5784,7 +5801,7 @@ def test_compiler_checks_vscrt(self): # Verify that the `b_vscrt` option is available env = get_fake_env() cc = env.detect_c_compiler(MachineChoice.HOST) - if 'b_vscrt' not in cc.base_options: + if OptionKey('b_vscrt') not in cc.base_options: raise unittest.SkipTest('Compiler does not support setting the VS CRT') def sanitycheck_vscrt(vscrt): @@ -6285,7 +6302,7 @@ def test_compiler_check_flags_order(self): Oargs = [arg for arg in cmd if arg.startswith('-O')] self.assertEqual(Oargs, [Oflag, '-O0']) - def _test_stds_impl(self, testdir, compiler: 'Compiler', p: str) -> None: + def _test_stds_impl(self, testdir: str, compiler: 'Compiler') -> None: has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0')) @@ -6301,8 +6318,8 @@ def _test_stds_impl(self, testdir, compiler: 'Compiler', p: str) -> None: # Check that all the listed -std=xxx options for this compiler work just fine when used # https://en.wikipedia.org/wiki/Xcode#Latest_versions # https://www.gnu.org/software/gcc/projects/cxx-status.html - for v in compiler.get_options()['std'].choices: - lang_std = p + '_std' + key = OptionKey('std', lang=compiler.language) + for v in compiler.get_options()[key].choices: # we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly # thus, C++ first if '++17' in v and not has_cpp17: @@ -6316,8 +6333,7 @@ def _test_stds_impl(self, testdir, compiler: 'Compiler', p: str) -> None: continue elif '18' in v and not has_c18: continue - std_opt = '{}={}'.format(lang_std, v) - self.init(testdir, extra_args=['-D' + std_opt]) + self.init(testdir, extra_args=[f'-D{key!s}={v}']) cmd = self.get_compdb()[0]['command'] # c++03 and gnu++03 are not understood by ICC, don't try to look for them skiplist = frozenset([ @@ -6329,15 +6345,15 @@ def _test_stds_impl(self, testdir, compiler: 'Compiler', p: str) -> None: try: self.build() except Exception: - print('{} was {!r}'.format(lang_std, v)) + print(f'{key!s} was {v!r}') raise self.wipe() # Check that an invalid std option in CFLAGS/CPPFLAGS fails # Needed because by default ICC ignores invalid options cmd_std = '-std=FAIL' - if p == 'c': + if compiler.language == 'c': env_flag_name = 'CFLAGS' - elif p == 'cpp': + elif compiler.language == 'cpp': env_flag_name = 'CXXFLAGS' else: raise NotImplementedError('Language {} not defined.'.format(p)) @@ -6358,7 +6374,7 @@ def test_compiler_c_stds(self): testdir = os.path.join(self.common_test_dir, '1 trivial') env = get_fake_env(testdir, self.builddir, self.prefix) cc = env.detect_c_compiler(MachineChoice.HOST) - self._test_stds_impl(testdir, cc, 'c') + self._test_stds_impl(testdir, cc) def test_compiler_cpp_stds(self): ''' @@ -6368,7 +6384,7 @@ def test_compiler_cpp_stds(self): testdir = os.path.join(self.common_test_dir, '2 cpp') env = get_fake_env(testdir, self.builddir, self.prefix) cpp = env.detect_cpp_compiler(MachineChoice.HOST) - self._test_stds_impl(testdir, cpp, 'cpp') + self._test_stds_impl(testdir, cpp) def test_unity_subproj(self): testdir = os.path.join(self.common_test_dir, '43 subproject') @@ -6890,7 +6906,7 @@ def test_pkgconfig_relative_paths(self): self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc'))) env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({'pkg_config_path': pkg_dir}, subproject='') + env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') kwargs = {'required': True, 'silent': True} relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) self.assertTrue(relative_path_dep.found()) @@ -8442,9 +8458,13 @@ def test_builtin_options_subprojects_overrides_buildfiles(self): testcase = os.path.join(self.common_test_dir, '224 persubproject options') config = self.helper_create_native_file({'sub2:built-in options': {'default_library': 'shared'}}) - with self.assertRaises(subprocess.CalledProcessError) as cm: + with self.assertRaises((RuntimeError, subprocess.CalledProcessError)) as cm: self.init(testcase, extra_args=['--native-file', config]) - self.assertIn(cm.exception.stdout, 'Parent should override default_library') + if isinstance(cm, RuntimeError): + check = str(cm.exception) + else: + check = cm.exception.stdout + self.assertIn(check, 'Parent should override default_library') def test_builtin_options_subprojects_dont_inherits_parent_override(self): # If the buildfile says subproject(... default_library: shared), ensure that's overwritten @@ -9299,7 +9319,7 @@ def ran_in(s): out = self._subprojects_cmd(['foreach', '--types', 'git'] + dummy_cmd) self.assertEqual(ran_in(out), ['subprojects/sub_git']) -def _clang_at_least(compiler, minver: str, apple_minver: T.Optional[str]) -> bool: +def _clang_at_least(compiler: 'Compiler', minver: str, apple_minver: T.Optional[str]) -> bool: """ check that Clang compiler is at least a specified version, whether AppleClang or regular Clang