1# Copyright 2017 - The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Command-related utilities."""
16
17from collections import namedtuple
18import logging
19import os
20import shlex
21import subprocess
22import sys
23
24
25CommandResult = namedtuple('CommandResult', 'returncode stdoutdata, stderrdata')
26PIPE = subprocess.PIPE
27
28_LOCAL_BIN_PATH = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])),
29                               'bin')
30
31
32def _update_command_for_local(command, kwargs):
33  if kwargs.get('shell', False):
34    # do nothing for shell commands
35    return
36
37  prog = command[0]
38  local_prog = os.path.join(_LOCAL_BIN_PATH, prog)
39  if os.path.isfile(local_prog) and os.access(local_prog, os.X_OK):
40    logging.debug('Using local executable: %s', local_prog)
41    command[0] = local_prog
42
43
44def run_command(command, read_stdout=False, read_stderr=False,
45                log_stdout=False, log_stderr=False,
46                raise_on_error=True, sudo=False, **kwargs):
47  """Runs a command and returns the results.
48
49    The method tries to use the executable in bin/ firstly.
50
51  Args:
52    command: A sequence of command arguments or else a single string.
53    read_stdout: If True, includes stdout data in the returned tuple.
54      Otherwise includes None in the returned tuple.
55    read_stderr: If True, includes stderr data in the returned tuple.
56      Otherwise includes None in the returned tuple.
57    log_stdout: If True, logs stdout data.
58    log_stderr: If True, logs stderro data.
59    raise_on_error: If True, raise exception if return code is nonzero.
60    sudo: Prepends 'sudo' to command if user is not root.
61    **kwargs: the keyword arguments passed to subprocess.Popen().
62
63  Returns:
64    A namedtuple CommandResult(returncode, stdoutdata, stderrdata).
65    The latter two fields will be set only when read_stdout/read_stderr
66    is True, respectively. Otherwise, they will be None.
67
68  Raises:
69    OSError: Not such a command to execute, raised by subprocess.Popen().
70    subprocess.CalledProcessError: The return code of the command is nonzero.
71  """
72  _update_command_for_local(command, kwargs)
73
74  if sudo and os.getuid() != 0:
75    if kwargs.pop('shell', False):
76      command = ['sudo', 'sh', '-c', command]
77    else:
78      command = ['sudo'] + command
79
80  if read_stdout or log_stdout:
81    assert kwargs.get('stdout') in [None, PIPE]
82    kwargs['stdout'] = PIPE
83  if read_stderr or log_stderr:
84    assert kwargs.get('stderr') in [None, PIPE]
85    kwargs['stderr'] = PIPE
86
87  need_communicate = (read_stdout or read_stderr or
88                      log_stdout or log_stderr)
89  proc = subprocess.Popen(command, **kwargs)
90  if need_communicate:
91    stdout, stderr = proc.communicate()
92  else:
93    proc.wait()  # no need to communicate; just wait.
94
95  if kwargs.get('shell'):
96    command_in_log = command
97  else:
98    command_in_log = ' '.join(arg for arg in command)
99  logging.log(logging.INFO, 'Executed command: %r (ret: %d)',
100              command_in_log, proc.returncode)
101
102  log_level = logging.ERROR if proc.returncode != 0 else logging.INFO
103  if log_stdout:
104    logging.log(log_level, '  stdout: %r', stdout)
105  if log_stderr:
106    logging.log(log_level, '  stderr: %r', stderr)
107
108  if proc.returncode != 0 and raise_on_error:
109    raise subprocess.CalledProcessError(proc.returncode, command)
110
111  return CommandResult(proc.returncode,
112                       stdout if read_stdout else None,
113                       stderr if read_stderr else None)
114