diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 7bba4fa69..4751af9e8 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -131,6 +131,8 @@ if T.TYPE_CHECKING: BuildTargetSource = T.Union[mesonlib.FileOrString, build.GeneratedTypes, build.StructuredSources] + ProgramVersionFunc = T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str] + def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) -> T.Optional[str]: if isinstance(value, list): @@ -1621,46 +1623,23 @@ class Interpreter(InterpreterBase, HoldableObject): required: bool = True, silent: bool = True, wanted: T.Union[str, T.List[str]] = '', search_dirs: T.Optional[T.List[str]] = None, - version_func: T.Optional[T.Callable[[T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']], str]] = None + version_func: T.Optional[ProgramVersionFunc] = None ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: args = mesonlib.listify(args) extra_info: T.List[mlog.TV_Loggable] = [] - progobj = self.program_lookup(args, for_machine, default_options, required, search_dirs, extra_info) - if progobj is None: + progobj = self.program_lookup(args, for_machine, default_options, required, search_dirs, wanted, version_func, extra_info) + if progobj is None or not self.check_program_version(progobj, wanted, version_func, extra_info): progobj = self.notfound_program(args) if isinstance(progobj, ExternalProgram) and not progobj.found(): if not silent: - mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO')) + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), *extra_info) if required: m = 'Program {!r} not found or not executable' raise InterpreterException(m.format(progobj.get_name())) return progobj - if wanted: - if version_func: - version = version_func(progobj) - elif isinstance(progobj, build.Executable): - if progobj.subproject: - interp = self.subprojects[progobj.subproject].held_object - else: - interp = self - assert isinstance(interp, Interpreter) - version = interp.project_version - else: - version = progobj.get_version(self) - is_found, not_found, _ = mesonlib.version_compare_many(version, wanted) - if not is_found: - mlog.log('Program', mlog.bold(progobj.name), 'found:', mlog.red('NO'), - 'found', mlog.normal_cyan(version), 'but need:', - mlog.bold(', '.join([f"'{e}'" for e in not_found])), *extra_info) - if required: - m = 'Invalid version of program, need {!r} {!r} found {!r}.' - raise InterpreterException(m.format(progobj.name, not_found, version)) - return self.notfound_program(args) - extra_info.insert(0, mlog.normal_cyan(version)) - # Only store successful lookups self.store_name_lookups(args) if not silent: @@ -1671,7 +1650,11 @@ class Interpreter(InterpreterBase, HoldableObject): def program_lookup(self, args: T.List[mesonlib.FileOrString], for_machine: MachineChoice, default_options: T.Optional[T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]]], - required: bool, search_dirs: T.List[str], extra_info: T.List[mlog.TV_Loggable] + required: bool, + search_dirs: T.List[str], + wanted: T.Union[str, T.List[str]], + version_func: T.Optional[ProgramVersionFunc], + extra_info: T.List[mlog.TV_Loggable] ) -> T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]]: progobj = self.program_from_overrides(args, extra_info) if progobj: @@ -1694,11 +1677,42 @@ class Interpreter(InterpreterBase, HoldableObject): if progobj is None and args[0].endswith('python3'): prog = ExternalProgram('python3', mesonlib.python_command, silent=True) progobj = prog if prog.found() else None + + if progobj and not self.check_program_version(progobj, wanted, version_func, extra_info): + progobj = None + if progobj is None and fallback and required: + progobj = self.notfound_program(args) + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), *extra_info) + extra_info.clear() progobj = self.find_program_fallback(fallback, args, default_options, required, extra_info) return progobj + def check_program_version(self, progobj: T.Union[ExternalProgram, build.Executable, OverrideProgram], + wanted: T.Union[str, T.List[str]], + version_func: T.Optional[ProgramVersionFunc], + extra_info: T.List[mlog.TV_Loggable]) -> bool: + if wanted: + if version_func: + version = version_func(progobj) + elif isinstance(progobj, build.Executable): + if progobj.subproject: + interp = self.subprojects[progobj.subproject].held_object + else: + interp = self + assert isinstance(interp, Interpreter) + version = interp.project_version + else: + version = progobj.get_version(self) + is_found, not_found, _ = mesonlib.version_compare_many(version, wanted) + if not is_found: + extra_info[:0] = ['found', mlog.normal_cyan(version), 'but need:', + mlog.bold(', '.join([f"'{e}'" for e in not_found]))] + return False + extra_info.insert(0, mlog.normal_cyan(version)) + return True + def find_program_fallback(self, fallback: str, args: T.List[mesonlib.FileOrString], default_options: T.Dict[OptionKey, T.Union[str, int, bool, T.List[str]]], required: bool, extra_info: T.List[mlog.TV_Loggable] diff --git a/mesonbuild/modules/__init__.py b/mesonbuild/modules/__init__.py index b46b30077..57c169d0b 100644 --- a/mesonbuild/modules/__init__.py +++ b/mesonbuild/modules/__init__.py @@ -26,6 +26,7 @@ from ..programs import ExternalProgram if T.TYPE_CHECKING: from ..interpreter import Interpreter + from ..interpreter.interpreter import ProgramVersionFunc from ..interpreter.interpreterobjects import MachineHolder from ..interpreterbase import TYPE_var, TYPE_kwargs from ..programs import OverrideProgram @@ -86,7 +87,7 @@ class ModuleState: def find_program(self, prog: T.Union[mesonlib.FileOrString, T.List[mesonlib.FileOrString]], required: bool = True, - version_func: T.Optional[T.Callable[[T.Union[ExternalProgram, build.Executable, OverrideProgram]], str]] = None, + version_func: T.Optional[ProgramVersionFunc] = None, wanted: T.Optional[str] = None, silent: bool = False, for_machine: MachineChoice = MachineChoice.HOST) -> T.Union[ExternalProgram, build.Executable, OverrideProgram]: if not isinstance(prog, list): diff --git a/test cases/common/182 find override/meson.build b/test cases/common/182 find override/meson.build index f21700140..edb16873f 100644 --- a/test cases/common/182 find override/meson.build +++ b/test cases/common/182 find override/meson.build @@ -23,3 +23,9 @@ six_prog = find_program('six_meson_exe') assert(six_prog.found()) assert(six_prog.full_path() != '') assert(six_prog.full_path() == six_prog.path()) + +# We have prog-version.py in current directory, but it's version 1.0. +# This needs to use fallback for "prog-version" name which will be version 2.0. +prog = find_program('prog-version.py', 'prog-version', version: '>= 2.0') +assert(prog.found()) +assert(prog.version() == '2.0') diff --git a/test cases/common/182 find override/prog-version.py b/test cases/common/182 find override/prog-version.py new file mode 100755 index 000000000..c00dd99df --- /dev/null +++ b/test cases/common/182 find override/prog-version.py @@ -0,0 +1,3 @@ +#! /usr/bin/env python3 + +print('1.0') diff --git a/test cases/common/182 find override/subprojects/sub2.wrap b/test cases/common/182 find override/subprojects/sub2.wrap new file mode 100644 index 000000000..035629fa8 --- /dev/null +++ b/test cases/common/182 find override/subprojects/sub2.wrap @@ -0,0 +1,5 @@ +[wrap-file] +directory = sub2 + +[provide] +program_names = prog-version diff --git a/test cases/common/182 find override/subprojects/sub2/meson.build b/test cases/common/182 find override/subprojects/sub2/meson.build new file mode 100644 index 000000000..f542073c5 --- /dev/null +++ b/test cases/common/182 find override/subprojects/sub2/meson.build @@ -0,0 +1,4 @@ +project('sub2') + +prog = find_program('prog-version.py') +meson.override_find_program('prog-version', prog) diff --git a/test cases/common/182 find override/subprojects/sub2/prog-version.py b/test cases/common/182 find override/subprojects/sub2/prog-version.py new file mode 100755 index 000000000..78401bb20 --- /dev/null +++ b/test cases/common/182 find override/subprojects/sub2/prog-version.py @@ -0,0 +1,3 @@ +#! /usr/bin/env python3 + +print('2.0')