Bug 807974 - Handle make errors more gracefully; r=jhammel
authorGregory Szorc <gps@mozilla.com>
Tue, 06 Nov 2012 17:01:08 -0800
changeset 112404 e57bd488af4c351016062e655b29415b993d7b13
parent 112403 f561a4ffeeb9f523fb59c678b7ac901763e32911
child 112468 351a7925bc5d4b676e214726021e4a80a2cd89ab
child 112506 4e8873d14ed3c7a7498cad6e109cbdd12b1e0444
push id23821
push usergszorc@mozilla.com
push dateWed, 07 Nov 2012 01:02:22 +0000
treeherdermozilla-central@e57bd488af4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhammel
bugs807974
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 807974 - Handle make errors more gracefully; r=jhammel We now return the status code from executed processes. The API to require a successful status code has been changed from ignore_errors to ensure_exit_code. The build mach command no longer spews a stack trace if make fails. DONTBUILD (NPOTB)
python/mach/mach/mixin/process.py
python/mozbuild/mozbuild/base.py
python/mozbuild/mozbuild/mach_commands.py
--- a/python/mach/mach/mixin/process.py
+++ b/python/mach/mach/mixin/process.py
@@ -38,33 +38,38 @@ if os.environ.get('MSYSTEM', None) == 'M
 
 
 class ProcessExecutionMixin(LoggingMixin):
     """Mix-in that provides process execution functionality."""
 
     def run_process(self, args=None, cwd=None, append_env=None,
         explicit_env=None, log_name=None, log_level=logging.INFO,
         line_handler=None, require_unix_environment=False,
-        ignore_errors=False, ignore_children=False):
+        ensure_exit_code=0, ignore_children=False):
         """Runs a single process to completion.
 
         Takes a list of arguments to run where the first item is the
         executable. Runs the command in the specified directory and
         with optional environment variables.
 
         append_env -- Dict of environment variables to append to the current
             set of environment variables.
         explicit_env -- Dict of environment variables to set for the new
             process. Any existing environment variables will be ignored.
 
         require_unix_environment if True will ensure the command is executed
         within a UNIX environment. Basically, if we are on Windows, it will
         execute the command via an appropriate UNIX-like shell.
 
         ignore_children is proxied to mozprocess's ignore_children.
+
+        ensure_exit_code is used to ensure the exit code of a process matches
+        what is expected. If it is an integer, we raise an Exception is the
+        exit code does not match this value. If it is True, we ensure the exit
+        code is 0. If it is False, we don't perform any exit code validation.
         """
         args = self._normalize_command(args, require_unix_environment)
 
         self.log(logging.INFO, 'new_process', {'args': args}, ' '.join(args))
 
         def handleLine(line):
             # Converts str to unicode on Python 2 and bytes to str on Python 3.
             if isinstance(line, bytes):
@@ -91,19 +96,27 @@ class ProcessExecutionMixin(LoggingMixin
 
         p = ProcessHandlerMixin(args, cwd=cwd, env=use_env,
             processOutputLine=[handleLine], universal_newlines=True,
             ignore_children=ignore_children)
         p.run()
         p.processOutput()
         status = p.wait()
 
-        if status != 0 and not ignore_errors:
+        if ensure_exit_code is False:
+            return status
+
+        if ensure_exit_code is True:
+            ensure_exit_code = 0
+
+        if status != ensure_exit_code:
             raise Exception('Process executed with non-0 exit code: %s' % args)
 
+        return status
+
     def _normalize_command(self, args, require_unix_environment):
         """Adjust command arguments to run in the necessary environment.
 
         This exists mainly to facilitate execution of programs requiring a *NIX
         shell when running on Windows. The caller specifies whether a shell
         environment is required. If it is and we are running on Windows but
         aren't running in the UNIX-like msys environment, then we rewrite the
         command to execute via a shell.
--- a/python/mozbuild/mozbuild/base.py
+++ b/python/mozbuild/mozbuild/base.py
@@ -163,17 +163,17 @@ class MozbuildObject(ProcessExecutionMix
 
     def _get_objdir_path(self, path):
         """Convert a relative path in the object directory to a full path."""
         return os.path.join(self.topobjdir, path)
 
     def _run_make(self, directory=None, filename=None, target=None, log=True,
             srcdir=False, allow_parallel=True, line_handler=None,
             append_env=None, explicit_env=None, ignore_errors=False,
-            silent=True, print_directory=True):
+            ensure_exit_code=0, silent=True, print_directory=True):
         """Invoke make.
 
         directory -- Relative directory to look for Makefile in.
         filename -- Explicit makefile to run.
         target -- Makefile target(s) to make. Can be a string or iterable of
             strings.
         srcdir -- If True, invoke make from the source directory tree.
             Otherwise, make will be invoked from the object directory.
@@ -219,28 +219,28 @@ class MozbuildObject(ProcessExecutionMix
 
         params = {
             'args': args,
             'line_handler': line_handler,
             'append_env': append_env,
             'explicit_env': explicit_env,
             'log_level': logging.INFO,
             'require_unix_environment': True,
-            'ignore_errors': ignore_errors,
+            'ensure_exit_code': ensure_exit_code,
 
             # Make manages its children, so mozprocess doesn't need to bother.
             # Having mozprocess manage children can also have side-effects when
             # building on Windows. See bug 796840.
             'ignore_children': True,
         }
 
         if log:
             params['log_name'] = 'make'
 
-        fn(**params)
+        return fn(**params)
 
     @property
     def _make_path(self):
         if self._make is None:
             if self._is_windows():
                 self._make = os.path.join(self.topsrcdir, 'build', 'pymake',
                     'make.py')
 
@@ -253,20 +253,20 @@ class MozbuildObject(ProcessExecutionMix
                         continue
 
         if self._make is None:
             raise Exception('Could not find suitable make binary!')
 
         return self._make
 
     def _run_command_in_srcdir(self, **args):
-        self.run_process(cwd=self.topsrcdir, **args)
+        return self.run_process(cwd=self.topsrcdir, **args)
 
     def _run_command_in_objdir(self, **args):
-        self.run_process(cwd=self.topobjdir, **args)
+        return self.run_process(cwd=self.topobjdir, **args)
 
     def _is_windows(self):
         return os.name in ('nt', 'ce')
 
     def _spawn(self, cls):
         """Create a new MozbuildObject-derived class instance from ourselves.
 
         This is used as a convenience method to create other
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -44,25 +44,28 @@ class Build(MachCommandBase):
                     self.log(logging.INFO, 'compiler_warning', warning,
                         'Warning: {flag} in {filename}: {message}')
             except:
                 # This will get logged in the more robust implementation.
                 pass
 
             self.log(logging.INFO, 'build_output', {'line': line}, '{line}')
 
-        self._run_make(srcdir=True, filename='client.mk', line_handler=on_line,
-            log=False, print_directory=False)
+        status = self._run_make(srcdir=True, filename='client.mk',
+            line_handler=on_line, log=False, print_directory=False,
+            ensure_exit_code=False)
 
         self.log(logging.WARNING, 'warning_summary',
             {'count': len(warnings_collector.database)},
             '{count} compiler warnings present.')
 
         warnings_database.save_to_file(warnings_path)
 
+        return status
+
 
 @CommandProvider
 class Warnings(MachCommandBase):
     """Provide commands for inspecting warnings."""
 
     @property
     def database_path(self):
         return self._get_state_filename('warnings.json')