patman: Allow running a tool and returning the full result

Add a new function which returns the entire result from running a tool,
not just stdout. Update Run() to use this and to return stdout on error,
if stderr is empty, since some unfortunate tools write their error
output to stdout rather than stderr.

Move building of the PATH to a separate function.

Make the exception catching more specific, to catch just ValueError, since
broad exceptions are a pain to debug.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/patman/tools.py b/tools/patman/tools.py
index 86c4f61..7af4a52 100644
--- a/tools/patman/tools.py
+++ b/tools/patman/tools.py
@@ -313,6 +313,70 @@
         target_name = name
     return target_name, extra_args
 
+def get_env_with_path():
+    """Get an updated environment with the PATH variable set correctly
+
+    If there are any search paths set, these need to come first in the PATH so
+    that these override any other version of the tools.
+
+    Returns:
+        dict: New environment with PATH updated, or None if there are not search
+            paths
+    """
+    if tool_search_paths:
+        env = dict(os.environ)
+        env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
+        return env
+
+def run_result(name, *args, **kwargs):
+    """Run a tool with some arguments
+
+    This runs a 'tool', which is a program used by binman to process files and
+    perhaps produce some output. Tools can be located on the PATH or in a
+    search path.
+
+    Args:
+        name: Command name to run
+        args: Arguments to the tool
+        for_host: True to resolve the command to the version for the host
+        for_target: False to run the command as-is, without resolving it
+                   to the version for the compile target
+        raise_on_error: Raise an error if the command fails (True by default)
+
+    Returns:
+        CommandResult object
+    """
+    try:
+        binary = kwargs.get('binary')
+        for_host = kwargs.get('for_host', False)
+        for_target = kwargs.get('for_target', not for_host)
+        raise_on_error = kwargs.get('raise_on_error', True)
+        env = get_env_with_path()
+        if for_target:
+            name, extra_args = GetTargetCompileTool(name)
+            args = tuple(extra_args) + args
+        elif for_host:
+            name, extra_args = GetHostCompileTool(name)
+            args = tuple(extra_args) + args
+        name = os.path.expanduser(name)  # Expand paths containing ~
+        all_args = (name,) + args
+        result = command.RunPipe([all_args], capture=True, capture_stderr=True,
+                                 env=env, raise_on_error=False, binary=binary)
+        if result.return_code:
+            if raise_on_error:
+                raise ValueError("Error %d running '%s': %s" %
+                                 (result.return_code,' '.join(all_args),
+                                  result.stderr or result.stdout))
+        return result
+    except ValueError:
+        if env and not PathHasFile(env['PATH'], name):
+            msg = "Please install tool '%s'" % name
+            package = packages.get(name)
+            if package:
+                 msg += " (e.g. from package '%s')" % package
+            raise ValueError(msg)
+        raise
+
 def Run(name, *args, **kwargs):
     """Run a tool with some arguments
 
@@ -330,37 +394,9 @@
     Returns:
         CommandResult object
     """
-    try:
-        binary = kwargs.get('binary')
-        for_host = kwargs.get('for_host', False)
-        for_target = kwargs.get('for_target', not for_host)
-        env = None
-        if tool_search_paths:
-            env = dict(os.environ)
-            env['PATH'] = ':'.join(tool_search_paths) + ':' + env['PATH']
-        if for_target:
-            name, extra_args = GetTargetCompileTool(name)
-            args = tuple(extra_args) + args
-        elif for_host:
-            name, extra_args = GetHostCompileTool(name)
-            args = tuple(extra_args) + args
-        name = os.path.expanduser(name)  # Expand paths containing ~
-        all_args = (name,) + args
-        result = command.RunPipe([all_args], capture=True, capture_stderr=True,
-                                 env=env, raise_on_error=False, binary=binary)
-        if result.return_code:
-            raise ValueError("Error %d running '%s': %s" %
-               (result.return_code,' '.join(all_args),
-                result.stderr))
+    result = run_result(name, *args, **kwargs)
+    if result is not None:
         return result.stdout
-    except:
-        if env and not PathHasFile(env['PATH'], name):
-            msg = "Please install tool '%s'" % name
-            package = packages.get(name)
-            if package:
-                 msg += " (e.g. from package '%s')" % package
-            raise ValueError(msg)
-        raise
 
 def Filename(fname):
     """Resolve a file path to an absolute path.