run_command: Add new kwarg 'capture'

capture: false means we won't try to read the stdout at all.

Closes https://github.com/mesonbuild/meson/issues/3364
This commit is contained in:
Nirbheek Chauhan 2018-04-15 14:44:41 +05:30 committed by Nirbheek Chauhan
parent cbe18e01e4
commit c9a89027ac
4 changed files with 46 additions and 19 deletions

View File

@ -1,4 +1,9 @@
## New 'check' keyword argument for the run_command function
## New keyword arguments: 'check' and 'capture' for run_command()
If `check` is `true`, then the configuration will fail if the command returns a
If `check:` is `true`, then the configuration will fail if the command returns a
non-zero exit status. The default value is `false` for compatibility reasons.
`run_command()` used to always capture the output and stored it for use in
build files. However, sometimes the stdout is in a binary format which is meant
to be discarded. For that case, you can now set the `capture:` keyword argument
to `false`.

View File

@ -33,6 +33,7 @@ from .modules import ModuleReturnValue
import os, sys, shutil, uuid
import re, shlex
import subprocess
from collections import namedtuple
from pathlib import PurePath
@ -144,10 +145,11 @@ class TryRunResultHolder(InterpreterObject):
class RunProcess(InterpreterObject):
def __init__(self, cmd, args, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False):
def __init__(self, cmd, args, source_dir, build_dir, subdir, mesonintrospect, in_builddir=False, check=False, capture=True):
super().__init__()
if not isinstance(cmd, ExternalProgram):
raise AssertionError('BUG: RunProcess must be passed an ExternalProgram')
self.capture = capture
pc, self.stdout, self.stderr = self.run_command(cmd, args, source_dir, build_dir, subdir, mesonintrospect, in_builddir, check)
self.returncode = pc.returncode
self.methods.update({'returncode': self.returncode_method,
@ -168,12 +170,17 @@ class RunProcess(InterpreterObject):
cwd = os.path.join(source_dir, subdir)
child_env = os.environ.copy()
child_env.update(env)
stdout = subprocess.PIPE if self.capture else subprocess.DEVNULL
mlog.debug('Running command:', ' '.join(command_array))
try:
p, o, e = Popen_safe(command_array, env=child_env, cwd=cwd)
mlog.debug('--- stdout----')
mlog.debug(o)
mlog.debug('----stderr----')
p, o, e = Popen_safe(command_array, stdout=stdout, env=child_env, cwd=cwd)
if self.capture:
mlog.debug('--- stdout ---')
mlog.debug(o)
else:
o = ''
mlog.debug('--- stdout disabled ---')
mlog.debug('--- stderr ---')
mlog.debug(e)
mlog.debug('')
@ -1796,7 +1803,7 @@ permitted_kwargs = {'add_global_arguments': {'language'},
'install_subdir': {'exclude_files', 'exclude_directories', 'install_dir', 'install_mode', 'strip_directory'},
'jar': build.known_jar_kwargs,
'project': {'version', 'meson_version', 'default_options', 'license', 'subproject_dir'},
'run_command': {'check'},
'run_command': {'check', 'capture'},
'run_target': {'command', 'depends'},
'shared_library': build.known_shlib_kwargs,
'shared_module': build.known_shmod_kwargs,
@ -2080,7 +2087,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if not isinstance(actual, wanted):
raise InvalidArguments('Incorrect argument type.')
@FeatureNewKwargs('run_command', '0.47.0', ['check'])
@FeatureNewKwargs('run_command', '0.47.0', ['check', 'capture'])
@permittedKwargs(permitted_kwargs['run_command'])
def func_run_command(self, node, args, kwargs):
return self.run_command_impl(node, args, kwargs)
@ -2090,6 +2097,7 @@ external dependencies (including libraries) must go to "dependencies".''')
raise InterpreterException('Not enough arguments')
cmd = args[0]
cargs = args[1:]
capture = kwargs.get('capture', True)
srcdir = self.environment.get_source_dir()
builddir = self.environment.get_build_dir()
@ -2149,7 +2157,8 @@ external dependencies (including libraries) must go to "dependencies".''')
if a not in self.build_def_files:
self.build_def_files.append(a)
return RunProcess(cmd, expanded_args, srcdir, builddir, self.subdir,
self.environment.get_build_command() + ['introspect'], in_builddir, check)
self.environment.get_build_command() + ['introspect'],
in_builddir=in_builddir, check=check, capture=capture)
@stringArgs
def func_gettext(self, nodes, args, kwargs):

View File

@ -796,22 +796,19 @@ def expand_arguments(args):
return None
return expended_args
def Popen_safe(args, write=None, stderr=subprocess.PIPE, **kwargs):
def Popen_safe(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
import locale
encoding = locale.getpreferredencoding()
if sys.version_info < (3, 6) or not sys.stdout.encoding or encoding.upper() != 'UTF-8':
return Popen_safe_legacy(args, write=write, stderr=stderr, **kwargs)
p = subprocess.Popen(args, universal_newlines=True,
close_fds=False,
stdout=subprocess.PIPE,
stderr=stderr, **kwargs)
return Popen_safe_legacy(args, write=write, stdout=stdout, stderr=stderr, **kwargs)
p = subprocess.Popen(args, universal_newlines=True, close_fds=False,
stdout=stdout, stderr=stderr, **kwargs)
o, e = p.communicate(write)
return p, o, e
def Popen_safe_legacy(args, write=None, stderr=subprocess.PIPE, **kwargs):
def Popen_safe_legacy(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs):
p = subprocess.Popen(args, universal_newlines=False,
stdout=subprocess.PIPE,
stderr=stderr, **kwargs)
stdout=stdout, stderr=stderr, **kwargs)
if write is not None:
write = write.encode('utf-8')
o, e = p.communicate(write)

View File

@ -55,3 +55,19 @@ if c.returncode() != 0
error('Using files() in argument failed.')
endif
py3 = import('python3').find_python()
ret = run_command(py3, '-c', 'print("some output")')
assert(ret.returncode() == 0, 'failed to run python3: ' + ret.stderr())
assert(ret.stdout() == 'some output\n', 'failed to run python3')
ret = run_command(py3, '-c', 'print("some output")', capture : false)
assert(ret.returncode() == 0, 'failed to run python3: ' + ret.stderr())
assert(ret.stdout() == '', 'stdout is "@0@" instead of empty'.format(ret.stdout()))
dd = find_program('dd', required : false)
if dd.found()
ret = run_command(dd, 'if=/dev/urandom', 'bs=10', 'count=1', capture: false)
assert(ret.returncode() == 0, 'failed to run dd: ' + ret.stderr())
assert(ret.stdout() == '', 'stdout is "@0@" instead of empty'.format(ret.stdout()))
endif