Don't use sys.exit() in build_utils.CheckCallDie().

Calling sys.exit() in a thread spawned by
multiprocessing.pool.ThreadPool causes an infinite hang that even SIGINT
can't fix. This is bad. Instead of calling sys.exit(), raise an
exception, crafted so that the important information (the failed
command's stdout and stderr) comes after the stacktrace and the copyable
command.

This also renames CheckCallDie() to CheckOutput() and changes it to
print stderr but not stdout by default. Suppressing stderr has hid bugs
in the past (e.g. aapt crunch failures), and hiding stdout is what we
usually want anyway.

R=cjhopman@chromium.org

Review URL: https://codereview.chromium.org/106923002

git-svn-id: http://src.chromium.org/svn/trunk/src/build@239276 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
This commit is contained in:
newt@chromium.org 2013-12-06 23:24:37 +00:00
Родитель 1ee17157e6
Коммит e0e8e6eb61
14 изменённых файлов: 54 добавлений и 52 удалений

Просмотреть файл

@ -13,12 +13,20 @@ output up until the BUILD SUCCESSFUL line.
"""
import sys
import traceback
from util import build_utils
def main(argv):
stdout = build_utils.CheckCallDie(['ant'] + argv[1:], suppress_output=True)
try:
stdout = build_utils.CheckOutput(['ant'] + argv[1:])
except build_utils.CalledProcessError as e:
traceback.print_exc()
if '-quiet' in e.args:
sys.stderr.write('Tip: run the ant command above without the -quiet flag '
'to see more details on the error\n')
sys.exit(1)
stdout = stdout.strip().split('\n')
for line in stdout:
if line.strip() == 'BUILD SUCCESSFUL':

Просмотреть файл

@ -23,10 +23,9 @@ def CreateStandaloneApk(options):
intermediate_path = intermediate_file.name
shutil.copy(options.input_apk_path, intermediate_path)
apk_path_abs = os.path.abspath(intermediate_path)
build_utils.CheckCallDie(
build_utils.CheckOutput(
['zip', '-r', '-1', apk_path_abs, 'lib'],
cwd=options.libraries_top_dir,
suppress_output=True)
cwd=options.libraries_top_dir)
shutil.copy(intermediate_path, options.output_apk_path)
input_paths = [options.input_apk_path, options.libraries_top_dir]

Просмотреть файл

@ -22,7 +22,7 @@ def DoDex(options, paths):
record_path = '%s.md5.stamp' % options.dex_path
md5_check.CallAndRecordIfStale(
lambda: build_utils.CheckCallDie(dex_cmd, suppress_output=True),
lambda: build_utils.CheckOutput(dex_cmd, print_stderr=False),
record_path=record_path,
input_paths=paths,
input_strings=dex_cmd)

Просмотреть файл

@ -161,7 +161,7 @@ def _RunInstrumentCommand(command, options, args, option_parser):
'-d', temp_dir,
'-out', coverage_file,
'-m', 'fullcopy']
build_utils.CheckCallDie(cmd, suppress_output=True)
build_utils.CheckOutput(cmd)
if command == 'instrument_jar':
for jar in os.listdir(os.path.join(temp_dir, 'lib')):

Просмотреть файл

@ -26,7 +26,7 @@ def SignApk(keystore_path, unsigned_path, signed_path):
signed_path,
'chromiumdebugkey',
]
build_utils.CheckCallDie(sign_cmd)
build_utils.CheckOutput(sign_cmd)
def AlignApk(android_sdk_root, unaligned_path, final_path):
@ -36,7 +36,7 @@ def AlignApk(android_sdk_root, unaligned_path, final_path):
unaligned_path,
final_path,
]
build_utils.CheckCallDie(align_cmd)
build_utils.CheckOutput(align_cmd)
def main(argv):

Просмотреть файл

@ -27,7 +27,7 @@ def DoGcc(options):
options.template
])
build_utils.CheckCallDie(gcc_cmd)
build_utils.CheckOutput(gcc_cmd)
def main(argv):

Просмотреть файл

@ -30,7 +30,7 @@ def DoJar(options):
record_path = '%s.md5.stamp' % options.jar_path
md5_check.CallAndRecordIfStale(
lambda: build_utils.CheckCallDie(jar_cmd, cwd=jar_cwd),
lambda: build_utils.CheckOutput(jar_cmd, cwd=jar_cwd),
record_path=record_path,
input_paths=class_files,
input_strings=jar_cmd)

Просмотреть файл

@ -44,7 +44,7 @@ def CallJavap(classpath, classes):
'-verbose',
'-classpath', classpath
] + classes
return build_utils.CheckCallDie(javap_cmd, suppress_output=True)
return build_utils.CheckOutput(javap_cmd)
def ExtractToc(disassembled_classes):

Просмотреть файл

@ -59,8 +59,7 @@ def DoJavac(options):
# not contain the corresponding old .class file after running this action.
build_utils.DeleteDirectory(output_dir)
build_utils.MakeDirectory(output_dir)
suppress_output = not options.chromium_code
build_utils.CheckCallDie(javac_cmd, suppress_output=suppress_output)
build_utils.CheckOutput(javac_cmd, print_stdout=options.chromium_code)
record_path = '%s/javac.md5.stamp' % options.output_dir
md5_check.CallAndRecordIfStale(

Просмотреть файл

@ -104,7 +104,7 @@ def main():
package_command.append('--non-constant-id')
if options.custom_package:
package_command += ['--custom-package', options.custom_package]
build_utils.CheckCallDie(package_command)
build_utils.CheckOutput(package_command)
# Crunch image resources. This shrinks png files and is necessary for 9-patch
# images to display correctly.
@ -113,7 +113,7 @@ def main():
'crunch',
'-S', options.crunch_input_dir,
'-C', options.crunch_output_dir]
build_utils.CheckCallDie(aapt_cmd, suppress_output=True, fail_if_stderr=True)
build_utils.CheckOutput(aapt_cmd, fail_if_stderr=True)
MoveImagesToNonMdpiFolders(options.crunch_output_dir)

Просмотреть файл

@ -26,7 +26,8 @@ def DoProguard(options):
'-outjars', outjars,
'-libraryjars', libraryjars,
'@' + options.proguard_config]
build_utils.CheckCallDie(proguard_cmd)
build_utils.CheckOutput(proguard_cmd, print_stdout=True)
def main(argv):
parser = optparse.OptionParser()

Просмотреть файл

@ -17,7 +17,7 @@ def StripLibrary(android_strip, android_strip_args, library_path, output_path):
strip_cmd = ([android_strip] +
android_strip_args +
['-o', output_path, library_path])
build_utils.CheckCallDie(strip_cmd)
build_utils.CheckOutput(strip_cmd)

Просмотреть файл

@ -78,49 +78,44 @@ def ReadJson(path):
return json.load(jsonfile)
# This can be used in most cases like subprocess.check_call. The output,
class CalledProcessError(Exception):
"""This exception is raised when the process run by CheckOutput
exits with a non-zero exit code."""
def __init__(self, cwd, args, output):
self.cwd = cwd
self.args = args
self.output = output
def __str__(self):
# A user should be able to simply copy and paste the command that failed
# into their shell.
copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd),
' '.join(map(pipes.quote, self.args)))
return 'Command failed: {}\n{}'.format(copyable_command, self.output)
# This can be used in most cases like subprocess.check_output(). The output,
# particularly when the command fails, better highlights the command's failure.
# This call will directly exit on a failure in the subprocess so that no python
# stacktrace is printed after the output of the failed command (and will
# instead print a python stack trace before the output of the failed command)
def CheckCallDie(args, suppress_output=False, cwd=None, fail_if_stderr=False):
# If the command fails, raises a build_utils.CalledProcessError.
def CheckOutput(args, cwd=None, print_stdout=False, print_stderr=True,
fail_if_stderr=False):
if not cwd:
cwd = os.getcwd()
child = subprocess.Popen(args,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
stdout, stderr = child.communicate()
returncode = child.returncode
if fail_if_stderr and stderr and returncode == 0:
returncode = 1
if child.returncode or (stderr and fail_if_stderr):
raise CalledProcessError(cwd, args, stdout + stderr)
if returncode:
stacktrace = traceback.extract_stack()
print >> sys.stderr, ''.join(traceback.format_list(stacktrace))
# A user should be able to simply copy and paste the command that failed
# into their shell.
copyable_command = ' '.join(map(pipes.quote, args))
copyable_command = ('( cd ' + os.path.abspath(cwd) + '; '
+ copyable_command + ' )')
print >> sys.stderr, 'Command failed:', copyable_command, '\n'
if print_stdout:
sys.stdout.write(stdout)
if print_stderr:
sys.stderr.write(stderr)
if stdout:
print stdout,
if stderr:
print >> sys.stderr, stderr,
# Directly exit to avoid printing stacktrace.
sys.exit(returncode)
else:
if not suppress_output:
if stdout:
print stdout,
if stderr:
print >> sys.stderr, stderr,
return stdout + stderr
return stdout
def GetModifiedTime(path):
@ -141,7 +136,7 @@ def IsTimeStale(output, inputs):
def IsDeviceReady():
device_state = CheckCallDie(['adb', 'get-state'], suppress_output=True)
device_state = CheckOutput(['adb', 'get-state'])
return device_state.strip() == 'device'

Просмотреть файл

@ -47,7 +47,7 @@ def CallReadElf(library_or_executable):
readelf_cmd = [_options.readelf,
'-d',
library_or_executable]
return build_utils.CheckCallDie(readelf_cmd, suppress_output=True)
return build_utils.CheckOutput(readelf_cmd)
def GetDependencies(library_or_executable):