Also log the stdout data from a failed command. So far we expected error data on stderr but there are also commands which print error messages on stdout. It should us at least worth a debug message with this information
159 lines
5.3 KiB
Python
159 lines
5.3 KiB
Python
# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved.
|
|
#
|
|
# This file is part of kiwi.
|
|
#
|
|
# kiwi is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# kiwi is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
|
|
#
|
|
import select
|
|
import os
|
|
import subprocess
|
|
from collections import namedtuple
|
|
|
|
# project
|
|
from .exceptions import KiwiCommandError
|
|
|
|
|
|
class Command(object):
|
|
"""
|
|
Implements command invocation
|
|
"""
|
|
@classmethod
|
|
def run(self, command, custom_env=None, raise_on_error=True):
|
|
"""
|
|
Execute a program and block the caller. The return value
|
|
is a hash containing the stdout, stderr and return code
|
|
information. Unless raise_on_error is set to false an
|
|
exception is thrown if the command exits with an error
|
|
code not equal to zero
|
|
"""
|
|
from .logger import log
|
|
log.debug('EXEC: [%s]', ' '.join(command))
|
|
environment = os.environ
|
|
if custom_env:
|
|
environment = custom_env
|
|
try:
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=environment
|
|
)
|
|
except Exception as e:
|
|
raise KiwiCommandError(
|
|
'%s: %s: %s' % (command[0], type(e).__name__, format(e))
|
|
)
|
|
output, error = process.communicate()
|
|
if process.returncode != 0 and not error:
|
|
error = b'(no output on stderr)'
|
|
if process.returncode != 0 and not output:
|
|
output = b'(no output on stdout)'
|
|
if process.returncode != 0 and raise_on_error:
|
|
log.debug(
|
|
'EXEC: Failed with stderr: %s, stdout: %s',
|
|
error.decode(), output.decode()
|
|
)
|
|
raise KiwiCommandError(
|
|
'%s: stderr: %s, stdout: %s' % (
|
|
command[0], error.decode(), output.decode()
|
|
)
|
|
)
|
|
command = namedtuple(
|
|
'command', ['output', 'error', 'returncode']
|
|
)
|
|
return command(
|
|
output=output.decode(),
|
|
error=error.decode(),
|
|
returncode=process.returncode
|
|
)
|
|
|
|
@classmethod
|
|
def call(self, command, custom_env=None):
|
|
"""
|
|
Execute a program and return an io file handle pair back.
|
|
stdout and stderr are both on different channels. The caller
|
|
must read from the output file handles in order to actually
|
|
run the command. This can be done as follows:
|
|
|
|
cmd = Command.call(...)
|
|
|
|
errors = ''
|
|
while cmd.process.poll() is None:
|
|
while cmd.output_available():
|
|
data = cmd.output.readline()
|
|
if not data:
|
|
break
|
|
print data
|
|
while cmd.error_available():
|
|
error = cmd.error.readline()
|
|
if not error:
|
|
break
|
|
errors += error
|
|
|
|
if cmd.process.returncode != 0:
|
|
print 'something failed: %s' % errors
|
|
"""
|
|
from .logger import log
|
|
log.debug('EXEC: [%s]', ' '.join(command))
|
|
environment = os.environ
|
|
if custom_env:
|
|
environment = custom_env
|
|
try:
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
env=environment
|
|
)
|
|
except Exception as e:
|
|
raise KiwiCommandError(
|
|
'%s: %s' % (type(e).__name__, format(e))
|
|
)
|
|
|
|
def output_available():
|
|
def __select():
|
|
descriptor_lists = select.select(
|
|
[process.stdout], [], [process.stdout], 1e-4
|
|
)
|
|
readable = descriptor_lists[0]
|
|
exceptional = descriptor_lists[2]
|
|
if readable and not exceptional:
|
|
return True
|
|
return __select
|
|
|
|
def error_available():
|
|
def __select():
|
|
descriptor_lists = select.select(
|
|
[process.stderr], [], [process.stderr], 1e-4
|
|
)
|
|
readable = descriptor_lists[0]
|
|
exceptional = descriptor_lists[2]
|
|
if readable and not exceptional:
|
|
return True
|
|
return __select
|
|
|
|
command = namedtuple(
|
|
'command', [
|
|
'output', 'output_available',
|
|
'error', 'error_available',
|
|
'process'
|
|
]
|
|
)
|
|
return command(
|
|
output=process.stdout,
|
|
output_available=output_available(),
|
|
error=process.stderr,
|
|
error_available=error_available(),
|
|
process=process
|
|
)
|