• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2009, Google Inc. All rights reserved.
2# Copyright (c) 2009 Apple Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30import os
31import StringIO
32import subprocess
33import sys
34
35from webkitpy.webkit_logging import tee
36
37
38class ScriptError(Exception):
39
40    def __init__(self,
41                 message=None,
42                 script_args=None,
43                 exit_code=None,
44                 output=None,
45                 cwd=None):
46        if not message:
47            message = 'Failed to run "%s"' % script_args
48            if exit_code:
49                message += " exit_code: %d" % exit_code
50            if cwd:
51                message += " cwd: %s" % cwd
52
53        Exception.__init__(self, message)
54        self.script_args = script_args # 'args' is already used by Exception
55        self.exit_code = exit_code
56        self.output = output
57        self.cwd = cwd
58
59    def message_with_output(self, output_limit=500):
60        if self.output:
61            if output_limit and len(self.output) > output_limit:
62                return "%s\nLast %s characters of output:\n%s" % \
63                    (self, output_limit, self.output[-output_limit:])
64            return "%s\n%s" % (self, self.output)
65        return str(self)
66
67    def command_name(self):
68        command_path = self.script_args
69        if type(command_path) is list:
70            command_path = command_path[0]
71        return os.path.basename(command_path)
72
73
74def run_command(*args, **kwargs):
75    # FIXME: This should not be a global static.
76    # New code should use Executive.run_command directly instead
77    return Executive().run_command(*args, **kwargs)
78
79
80class Executive(object):
81
82    def _run_command_with_teed_output(self, args, teed_output):
83        child_process = subprocess.Popen(args,
84                                         stdout=subprocess.PIPE,
85                                         stderr=subprocess.STDOUT)
86
87        # Use our own custom wait loop because Popen ignores a tee'd
88        # stderr/stdout.
89        # FIXME: This could be improved not to flatten output to stdout.
90        while True:
91            output_line = child_process.stdout.readline()
92            if output_line == "" and child_process.poll() != None:
93                return child_process.poll()
94            teed_output.write(output_line)
95
96    def run_and_throw_if_fail(self, args, quiet=False):
97        # Cache the child's output locally so it can be used for error reports.
98        child_out_file = StringIO.StringIO()
99        if quiet:
100            dev_null = open(os.devnull, "w")
101        child_stdout = tee(child_out_file, dev_null if quiet else sys.stdout)
102        exit_code = self._run_command_with_teed_output(args, child_stdout)
103        if quiet:
104            dev_null.close()
105
106        child_output = child_out_file.getvalue()
107        child_out_file.close()
108
109        if exit_code:
110            raise ScriptError(script_args=args,
111                              exit_code=exit_code,
112                              output=child_output)
113
114    @staticmethod
115    def cpu_count():
116        # This API exists only in Python 2.6 and higher.  :(
117        try:
118            import multiprocessing
119            return multiprocessing.cpu_count()
120        except (ImportError, NotImplementedError):
121            # This quantity is a lie but probably a reasonable guess for modern
122            # machines.
123            return 2
124
125    # Error handlers do not need to be static methods once all callers are
126    # updated to use an Executive object.
127
128    @staticmethod
129    def default_error_handler(error):
130        raise error
131
132    @staticmethod
133    def ignore_error(error):
134        pass
135
136    # FIXME: This should be merged with run_and_throw_if_fail
137
138    def run_command(self,
139                    args,
140                    cwd=None,
141                    input=None,
142                    error_handler=None,
143                    return_exit_code=False,
144                    return_stderr=True):
145        if hasattr(input, 'read'): # Check if the input is a file.
146            stdin = input
147            string_to_communicate = None
148        else:
149            stdin = subprocess.PIPE if input else None
150            string_to_communicate = input
151        if return_stderr:
152            stderr = subprocess.STDOUT
153        else:
154            stderr = None
155
156        process = subprocess.Popen(args,
157                                   stdin=stdin,
158                                   stdout=subprocess.PIPE,
159                                   stderr=stderr,
160                                   cwd=cwd)
161        output = process.communicate(string_to_communicate)[0]
162        exit_code = process.wait()
163        if exit_code:
164            script_error = ScriptError(script_args=args,
165                                       exit_code=exit_code,
166                                       output=output,
167                                       cwd=cwd)
168            (error_handler or self.default_error_handler)(script_error)
169        if return_exit_code:
170            return exit_code
171        return output
172