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