host.py revision effb81e5f8246d0db0270817048dc992db66e9fb
1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Module for build host support.""" 6 7import os 8import pipes 9import signal 10import subprocess 11 12import cr 13 14# Controls what verbosity level turns on command trail logging 15_TRAIL_VERBOSITY = 2 16 17 18class Host(cr.Plugin, cr.Plugin.Type): 19 """Base class for implementing cr hosts. 20 21 The host is the main access point to services provided by the machine cr 22 is running on. It exposes information about the machine, and runs external 23 commands on behalf of the actions. 24 """ 25 26 def __init__(self): 27 super(Host, self).__init__() 28 29 def Matches(self): 30 """Detects whether this is the correct host implementation. 31 32 This method is overridden by the concrete implementations. 33 Returns: 34 true if the plugin matches the machine it is running on. 35 """ 36 return False 37 38 @classmethod 39 def Select(cls): 40 for host in cls.Plugins(): 41 if host.Matches(): 42 return host 43 44 def _Execute(self, command, 45 shell=False, capture=False, silent=False, 46 ignore_dry_run=False, return_status=False, 47 ignore_interrupt_signal=False): 48 """This is the only method that launches external programs. 49 50 It is a thin wrapper around subprocess.Popen that handles cr specific 51 issues. The command is expanded in the active context so that variables 52 are substituted. 53 Args: 54 command: the command to run. 55 shell: whether to run the command using the shell. 56 capture: controls wether the output of the command is captured. 57 ignore_dry_run: Normally, if the context is in dry run mode the command is 58 printed but not executed. This flag overrides that behaviour, causing 59 the command to be run anyway. 60 return_status: switches the function to returning the status code rather 61 the output. 62 ignore_interrupt_signal: Ignore the interrupt signal (i.e., Ctrl-C) while 63 the command is running. Useful for letting interactive programs manage 64 Ctrl-C by themselves. 65 Returns: 66 the status if return_status is true, or the output if capture is true, 67 otherwise nothing. 68 """ 69 with cr.context.Trace(): 70 command = [cr.context.Substitute(arg) for arg in command if arg] 71 trail = cr.context.trail 72 if not command: 73 print 'Empty command passed to execute' 74 exit(1) 75 if cr.context.verbose: 76 print ' '.join(command) 77 if cr.context.verbose >= _TRAIL_VERBOSITY: 78 print 'Command expanded the following variables:' 79 for key, value in trail: 80 print ' ', key, '=', value 81 if ignore_dry_run or not cr.context.dry_run: 82 out = None 83 if capture: 84 out = subprocess.PIPE 85 elif silent: 86 out = open(os.devnull, "w") 87 try: 88 p = subprocess.Popen( 89 command, shell=shell, 90 env={k: str(v) for k, v in cr.context.exported.items()}, 91 stdout=out) 92 except OSError: 93 print 'Failed to exec', command 94 # Don't log the trail if we already have 95 if cr.context.verbose < _TRAIL_VERBOSITY: 96 print 'Variables used to build the command were:' 97 for key, value in trail: 98 print ' ', key, '=', value 99 exit(1) 100 try: 101 if ignore_interrupt_signal: 102 signal.signal(signal.SIGINT, signal.SIG_IGN) 103 output, _ = p.communicate() 104 finally: 105 if ignore_interrupt_signal: 106 signal.signal(signal.SIGINT, signal.SIG_DFL) 107 if silent: 108 out.close() 109 if return_status: 110 return p.returncode 111 if p.returncode != 0: 112 print 'Error {0} executing command {1}'.format(p.returncode, command) 113 exit(p.returncode) 114 return output or '' 115 return '' 116 117 @cr.Plugin.activemethod 118 def Shell(self, *command): 119 command = ' '.join([pipes.quote(arg) for arg in command]) 120 return self._Execute([command], shell=True, ignore_interrupt_signal=True) 121 122 @cr.Plugin.activemethod 123 def Execute(self, *command): 124 return self._Execute(command, shell=False) 125 126 @cr.Plugin.activemethod 127 def ExecuteSilently(self, *command): 128 return self._Execute(command, shell=False, silent=True) 129 130 @cr.Plugin.activemethod 131 def CaptureShell(self, *command): 132 return self._Execute(command, 133 shell=True, capture=True, ignore_dry_run=True) 134 135 @cr.Plugin.activemethod 136 def Capture(self, *command): 137 return self._Execute(command, capture=True, ignore_dry_run=True) 138 139 @cr.Plugin.activemethod 140 def ExecuteStatus(self, *command): 141 return self._Execute(command, 142 ignore_dry_run=True, return_status=True) 143 144 @cr.Plugin.activemethod 145 def YesNo(self, question, default=True): 146 """Ask the user a yes no question 147 148 This blocks until the user responds. 149 Args: 150 question: The question string to show the user 151 default: True if the default response is Yes 152 Returns: 153 True if the response was yes. 154 """ 155 options = 'Y/n' if default else 'y/N' 156 result = raw_input(question + ' [' + options + '] ').lower() 157 if result == '': 158 return default 159 return result in ['y', 'yes'] 160 161 @classmethod 162 def SearchPath(cls, name, paths=[]): 163 """Searches the PATH for an executable. 164 165 Args: 166 name: the name of the binary to search for. 167 Returns: 168 the set of executables found, or an empty list if none. 169 """ 170 result = [] 171 extensions = [''] 172 extensions.extend(os.environ.get('PATHEXT', '').split(os.pathsep)) 173 paths = [cr.context.Substitute(path) for path in paths if path] 174 paths = paths + os.environ.get('PATH', '').split(os.pathsep) 175 for path in paths: 176 partial = os.path.join(path, name) 177 for extension in extensions: 178 filename = partial + extension 179 if os.path.exists(filename) and filename not in result: 180 result.append(filename) 181 return result 182