Restart codereview issue 6792042

Change-Id: I052d4d713a7d15cc50290e68bc433a9f1c0792b4

BUG=78345
TEST=unittests

Review URL: http://codereview.chromium.org/6840064
diff --git a/lib/cros_build_lib.py b/lib/cros_build_lib.py
index f7ea5e9..9e35dbc 100644
--- a/lib/cros_build_lib.py
+++ b/lib/cros_build_lib.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+ # Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -43,11 +43,14 @@
 def RunCommand(cmd, print_cmd=True, error_ok=False, error_message=None,
                exit_code=False, redirect_stdout=False, redirect_stderr=False,
                cwd=None, input=None, enter_chroot=False, shell=False,
-               env=None, ignore_sigint=False, combine_stdout_stderr=False):
+               env=None, extra_env=None, ignore_sigint=False,
+               combine_stdout_stderr=False):
   """Runs a command.
 
   Args:
-    cmd: cmd to run.  Should be input to subprocess.Popen.
+    cmd: cmd to run.  Should be input to subprocess.Popen. If a string, shell
+      must be true. Otherwise the command must be an array of arguments, and
+      shell must be false.
     print_cmd: prints the command before running it.
     error_ok: does not raise an exception on error.
     error_message: prints out this message when an error occurrs.
@@ -58,9 +61,15 @@
     input: input to pipe into this command through stdin.
     enter_chroot: this command should be run from within the chroot.  If set,
       cwd must point to the scripts directory.
-    shell: If shell is True, the specified command will be executed through
-      the shell.
-    env: If non-None, this is the environment for the new process.
+    shell: Controls whether we add a shell as a command interpreter.  See cmd
+      since it has to agree as to the type.
+    env: If non-None, this is the environment for the new process.  If
+      enter_chroot is true then this is the environment of the enter_chroot,
+      most of which gets removed from the cmd run.
+    extra_env: If set, this is added to the environment for the new process.
+      In enter_chroot=True case, these are specified on the post-entry
+      side, and so are often more useful.  This dictionary is not used to
+      clear any entries though.
     ignore_sigint: If True, we'll ignore signal.SIGINT before calling the
       child.  This is the desired behavior if we know our child will handle
       Ctrl-C.  If we don't do this, I think we and the child will both get
@@ -86,24 +95,41 @@
   # TODO(sosa): gpylint complains about redefining built-in 'input'.
   #   Can we rename this variable?
   if input: stdin = subprocess.PIPE
+
   if isinstance(cmd, basestring):
-    if enter_chroot: cmd = './enter_chroot.sh -- ' + cmd
-    cmd_str = cmd
-  else:
-    if enter_chroot: cmd = ['./enter_chroot.sh', '--'] + cmd
-    cmd_str = ' '.join(cmd)
+    if not shell:
+      raise Exception('Cannot run a string command without a shell')
+    cmd = ['/bin/sh', '-c', cmd]
+    shell = False
+  elif shell:
+    raise Exception('Cannot run an array command with a shell')
+
+  # If we are using enter_chroot we need to use enterchroot pass env through
+  # to the final command.
+  if enter_chroot:
+    cmd = ['./enter_chroot.sh', '--'] + cmd
+    if extra_env:
+      for (key, value) in extra_env.items():
+        cmd.insert(1, '%s=%s' % (key, value))
+  elif extra_env:
+    if env is not None:
+      env = env.copy()
+    else:
+      env = os.environ.copy()
+
+    env.update(extra_env)
 
   # Print out the command before running.
   if print_cmd:
     if cwd:
-      Info('RunCommand: %s in %s' % (cmd_str, cwd))
+      Info('RunCommand: %r in %s' % (cmd, cwd))
     else:
-      Info('RunCommand: %s' % cmd_str)
+      Info('RunCommand: %r' % cmd)
   cmd_result.cmd = cmd
 
   try:
     proc = subprocess.Popen(cmd, cwd=cwd, stdin=stdin, stdout=stdout,
-                            stderr=stderr, shell=shell, env=env)
+                            stderr=stderr, shell=False, env=env)
     if ignore_sigint:
       old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
     try:
@@ -116,10 +142,15 @@
       cmd_result.returncode = proc.returncode
 
     if not error_ok and proc.returncode:
-      msg = ('Command "%s" failed.\n' % cmd_str +
+      msg = ('Command "%r" failed.\n' % cmd +
              (error_message or cmd_result.error or cmd_result.output or ''))
       raise RunCommandError(msg, cmd)
   # TODO(sosa): is it possible not to use the catch-all Exception here?
+  except OSError, e:
+    if not error_ok:
+      raise RunCommandError(str(e), cmd)
+    else:
+      Warning(str(e))
   except Exception, e:
     if not error_ok:
       raise