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.
5 """Module for build host support."""
14 # Controls what verbosity level turns on command trail logging
17 def PrintTrail(trail):
18 print 'Command expanded the following variables:'
19 for key, value in trail:
22 print ' ', key, '=', value
25 class Host(cr.Plugin, cr.Plugin.Type):
26 """Base class for implementing cr hosts.
28 The host is the main access point to services provided by the machine cr
29 is running on. It exposes information about the machine, and runs external
30 commands on behalf of the actions.
34 super(Host, self).__init__()
37 """Detects whether this is the correct host implementation.
39 This method is overridden by the concrete implementations.
41 true if the plugin matches the machine it is running on.
47 for host in cls.Plugins():
51 def _Execute(self, command,
52 shell=False, capture=False, silent=False,
53 ignore_dry_run=False, return_status=False,
54 ignore_interrupt_signal=False):
55 """This is the only method that launches external programs.
57 It is a thin wrapper around subprocess.Popen that handles cr specific
58 issues. The command is expanded in the active context so that variables
61 command: the command to run.
62 shell: whether to run the command using the shell.
63 capture: controls wether the output of the command is captured.
64 ignore_dry_run: Normally, if the context is in dry run mode the command is
65 printed but not executed. This flag overrides that behaviour, causing
66 the command to be run anyway.
67 return_status: switches the function to returning the status code rather
69 ignore_interrupt_signal: Ignore the interrupt signal (i.e., Ctrl-C) while
70 the command is running. Useful for letting interactive programs manage
73 the status if return_status is true, or the output if capture is true,
76 with cr.context.Trace():
77 command = [cr.context.Substitute(arg) for arg in command if arg]
78 trail = cr.context.trail
80 print 'Empty command passed to execute'
82 if cr.context.verbose:
83 print ' '.join(command)
84 if cr.context.verbose >= _TRAIL_VERBOSITY:
86 if ignore_dry_run or not cr.context.dry_run:
91 out = open(os.devnull, "w")
95 env={k: str(v) for k, v in cr.context.exported.items()},
98 print 'Failed to exec', command
99 # Don't log the trail if we already have
100 if cr.context.verbose < _TRAIL_VERBOSITY:
104 if ignore_interrupt_signal:
105 signal.signal(signal.SIGINT, signal.SIG_IGN)
106 output, _ = p.communicate()
108 if ignore_interrupt_signal:
109 signal.signal(signal.SIGINT, signal.SIG_DFL)
114 if p.returncode != 0:
115 print 'Error {0} executing command {1}'.format(p.returncode, command)
120 @cr.Plugin.activemethod
121 def Shell(self, *command):
122 command = ' '.join([pipes.quote(arg) for arg in command])
123 return self._Execute([command], shell=True, ignore_interrupt_signal=True)
125 @cr.Plugin.activemethod
126 def Execute(self, *command):
127 return self._Execute(command, shell=False)
129 @cr.Plugin.activemethod
130 def ExecuteSilently(self, *command):
131 return self._Execute(command, shell=False, silent=True)
133 @cr.Plugin.activemethod
134 def CaptureShell(self, *command):
135 return self._Execute(command,
136 shell=True, capture=True, ignore_dry_run=True)
138 @cr.Plugin.activemethod
139 def Capture(self, *command):
140 return self._Execute(command, capture=True, ignore_dry_run=True)
142 @cr.Plugin.activemethod
143 def ExecuteStatus(self, *command):
144 return self._Execute(command,
145 ignore_dry_run=True, return_status=True)
147 @cr.Plugin.activemethod
148 def YesNo(self, question, default=True):
149 """Ask the user a yes no question
151 This blocks until the user responds.
153 question: The question string to show the user
154 default: True if the default response is Yes
156 True if the response was yes.
158 options = 'Y/n' if default else 'y/N'
159 result = raw_input(question + ' [' + options + '] ').lower()
162 return result in ['y', 'yes']
165 def SearchPath(cls, name, paths=[]):
166 """Searches the PATH for an executable.
169 name: the name of the binary to search for.
171 the set of executables found, or an empty list if none.
175 extensions.extend(os.environ.get('PATHEXT', '').split(os.pathsep))
176 paths = [cr.context.Substitute(path) for path in paths if path]
177 paths = paths + os.environ.get('PATH', '').split(os.pathsep)
179 partial = os.path.join(path, name)
180 for extension in extensions:
181 filename = partial + extension
182 if os.path.exists(filename) and filename not in result:
183 result.append(filename)