diff options
-rw-r--r-- | docs/markdown/Reference-manual.md | 8 | ||||
-rw-r--r-- | docs/markdown/snippets/find_program_version.md | 9 | ||||
-rw-r--r-- | mesonbuild/interpreter.py | 38 | ||||
-rw-r--r-- | test cases/common/29 find program/meson.build | 9 | ||||
-rw-r--r-- | test cases/common/29 find program/print-version-with-prefix.py | 8 | ||||
-rw-r--r-- | test cases/common/29 find program/print-version.py | 8 |
6 files changed, 77 insertions, 3 deletions
diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 328cc09..3a21cef 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -671,6 +671,14 @@ Keyword arguments are the following: [disabler object](#disabler-object) instead of a not-found object. *Since 0.49.0* +- `version` *(since 0.52.0)* Specifies the required version, see + [`dependency()`](#dependency) for argument format. The version of the program + is determined by running `program_name --version` command. If stdout is empty + it fallbacks to stderr. If the output contains more text than simply a version + number, only the first occurence of numbers separated by dots is kept. + If the output is more complicated than that, the version checking will have to + be done manually using [`run_command()`](#run_command). + Meson will also autodetect scripts with a shebang line and run them with the executable/interpreter specified in it both on Windows (because the command invocator will reject the command otherwise) and diff --git a/docs/markdown/snippets/find_program_version.md b/docs/markdown/snippets/find_program_version.md new file mode 100644 index 0000000..04424d7 --- /dev/null +++ b/docs/markdown/snippets/find_program_version.md @@ -0,0 +1,9 @@ +## Version check in `find_program()` + +A new `version` keyword argument has been added to `find_program` to specify +the required version. See [`dependency()`](#dependency) for argument format. +The version of the program is determined by running `program_name --version` +command. If stdout is empty it fallbacks to stderr. If the output contains more +text than simply a version number, only the first occurence of numbers separated +by dots is kept. If the output is more complicated than that, the version +checking will have to be done manually using [`run_command()`](#run_command). diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index e22a168..4b4d776 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -489,6 +489,7 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): ObjectHolder.__init__(self, ep) self.methods.update({'found': self.found_method, 'path': self.path_method}) + self.cached_version = None @noPosargs @permittedKwargs({}) @@ -509,6 +510,24 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder): def get_name(self): return self.held_object.get_name() + def get_version(self, interpreter): + if not self.cached_version: + raw_cmd = self.get_command() + ['--version'] + cmd = [self, '--version'] + res = interpreter.run_command_impl(interpreter.current_node, cmd, {}, True) + if res.returncode != 0: + m = 'Running {!r} failed' + raise InterpreterException(m.format(raw_cmd)) + output = res.stdout.strip() + if not output: + output = res.stderr.strip() + match = re.search(r'([0-9\.]+)', output) + if not match: + m = 'Could not find a version number in output of {!r}' + raise InterpreterException(m.format(raw_cmd)) + self.cached_version = match.group(1) + return self.cached_version + class ExternalLibraryHolder(InterpreterObject, ObjectHolder): def __init__(self, el, pv): InterpreterObject.__init__(self) @@ -1993,7 +2012,7 @@ permitted_kwargs = {'add_global_arguments': {'language', 'native'}, 'version', }, 'executable': build.known_exe_kwargs, - 'find_program': {'required', 'native'}, + 'find_program': {'required', 'native', 'version'}, 'generator': {'arguments', 'output', 'depends', @@ -2879,7 +2898,7 @@ external dependencies (including libraries) must go to "dependencies".''') # TODO update modules to always pass `for_machine`. It is bad-form to assume # the host machine. - def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, required=True, silent=True): + def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, required=True, silent=True, wanted=''): if not isinstance(args, list): args = [args] @@ -2897,8 +2916,20 @@ external dependencies (including libraries) must go to "dependencies".''') return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) # Only store successful lookups self.store_name_lookups(args) + if wanted: + version = progobj.get_version(self) + is_found, not_found, found = mesonlib.version_compare_many(version, wanted) + if not is_found: + mlog.log('Program', mlog.bold(progobj.get_name()), 'found:', mlog.red('NO'), + 'found {!r} but need:'.format(version), + ', '.join(["'{}'".format(e) for e in not_found])) + if required: + m = 'Invalid version of program, need {!r} {!r} found {!r}.' + raise InvalidArguments(m.format(progobj.get_name(), not_found, version)) + return ExternalProgramHolder(dependencies.NonExistingExternalProgram()) return progobj + @FeatureNewKwargs('find_program', '0.52.0', ['version']) @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) @disablerIfNotFound @permittedKwargs(permitted_kwargs['find_program']) @@ -2913,8 +2944,9 @@ external dependencies (including libraries) must go to "dependencies".''') if not isinstance(required, bool): raise InvalidArguments('"required" argument must be a boolean.') + wanted = mesonlib.stringlistify(kwargs.get('version', [])) for_machine = self.machine_from_native_kwarg(kwargs) - return self.find_program_impl(args, for_machine, required=required, silent=False) + return self.find_program_impl(args, for_machine, required=required, silent=False, wanted=wanted) def func_find_library(self, node, args, kwargs): raise InvalidCode('find_library() is removed, use meson.get_compiler(\'name\').find_library() instead.\n' diff --git a/test cases/common/29 find program/meson.build b/test cases/common/29 find program/meson.build index ba5386d..983b7b4 100644 --- a/test cases/common/29 find program/meson.build +++ b/test cases/common/29 find program/meson.build @@ -18,3 +18,12 @@ else e = executable('prog', generated) test('external exe', e) endif + +prog = find_program('print-version.py', version : '>=2.0', required : false) +assert(not prog.found(), 'Version should be too old') + +prog = find_program('print-version.py', version : '>=1.0') +assert(prog.found(), 'Program version should match') + +prog = find_program('print-version-with-prefix.py', version : '>=1.0') +assert(prog.found(), 'Program version should match') diff --git a/test cases/common/29 find program/print-version-with-prefix.py b/test cases/common/29 find program/print-version-with-prefix.py new file mode 100644 index 0000000..520e0ba --- /dev/null +++ b/test cases/common/29 find program/print-version-with-prefix.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import sys + +if len(sys.argv) != 2 or sys.argv[1] != '--version': + exit(1) + +print('Version: 1.0') diff --git a/test cases/common/29 find program/print-version.py b/test cases/common/29 find program/print-version.py new file mode 100644 index 0000000..4a78e5b --- /dev/null +++ b/test cases/common/29 find program/print-version.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import sys + +if len(sys.argv) != 2 or sys.argv[1] != '--version': + exit(1) + +print('1.0') |