aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Reference-manual.md8
-rw-r--r--docs/markdown/snippets/find_program_version.md9
-rw-r--r--mesonbuild/interpreter.py38
-rw-r--r--test cases/common/29 find program/meson.build9
-rw-r--r--test cases/common/29 find program/print-version-with-prefix.py8
-rw-r--r--test cases/common/29 find program/print-version.py8
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')