Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1216)

Side by Side Diff: mojo/tools/mopy/android.py

Issue 1100823005: Allow android python shell run script to not stop if multiple devices are connected. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « mojo/tools/android_mojo_shell.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import atexit 5 import atexit
6 import itertools 6 import itertools
7 import json 7 import json
8 import logging 8 import logging
9 import os 9 import os
10 import os.path 10 import os.path
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
98 process.kill() 98 process.kill()
99 99
100 100
101 class AndroidShell(object): 101 class AndroidShell(object):
102 """ Allows to set up and run a given mojo shell binary on an Android device. 102 """ Allows to set up and run a given mojo shell binary on an Android device.
103 103
104 Args: 104 Args:
105 shell_apk_path: path to the shell Android binary 105 shell_apk_path: path to the shell Android binary
106 local_dir: directory where locally build Mojo apps will be served, optional 106 local_dir: directory where locally build Mojo apps will be served, optional
107 adb_path: path to adb, optional if adb is in PATH 107 adb_path: path to adb, optional if adb is in PATH
108 target_device: device to run on, if multiple devices are connected
108 """ 109 """
109 def __init__(self, shell_apk_path, local_dir=None, adb_path="adb"): 110 def __init__(
111 self, shell_apk_path, local_dir=None, adb_path="adb", target_device=None):
110 self.shell_apk_path = shell_apk_path 112 self.shell_apk_path = shell_apk_path
111 self.adb_path = adb_path 113 self.adb_path = adb_path
112 self.local_dir = local_dir 114 self.local_dir = local_dir
115 self.target_device = target_device
116
117 def _CreateADBCommand(self, args):
118 adb_command = [self.adb_path]
119 if self.target_device:
120 adb_command.extend(['-s', self.target_device])
121 adb_command.extend(args)
122 return adb_command
113 123
114 def _ReadFifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5): 124 def _ReadFifo(self, fifo_path, pipe, on_fifo_closed, max_attempts=5):
115 """ 125 """
116 Reads |fifo_path| on the device and write the contents to |pipe|. Calls 126 Reads |fifo_path| on the device and write the contents to |pipe|. Calls
117 |on_fifo_closed| when the fifo is closed. This method will try to find the 127 |on_fifo_closed| when the fifo is closed. This method will try to find the
118 path up to |max_attempts|, waiting 1 second between each attempt. If it 128 path up to |max_attempts|, waiting 1 second between each attempt. If it
119 cannot find |fifo_path|, a exception will be raised. 129 cannot find |fifo_path|, a exception will be raised.
120 """ 130 """
121 fifo_command = [self.adb_path, 'shell', 'test -e "%s"; echo $?' % fifo_path] 131 fifo_command = self._CreateADBCommand(
132 ['shell', 'test -e "%s"; echo $?' % fifo_path])
122 133
123 def Run(): 134 def Run():
124 def _WaitForFifo(): 135 def _WaitForFifo():
125 for _ in xrange(max_attempts): 136 for _ in xrange(max_attempts):
126 if subprocess.check_output(fifo_command)[0] == '0': 137 if subprocess.check_output(fifo_command)[0] == '0':
127 return 138 return
128 time.sleep(1) 139 time.sleep(1)
129 if on_fifo_closed: 140 if on_fifo_closed:
130 on_fifo_closed() 141 on_fifo_closed()
131 raise Exception("Unable to find fifo.") 142 raise Exception("Unable to find fifo.")
132 _WaitForFifo() 143 _WaitForFifo()
133 stdout_cat = subprocess.Popen([self.adb_path, 144 stdout_cat = subprocess.Popen(self._CreateADBCommand([
134 'shell', 145 'shell',
135 'cat', 146 'cat',
136 fifo_path], 147 fifo_path]),
137 stdout=pipe) 148 stdout=pipe)
138 atexit.register(_ExitIfNeeded, stdout_cat) 149 atexit.register(_ExitIfNeeded, stdout_cat)
139 stdout_cat.wait() 150 stdout_cat.wait()
140 if on_fifo_closed: 151 if on_fifo_closed:
141 on_fifo_closed() 152 on_fifo_closed()
142 153
143 thread = threading.Thread(target=Run, name="StdoutRedirector") 154 thread = threading.Thread(target=Run, name="StdoutRedirector")
144 thread.start() 155 thread.start()
145 156
146 def _MapPort(self, device_port, host_port): 157 def _MapPort(self, device_port, host_port):
147 """ 158 """
148 Maps the device port to the host port. If |device_port| is 0, a random 159 Maps the device port to the host port. If |device_port| is 0, a random
149 available port is chosen. Returns the device port. 160 available port is chosen. Returns the device port.
150 """ 161 """
151 def _FindAvailablePortOnDevice(): 162 def _FindAvailablePortOnDevice():
152 opened = subprocess.check_output([self.adb_path, 'shell', 'netstat']) 163 opened = subprocess.check_output(
164 self._CreateADBCommand(['shell', 'netstat']))
153 opened = [int(x.strip().split()[3].split(':')[1]) 165 opened = [int(x.strip().split()[3].split(':')[1])
154 for x in opened if x.startswith(' tcp')] 166 for x in opened if x.startswith(' tcp')]
155 while True: 167 while True:
156 port = random.randint(4096, 16384) 168 port = random.randint(4096, 16384)
157 if port not in opened: 169 if port not in opened:
158 return port 170 return port
159 if device_port == 0: 171 if device_port == 0:
160 device_port = _FindAvailablePortOnDevice() 172 device_port = _FindAvailablePortOnDevice()
161 subprocess.Popen([self.adb_path, 173 subprocess.Popen(self._CreateADBCommand([
162 "reverse", 174 "reverse",
163 "tcp:%d" % device_port, 175 "tcp:%d" % device_port,
164 "tcp:%d" % host_port]).wait() 176 "tcp:%d" % host_port])).wait()
165 177
166 unmap_command = [self.adb_path, "reverse", "--remove", 178 unmap_command = self._CreateADBCommand(["reverse", "--remove",
167 "tcp:%d" % device_port] 179 "tcp:%d" % device_port])
168 180
169 def _UnmapPort(): 181 def _UnmapPort():
170 subprocess.Popen(unmap_command) 182 subprocess.Popen(unmap_command)
171 atexit.register(_UnmapPort) 183 atexit.register(_UnmapPort)
172 return device_port 184 return device_port
173 185
174 def _StartHttpServerForDirectory(self, path, port=0): 186 def _StartHttpServerForDirectory(self, path, port=0):
175 """Starts an http server serving files from |path|. Returns the local 187 """Starts an http server serving files from |path|. Returns the local
176 url.""" 188 url."""
177 assert path 189 assert path
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
218 result.append(self._StartHttpServerForOriginMapping( 230 result.append(self._StartHttpServerForOriginMapping(
219 value, DEFAULT_BASE_PORT + 1 + i if fixed_port else 0)) 231 value, DEFAULT_BASE_PORT + 1 + i if fixed_port else 0))
220 return [MAPPING_PREFIX + ','.join(result)] 232 return [MAPPING_PREFIX + ','.join(result)]
221 233
222 def PrepareShellRun(self, origin=None, fixed_port=True): 234 def PrepareShellRun(self, origin=None, fixed_port=True):
223 """ Prepares for StartShell: runs adb as root and installs the apk. If no 235 """ Prepares for StartShell: runs adb as root and installs the apk. If no
224 --origin is specified, local http server will be set up to serve files from 236 --origin is specified, local http server will be set up to serve files from
225 the build directory along with port forwarding. 237 the build directory along with port forwarding.
226 238
227 Returns arguments that should be appended to shell argument list.""" 239 Returns arguments that should be appended to shell argument list."""
228 if 'cannot run as root' in subprocess.check_output([self.adb_path, 'root']): 240 if 'cannot run as root' in subprocess.check_output(
241 self._CreateADBCommand(['root'])):
229 raise Exception("Unable to run adb as root.") 242 raise Exception("Unable to run adb as root.")
230 subprocess.check_call( 243 subprocess.check_call(
231 [self.adb_path, 'install', '-r', self.shell_apk_path, '-i', 244 self._CreateADBCommand(['install', '-r', self.shell_apk_path, '-i',
232 MOJO_SHELL_PACKAGE_NAME]) 245 MOJO_SHELL_PACKAGE_NAME]))
233 atexit.register(self.StopShell) 246 atexit.register(self.StopShell)
234 247
235 extra_shell_args = [] 248 extra_shell_args = []
236 origin_url = origin if origin else self._StartHttpServerForDirectory( 249 origin_url = origin if origin else self._StartHttpServerForDirectory(
237 self.local_dir, DEFAULT_BASE_PORT if fixed_port else 0) 250 self.local_dir, DEFAULT_BASE_PORT if fixed_port else 0)
238 extra_shell_args.append("--origin=" + origin_url) 251 extra_shell_args.append("--origin=" + origin_url)
239 252
240 return extra_shell_args 253 return extra_shell_args
241 254
242 def StartShell(self, 255 def StartShell(self,
243 arguments, 256 arguments,
244 stdout=None, 257 stdout=None,
245 on_application_stop=None, 258 on_application_stop=None,
246 fixed_port=True): 259 fixed_port=True):
247 """ 260 """
248 Starts the mojo shell, passing it the given arguments. 261 Starts the mojo shell, passing it the given arguments.
249 262
250 The |arguments| list must contain the "--origin=" arg from PrepareShellRun. 263 The |arguments| list must contain the "--origin=" arg from PrepareShellRun.
251 If |stdout| is not None, it should be a valid argument for subprocess.Popen. 264 If |stdout| is not None, it should be a valid argument for subprocess.Popen.
252 """ 265 """
253 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % MOJO_SHELL_PACKAGE_NAME 266 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % MOJO_SHELL_PACKAGE_NAME
254 267
255 cmd = [self.adb_path, 268 cmd = self._CreateADBCommand([
256 'shell', 269 'shell',
257 'am', 270 'am',
258 'start', 271 'start',
259 '-S', 272 '-S',
260 '-a', 'android.intent.action.VIEW', 273 '-a', 'android.intent.action.VIEW',
261 '-n', '%s/.MojoShellActivity' % MOJO_SHELL_PACKAGE_NAME] 274 '-n', '%s/.MojoShellActivity' % MOJO_SHELL_PACKAGE_NAME])
262 275
263 parameters = [] 276 parameters = []
264 if stdout or on_application_stop: 277 if stdout or on_application_stop:
265 subprocess.check_call([self.adb_path, 'shell', 'rm', STDOUT_PIPE]) 278 subprocess.check_call(self._CreateADBCommand(
279 ['shell', 'rm', STDOUT_PIPE]))
266 parameters.append('--fifo-path=%s' % STDOUT_PIPE) 280 parameters.append('--fifo-path=%s' % STDOUT_PIPE)
267 self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop) 281 self._ReadFifo(STDOUT_PIPE, stdout, on_application_stop)
268 # The origin has to be specified whether it's local or external. 282 # The origin has to be specified whether it's local or external.
269 assert any("--origin=" in arg for arg in arguments) 283 assert any("--origin=" in arg for arg in arguments)
270 284
271 # Extract map-origin arguments. 285 # Extract map-origin arguments.
272 map_parameters, other_parameters = _Split(arguments, _IsMapOrigin) 286 map_parameters, other_parameters = _Split(arguments, _IsMapOrigin)
273 parameters += other_parameters 287 parameters += other_parameters
274 parameters += self._StartHttpServerForOriginMappings(map_parameters, 288 parameters += self._StartHttpServerForOriginMappings(map_parameters,
275 fixed_port) 289 fixed_port)
276 290
277 if parameters: 291 if parameters:
278 encodedParameters = json.dumps(parameters) 292 encodedParameters = json.dumps(parameters)
279 cmd += ['--es', 'encodedParameters', encodedParameters] 293 cmd += ['--es', 'encodedParameters', encodedParameters]
280 294
281 with open(os.devnull, 'w') as devnull: 295 with open(os.devnull, 'w') as devnull:
282 subprocess.Popen(cmd, stdout=devnull).wait() 296 subprocess.Popen(cmd, stdout=devnull).wait()
283 297
284 def StopShell(self): 298 def StopShell(self):
285 """ 299 """
286 Stops the mojo shell. 300 Stops the mojo shell.
287 """ 301 """
288 subprocess.check_call( 302 subprocess.check_call(self._CreateADBCommand(['shell',
289 [self.adb_path, 'shell', 'am', 'force-stop', MOJO_SHELL_PACKAGE_NAME]) 303 'am',
304 'force-stop',
305 MOJO_SHELL_PACKAGE_NAME]))
290 306
291 def CleanLogs(self): 307 def CleanLogs(self):
292 """ 308 """
293 Cleans the logs on the device. 309 Cleans the logs on the device.
294 """ 310 """
295 subprocess.check_call([self.adb_path, 'logcat', '-c']) 311 subprocess.check_call(self._CreateADBCommand(['logcat', '-c']))
296 312
297 def ShowLogs(self): 313 def ShowLogs(self):
298 """ 314 """
299 Displays the log for the mojo shell. 315 Displays the log for the mojo shell.
300 316
301 Returns the process responsible for reading the logs. 317 Returns the process responsible for reading the logs.
302 """ 318 """
303 logcat = subprocess.Popen([self.adb_path, 319 logcat = subprocess.Popen(self._CreateADBCommand([
304 'logcat', 320 'logcat',
305 '-s', 321 '-s',
306 ' '.join(LOGCAT_TAGS)], 322 ' '.join(LOGCAT_TAGS)]),
307 stdout=sys.stdout) 323 stdout=sys.stdout)
308 atexit.register(_ExitIfNeeded, logcat) 324 atexit.register(_ExitIfNeeded, logcat)
309 return logcat 325 return logcat
OLDNEW
« no previous file with comments | « mojo/tools/android_mojo_shell.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698