From fa663f35358221261acfdbb6a6f98e990f5a198d Mon Sep 17 00:00:00 2001 From: Ben Corby Date: Wed, 18 Dec 2024 11:31:59 +1100 Subject: [PATCH] Fix spurious error message Make it clear that reading meson.options or a meson -Dopt=value parameter by using get_option() as a parameter to project() isn't allowed. --- mesonbuild/interpreter/interpreter.py | 75 +++++++++++++------ .../meson.build | 2 + .../meson.options | 1 + .../test.json | 7 ++ 4 files changed, 62 insertions(+), 23 deletions(-) create mode 100644 test cases/common/281 get_option in project function/meson.build create mode 100644 test cases/common/281 get_option in project function/meson.options create mode 100644 test cases/common/281 get_option in project function/test.json diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d717485e8a1e..7728ff2a192e 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1078,8 +1078,6 @@ def get_option_internal(self, optname: str) -> options.UserOption: except KeyError: pass - raise InterpreterException(f'Tried to access unknown option {optname!r}.') - @typed_pos_args('get_option', str) @noKwargs def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], @@ -1096,12 +1094,34 @@ def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], opt = self.get_option_internal(optname) if isinstance(opt, options.UserFeatureOption): opt.name = optname - return opt + _option_value = opt elif isinstance(opt, options.UserOption): if isinstance(opt.value, str): - return P_OBJ.OptionString(opt.value, f'{{{optname}}}') - return opt.value - return opt + _option_value = P_OBJ.OptionString(opt.value, f'{{{optname}}}') + else: + _option_value = opt.value + else: + _option_value = opt + + self.load_options(nodes, args) + opt = self.get_option_internal(optname) + if isinstance(opt, options.UserFeatureOption): + opt.name = optname + option_value = opt + elif isinstance(opt, options.UserOption): + if isinstance(opt.value, str): + option_value = P_OBJ.OptionString(opt.value, f'{{{optname}}}') + else: + option_value = opt.value + else: + option_value = opt + + if option_value is None and _option_value is None: + raise InterpreterException(f'Tried to access unknown option {optname!r}.') + elif not option_value == _option_value: + if not hasattr(self, 'prohibited_load_options_used'): + self.prohibited_load_options_used = {'option': optname, 'value': option_value} + return option_value @typed_pos_args('configuration_data', optargs=[dict]) @noKwargs @@ -1148,6 +1168,23 @@ def set_backend(self) -> None: options = {k: v for k, v in self.environment.options.items() if self.environment.coredata.optstore.is_backend_option(k)} self.coredata.set_options(options) + def load_options(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str]]) -> None: + option_file = os.path.join(self.source_root, self.subdir, 'meson.options') + old_option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') + if not os.path.exists(option_file): + option_file = old_option_file + if os.path.exists(option_file): + with open(option_file, 'rb') as f: + # We want fast not cryptographically secure, this is just to + # see if the option file has changed + self.coredata.options_files[self.subproject] = (option_file, hashlib.sha1(f.read()).hexdigest()) + oi = optinterpreter.OptionInterpreter(self.environment.coredata.optstore, self.subproject) + oi.process(option_file) + self.coredata.update_project_options(oi.options, self.subproject) + self.add_build_def_file(option_file) + else: + self.coredata.options_files[self.subproject] = None + @typed_pos_args('project', str, varargs=str) @typed_kwargs( 'project', @@ -1169,6 +1206,9 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str if ':' in proj_name: raise InvalidArguments(f"Project name {proj_name!r} must not contain ':'") + # Read in the options file. + self.load_options(node, args) + # This needs to be evaluated as early as possible, as meson uses this # for things like deprecation testing. if kwargs['meson_version']: @@ -1176,12 +1216,9 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str else: mesonlib.project_meson_versions[self.subproject] = mesonlib.NoProjectVersion() - # Load "meson.options" before "meson_options.txt", and produce a warning if - # it is being used with an old version. I have added check that if both - # exist the warning isn't raised + # Warn about use of meson.options / meson_options.txt. option_file = os.path.join(self.source_root, self.subdir, 'meson.options') old_option_file = os.path.join(self.source_root, self.subdir, 'meson_options.txt') - if os.path.exists(option_file): if os.path.exists(old_option_file): if os.path.samefile(option_file, old_option_file): @@ -1190,19 +1227,11 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str raise MesonException("meson.options and meson_options.txt both exist, but are not the same file.") else: FeatureNew.single_use('meson.options file', '1.1', self.subproject, 'Use meson_options.txt instead') - else: - option_file = old_option_file - if os.path.exists(option_file): - with open(option_file, 'rb') as f: - # We want fast not cryptographically secure, this is just to - # see if the option file has changed - self.coredata.options_files[self.subproject] = (option_file, hashlib.sha1(f.read()).hexdigest()) - oi = optinterpreter.OptionInterpreter(self.environment.coredata.optstore, self.subproject) - oi.process(option_file) - self.coredata.update_project_options(oi.options, self.subproject) - self.add_build_def_file(option_file) - else: - self.coredata.options_files[self.subproject] = None + + # Using get_option() in project() isn't allowed. + # For the reason see https://github.com/mesonbuild/meson/pull/14016 + if hasattr(self, 'prohibited_load_options_used'): + FeatureBroken.single_use('Reading meson.options or a meson -Dopt=value parameter by using get_option() as a parameter to project() isn\'t allowed', '1.7', self.subproject, 'Instead use something similar to run_command(\'./get_value.py\', check: true).stdout().strip()') if self.subproject: self.project_default_options = {k.evolve(subproject=self.subproject): v diff --git a/test cases/common/281 get_option in project function/meson.build b/test cases/common/281 get_option in project function/meson.build new file mode 100644 index 000000000000..73f8e7a72242 --- /dev/null +++ b/test cases/common/281 get_option in project function/meson.build @@ -0,0 +1,2 @@ +# Project +project('test', 'c', version: '1.0' + get_option('append_to_version')) diff --git a/test cases/common/281 get_option in project function/meson.options b/test cases/common/281 get_option in project function/meson.options new file mode 100644 index 000000000000..9fd0f5328d8f --- /dev/null +++ b/test cases/common/281 get_option in project function/meson.options @@ -0,0 +1 @@ +option('append_to_version', type : 'string', value : '.0001', description : 'append to version') diff --git a/test cases/common/281 get_option in project function/test.json b/test cases/common/281 get_option in project function/test.json new file mode 100644 index 000000000000..101fbcd8ffe1 --- /dev/null +++ b/test cases/common/281 get_option in project function/test.json @@ -0,0 +1,7 @@ +{ + "stdout": [ + { + "line": "DEPRECATION: Project uses feature that was always broken, and is now deprecated since '1.7': Reading meson.options or a meson -Dopt=value parameter by using get_option() as a parameter to project() isn't allowed. Instead use something similar to run_command('./get_value.py', check: true).stdout().strip()" + } + ] +}