| OLD | NEW |
| 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 hashlib | 6 import hashlib |
| 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 |
| 11 import random | 11 import random |
| 12 import re | 12 import re |
| 13 import subprocess | 13 import subprocess |
| 14 import sys | 14 import sys |
| 15 import tempfile | 15 import tempfile |
| 16 import threading | 16 import threading |
| 17 import time | 17 import time |
| 18 | 18 |
| 19 from devtoolslib.http_server import StartHttpServer | 19 from devtoolslib.http_server import start_http_server |
| 20 from devtoolslib.shell import Shell | 20 from devtoolslib.shell import Shell |
| 21 | 21 |
| 22 | 22 |
| 23 # Tags used by mojo shell Java logging. | 23 # Tags used by mojo shell Java logging. |
| 24 _LOGCAT_JAVA_TAGS = [ | 24 _LOGCAT_JAVA_TAGS = [ |
| 25 'AndroidHandler', | 25 'AndroidHandler', |
| 26 'MojoFileHelper', | 26 'MojoFileHelper', |
| 27 'MojoMain', | 27 'MojoMain', |
| 28 'MojoShellActivity', | 28 'MojoShellActivity', |
| 29 'MojoShellApplication', | 29 'MojoShellApplication', |
| 30 ] | 30 ] |
| 31 | 31 |
| 32 # Tags used by native logging reflected in the logcat. | 32 # Tags used by native logging reflected in the logcat. |
| 33 _LOGCAT_NATIVE_TAGS = [ | 33 _LOGCAT_NATIVE_TAGS = [ |
| 34 'chromium', | 34 'chromium', |
| 35 'sky', | 35 'sky', |
| 36 ] | 36 ] |
| 37 | 37 |
| 38 _MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' | 38 _MOJO_SHELL_PACKAGE_NAME = 'org.chromium.mojo.shell' |
| 39 | 39 |
| 40 | 40 |
| 41 _logger = logging.getLogger() | 41 _logger = logging.getLogger() |
| 42 | 42 |
| 43 | 43 |
| 44 def _ExitIfNeeded(process): | 44 def _exit_if_needed(process): |
| 45 """Exits |process| if it is still alive.""" | 45 """Exits |process| if it is still alive.""" |
| 46 if process.poll() is None: | 46 if process.poll() is None: |
| 47 process.kill() | 47 process.kill() |
| 48 | 48 |
| 49 | 49 |
| 50 def _FindAvailablePort(netstat_output, max_attempts=10000): | 50 def _find_available_port(netstat_output, max_attempts=10000): |
| 51 opened = [int(x.strip().split()[3].split(':')[1]) | 51 opened = [int(x.strip().split()[3].split(':')[1]) |
| 52 for x in netstat_output if x.startswith(' tcp')] | 52 for x in netstat_output if x.startswith(' tcp')] |
| 53 for _ in xrange(max_attempts): | 53 for _ in xrange(max_attempts): |
| 54 port = random.randint(4096, 16384) | 54 port = random.randint(4096, 16384) |
| 55 if port not in opened: | 55 if port not in opened: |
| 56 return port | 56 return port |
| 57 else: | 57 else: |
| 58 raise Exception('Failed to identify an available port.') | 58 raise Exception('Failed to identify an available port.') |
| 59 | 59 |
| 60 | 60 |
| 61 def _FindAvailableHostPort(): | 61 def _find_available_host_port(): |
| 62 netstat_output = subprocess.check_output(['netstat']) | 62 netstat_output = subprocess.check_output(['netstat']) |
| 63 return _FindAvailablePort(netstat_output) | 63 return _find_available_port(netstat_output) |
| 64 | 64 |
| 65 | 65 |
| 66 class AndroidShell(Shell): | 66 class AndroidShell(Shell): |
| 67 """Wrapper around Mojo shell running on an Android device. | 67 """Wrapper around Mojo shell running on an Android device. |
| 68 | 68 |
| 69 Args: | 69 Args: |
| 70 adb_path: Path to adb, optional if adb is in PATH. | 70 adb_path: Path to adb, optional if adb is in PATH. |
| 71 target_device: Device to run on, if multiple devices are connected. | 71 target_device: Device to run on, if multiple devices are connected. |
| 72 logcat_tags: Comma-separated list of additional logcat tags to use. | 72 logcat_tags: Comma-separated list of additional logcat tags to use. |
| 73 """ | 73 """ |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 106 for _ in xrange(max_attempts): | 106 for _ in xrange(max_attempts): |
| 107 if subprocess.check_output(fifo_command)[0] == '0': | 107 if subprocess.check_output(fifo_command)[0] == '0': |
| 108 return | 108 return |
| 109 time.sleep(1) | 109 time.sleep(1) |
| 110 if on_fifo_closed: | 110 if on_fifo_closed: |
| 111 on_fifo_closed() | 111 on_fifo_closed() |
| 112 raise Exception("Unable to find fifo.") | 112 raise Exception("Unable to find fifo.") |
| 113 _WaitForFifo() | 113 _WaitForFifo() |
| 114 stdout_cat = subprocess.Popen( | 114 stdout_cat = subprocess.Popen( |
| 115 self._AdbCommand(['shell', 'cat', fifo_path]), stdout=pipe) | 115 self._AdbCommand(['shell', 'cat', fifo_path]), stdout=pipe) |
| 116 atexit.register(_ExitIfNeeded, stdout_cat) | 116 atexit.register(_exit_if_needed, stdout_cat) |
| 117 stdout_cat.wait() | 117 stdout_cat.wait() |
| 118 if on_fifo_closed: | 118 if on_fifo_closed: |
| 119 on_fifo_closed() | 119 on_fifo_closed() |
| 120 | 120 |
| 121 thread = threading.Thread(target=Run, name="StdoutRedirector") | 121 thread = threading.Thread(target=Run, name="StdoutRedirector") |
| 122 thread.start() | 122 thread.start() |
| 123 | 123 |
| 124 def _FindAvailableDevicePort(self): | 124 def _FindAvailableDevicePort(self): |
| 125 netstat_output = subprocess.check_output( | 125 netstat_output = subprocess.check_output( |
| 126 self._AdbCommand(['shell', 'netstat'])) | 126 self._AdbCommand(['shell', 'netstat'])) |
| 127 return _FindAvailablePort(netstat_output) | 127 return _find_available_port(netstat_output) |
| 128 | 128 |
| 129 def _ForwardDevicePortToHost(self, device_port, host_port): | 129 def _ForwardDevicePortToHost(self, device_port, host_port): |
| 130 """Maps the device port to the host port. If |device_port| is 0, a random | 130 """Maps the device port to the host port. If |device_port| is 0, a random |
| 131 available port is chosen. | 131 available port is chosen. |
| 132 | 132 |
| 133 Returns: | 133 Returns: |
| 134 The device port. | 134 The device port. |
| 135 """ | 135 """ |
| 136 assert host_port | 136 assert host_port |
| 137 # Root is not required for `adb forward` (hence we don't check the return | 137 # Root is not required for `adb forward` (hence we don't check the return |
| (...skipping 20 matching lines...) Expand all Loading... |
| 158 available port is chosen. | 158 available port is chosen. |
| 159 | 159 |
| 160 Returns: | 160 Returns: |
| 161 The host port. | 161 The host port. |
| 162 """ | 162 """ |
| 163 assert device_port | 163 assert device_port |
| 164 self._RunAdbAsRoot() | 164 self._RunAdbAsRoot() |
| 165 | 165 |
| 166 if host_port == 0: | 166 if host_port == 0: |
| 167 # TODO(ppi): Should we have a retry loop to handle the unlikely races? | 167 # TODO(ppi): Should we have a retry loop to handle the unlikely races? |
| 168 host_port = _FindAvailableHostPort() | 168 host_port = _find_available_host_port() |
| 169 subprocess.check_call(self._AdbCommand([ | 169 subprocess.check_call(self._AdbCommand([ |
| 170 "forward", 'tcp:%d' % host_port, 'tcp:%d' % device_port])) | 170 "forward", 'tcp:%d' % host_port, 'tcp:%d' % device_port])) |
| 171 | 171 |
| 172 def _UnmapPort(): | 172 def _UnmapPort(): |
| 173 unmap_command = self._AdbCommand([ | 173 unmap_command = self._AdbCommand([ |
| 174 "forward", "--remove", "tcp:%d" % device_port]) | 174 "forward", "--remove", "tcp:%d" % device_port]) |
| 175 subprocess.Popen(unmap_command) | 175 subprocess.Popen(unmap_command) |
| 176 atexit.register(_UnmapPort) | 176 atexit.register(_UnmapPort) |
| 177 return host_port | 177 return host_port |
| 178 | 178 |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 272 self.StopShell() | 272 self.StopShell() |
| 273 | 273 |
| 274 def StartShell(self, | 274 def StartShell(self, |
| 275 arguments, | 275 arguments, |
| 276 stdout=None, | 276 stdout=None, |
| 277 on_application_stop=None): | 277 on_application_stop=None): |
| 278 """Starts the mojo shell, passing it the given arguments. | 278 """Starts the mojo shell, passing it the given arguments. |
| 279 | 279 |
| 280 Args: | 280 Args: |
| 281 arguments: List of arguments for the shell. It must contain the | 281 arguments: List of arguments for the shell. It must contain the |
| 282 "--origin=" arg. shell_arguments.ConfigureLocalOrigin() can be used | 282 "--origin=" arg. shell_arguments.configure_local_origin() can be used |
| 283 to set up a local directory on the host machine as origin. | 283 to set up a local directory on the host machine as origin. |
| 284 stdout: Valid argument for subprocess.Popen() or None. | 284 stdout: Valid argument for subprocess.Popen() or None. |
| 285 """ | 285 """ |
| 286 if not self.stop_shell_registered: | 286 if not self.stop_shell_registered: |
| 287 atexit.register(self.StopShell) | 287 atexit.register(self.StopShell) |
| 288 self.stop_shell_registered = True | 288 self.stop_shell_registered = True |
| 289 | 289 |
| 290 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % _MOJO_SHELL_PACKAGE_NAME | 290 STDOUT_PIPE = "/data/data/%s/stdout.fifo" % _MOJO_SHELL_PACKAGE_NAME |
| 291 | 291 |
| 292 cmd = self._AdbCommand(['shell', 'am', 'start', | 292 cmd = self._AdbCommand(['shell', 'am', 'start', |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 337 The process responsible for reading the logs. | 337 The process responsible for reading the logs. |
| 338 """ | 338 """ |
| 339 tags = _LOGCAT_JAVA_TAGS | 339 tags = _LOGCAT_JAVA_TAGS |
| 340 if include_native_logs: | 340 if include_native_logs: |
| 341 tags.extend(_LOGCAT_NATIVE_TAGS) | 341 tags.extend(_LOGCAT_NATIVE_TAGS) |
| 342 if self.additional_logcat_tags is not None: | 342 if self.additional_logcat_tags is not None: |
| 343 tags.extend(self.additional_logcat_tags.split(",")) | 343 tags.extend(self.additional_logcat_tags.split(",")) |
| 344 logcat = subprocess.Popen( | 344 logcat = subprocess.Popen( |
| 345 self._AdbCommand(['logcat', '-s', ' '.join(tags)]), | 345 self._AdbCommand(['logcat', '-s', ' '.join(tags)]), |
| 346 stdout=sys.stdout) | 346 stdout=sys.stdout) |
| 347 atexit.register(_ExitIfNeeded, logcat) | 347 atexit.register(_exit_if_needed, logcat) |
| 348 return logcat | 348 return logcat |
| 349 | 349 |
| 350 def ForwardObservatoryPorts(self): | 350 def ForwardObservatoryPorts(self): |
| 351 """Forwards the ports used by the dart observatories to the host machine. | 351 """Forwards the ports used by the dart observatories to the host machine. |
| 352 """ | 352 """ |
| 353 logcat = subprocess.Popen(self._AdbCommand(['logcat']), | 353 logcat = subprocess.Popen(self._AdbCommand(['logcat']), |
| 354 stdout=subprocess.PIPE) | 354 stdout=subprocess.PIPE) |
| 355 atexit.register(_ExitIfNeeded, logcat) | 355 atexit.register(_exit_if_needed, logcat) |
| 356 | 356 |
| 357 def _ForwardObservatoriesAsNeeded(): | 357 def _ForwardObservatoriesAsNeeded(): |
| 358 while True: | 358 while True: |
| 359 line = logcat.stdout.readline() | 359 line = logcat.stdout.readline() |
| 360 if not line: | 360 if not line: |
| 361 break | 361 break |
| 362 match = re.search(r'Observatory listening on http://127.0.0.1:(\d+)', | 362 match = re.search(r'Observatory listening on http://127.0.0.1:(\d+)', |
| 363 line) | 363 line) |
| 364 if match: | 364 if match: |
| 365 device_port = int(match.group(1)) | 365 device_port = int(match.group(1)) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 382 local_dir_path: path to the directory to be served | 382 local_dir_path: path to the directory to be served |
| 383 port: port at which the server will be available to the shell | 383 port: port at which the server will be available to the shell |
| 384 additional_mappings: List of tuples (prefix, local_base_path) mapping | 384 additional_mappings: List of tuples (prefix, local_base_path) mapping |
| 385 URLs that start with |prefix| to local directory at |local_base_path|. | 385 URLs that start with |prefix| to local directory at |local_base_path|. |
| 386 The prefixes should skip the leading slash. | 386 The prefixes should skip the leading slash. |
| 387 | 387 |
| 388 Returns: | 388 Returns: |
| 389 The url that the shell can use to access the content of |local_dir_path|. | 389 The url that the shell can use to access the content of |local_dir_path|. |
| 390 """ | 390 """ |
| 391 assert local_dir_path | 391 assert local_dir_path |
| 392 server_address = StartHttpServer(local_dir_path, host_port=port, | 392 server_address = start_http_server(local_dir_path, host_port=port, |
| 393 additional_mappings=additional_mappings) | 393 additional_mappings=additional_mappings) |
| 394 | 394 |
| 395 return 'http://127.0.0.1:%d/' % self._ForwardDevicePortToHost( | 395 return 'http://127.0.0.1:%d/' % self._ForwardDevicePortToHost( |
| 396 port, server_address[1]) | 396 port, server_address[1]) |
| 397 | 397 |
| 398 def ForwardHostPortToShell(self, host_port): | 398 def ForwardHostPortToShell(self, host_port): |
| 399 """Forwards a port on the host machine to the same port wherever the shell | 399 """Forwards a port on the host machine to the same port wherever the shell |
| 400 is running. | 400 is running. |
| 401 | 401 |
| 402 This is a no-op if the shell is running locally. | 402 This is a no-op if the shell is running locally. |
| 403 """ | 403 """ |
| (...skipping 27 matching lines...) Expand all Loading... |
| 431 A tuple of (return_code, output). |return_code| is the exit code returned | 431 A tuple of (return_code, output). |return_code| is the exit code returned |
| 432 by the shell or None if the exit code cannot be retrieved. |output| is the | 432 by the shell or None if the exit code cannot be retrieved. |output| is the |
| 433 stdout mingled with the stderr produced by the shell. | 433 stdout mingled with the stderr produced by the shell. |
| 434 """ | 434 """ |
| 435 (r, w) = os.pipe() | 435 (r, w) = os.pipe() |
| 436 with os.fdopen(r, "r") as rf: | 436 with os.fdopen(r, "r") as rf: |
| 437 with os.fdopen(w, "w") as wf: | 437 with os.fdopen(w, "w") as wf: |
| 438 self.StartShell(arguments, wf, wf.close) | 438 self.StartShell(arguments, wf, wf.close) |
| 439 output = rf.read() | 439 output = rf.read() |
| 440 return None, output | 440 return None, output |
| OLD | NEW |