Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 from skypy.paths import Paths | 6 from skypy.paths import Paths |
| 7 from skypy.skyserver import SkyServer | 7 from skypy.skyserver import SkyServer |
| 8 import argparse | 8 import argparse |
| 9 import json | 9 import json |
| 10 import logging | 10 import logging |
| 11 import os | 11 import os |
| 12 import pipes | |
| 12 import requests | 13 import requests |
| 13 import signal | 14 import signal |
| 14 import skypy.configuration as configuration | |
| 15 import subprocess | 15 import subprocess |
| 16 import sys | |
| 17 import time | |
| 16 import urlparse | 18 import urlparse |
| 17 import time | 19 |
| 20 sys.path.insert(0, os.path.join(Paths('ignored').src_root, 'build', 'android')) | |
|
eseidel
2015/01/09 22:58:50
As I note in the description, we could *almost* re
| |
| 21 from pylib import android_commands | |
| 22 from pylib import constants | |
| 23 from pylib import forwarder | |
| 18 | 24 |
| 19 | 25 |
| 20 SUPPORTED_MIME_TYPES = [ | 26 SUPPORTED_MIME_TYPES = [ |
| 21 'text/html', | 27 'text/html', |
| 22 'text/sky', | 28 'text/sky', |
| 23 'text/plain', | 29 'text/plain', |
| 24 ] | 30 ] |
| 25 | 31 |
| 26 DEFAULT_SKY_COMMAND_PORT = 7777 | 32 DEFAULT_SKY_COMMAND_PORT = 7777 |
| 27 SKY_SERVER_PORT = 9999 | 33 SKY_SERVER_PORT = 9999 |
| 28 PID_FILE_PATH = "/tmp/skydb.pids" | 34 PID_FILE_PATH = "/tmp/skydb.pids" |
| 29 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky" | 35 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky" |
| 30 | 36 |
| 31 | 37 |
| 38 # FIXME: Move this into mopy.config | |
| 39 def gn_args_from_build_dir(build_dir): | |
| 40 gn_cmd = [ | |
| 41 'gn', 'args', | |
| 42 build_dir, | |
| 43 '--list', '--short' | |
| 44 ] | |
| 45 config = {} | |
| 46 for line in subprocess.check_output(gn_cmd).strip().split('\n'): | |
| 47 # FIXME: This doesn't handle = in values. | |
| 48 key, value = line.split(' = ') | |
| 49 config[key] = value | |
| 50 return config | |
| 51 | |
| 52 | |
| 32 class SkyDebugger(object): | 53 class SkyDebugger(object): |
| 33 def __init__(self): | 54 def __init__(self): |
| 34 self.paths = None | 55 self.paths = None |
| 35 self.pids = {} | 56 self.pids = {} |
| 36 | 57 |
| 37 def _server_root_for_url(self, url_or_path): | 58 def _server_root_for_url(self, url_or_path): |
| 38 path = os.path.abspath(url_or_path) | 59 path = os.path.abspath(url_or_path) |
| 39 if os.path.commonprefix([path, self.paths.src_root]) == self.paths.src_r oot: | 60 if os.path.commonprefix([path, self.paths.src_root]) == self.paths.src_r oot: |
| 40 server_root = self.paths.src_root | 61 server_root = self.paths.src_root |
| 41 else: | 62 else: |
| 42 server_root = os.path.dirname(path) | 63 server_root = os.path.dirname(path) |
| 43 logging.warn( | 64 logging.warn( |
| 44 '%s is outside of mojo root, using %s as server root' % | 65 '%s is outside of mojo root, using %s as server root' % |
| 45 (path, server_root)) | 66 (path, server_root)) |
| 46 return server_root | 67 return server_root |
| 47 | 68 |
| 48 def _in_chromoting(self): | 69 def _in_chromoting(self): |
| 49 return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False) | 70 return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False) |
| 50 | 71 |
| 72 def _wrap_for_android(self, shell_args): | |
| 73 build_dir_url = SkyServer.url_for_path( | |
| 74 self.pids['remote_sky_server_port'], | |
| 75 self.pids['sky_server_root'], | |
| 76 self.pids['build_dir']) | |
| 77 shell_args += ['--origin=%s' % build_dir_url] | |
| 78 | |
| 79 # am shell --esa: (someone shoot me now) | |
| 80 # [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]] | |
| 81 # (to embed a comma into a string escape it using "\,") | |
| 82 escaped_args = map(lambda arg: arg.replace(',', '\\,'), shell_args) | |
| 83 return [ | |
| 84 'adb', 'shell', | |
| 85 'am', 'start', | |
| 86 '-W', | |
| 87 '-S', | |
| 88 '-a', 'android.intent.action.VIEW', | |
| 89 '-n', 'org.chromium.mojo.shell/.MojoShellActivity', | |
| 90 # FIXME: This quoting is very error-prone. Perhaps we should read | |
| 91 # our args from a file instead? | |
| 92 '--esa', 'parameters', ','.join(escaped_args), | |
| 93 ] | |
| 94 | |
| 51 def _build_mojo_shell_command(self, args): | 95 def _build_mojo_shell_command(self, args): |
| 52 self.paths = Paths(os.path.join('out', args.configuration)) | |
| 53 | |
| 54 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') | 96 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') |
| 55 for mime_type in SUPPORTED_MIME_TYPES] | 97 for mime_type in SUPPORTED_MIME_TYPES] |
| 56 shell_command = [ | 98 |
| 57 self.paths.mojo_shell_path, | 99 remote_command_port = self.pids.get('remote_sky_command_port', self.pids ['sky_command_port']) |
| 100 | |
| 101 shell_args = [ | |
| 58 '--v=1', | 102 '--v=1', |
| 59 '--content-handlers=%s' % ','.join(content_handlers), | 103 '--content-handlers=%s' % ','.join(content_handlers), |
| 60 '--url-mappings=mojo:window_manager=mojo:sky_debugger', | 104 '--url-mappings=mojo:window_manager=mojo:sky_debugger', |
| 61 '--args-for=mojo:sky_debugger_prompt %d' % args.command_port, | 105 '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port, |
| 62 'mojo:window_manager', | 106 'mojo:window_manager', |
| 63 ] | 107 ] |
| 108 # FIXME: This probably is wrong for android? | |
| 64 if args.use_osmesa: | 109 if args.use_osmesa: |
| 65 shell_command.append('--args-for=mojo:native_viewport_service --use- osmesa') | 110 shell_args.append('--args-for=mojo:native_viewport_service --use-osm esa') |
| 66 | 111 |
| 112 if 'remote_sky_server_port' in self.pids: | |
| 113 shell_command = self._wrap_for_android(shell_args) | |
| 114 else: | |
| 115 shell_command = [self.paths.mojo_shell_path] + shell_args | |
| 116 | |
| 117 # FIXME: This doesn't work for android | |
| 67 if args.gdb: | 118 if args.gdb: |
| 68 shell_command = ['gdb', '--args'] + shell_command | 119 shell_command = ['gdb', '--args'] + shell_command |
| 69 | 120 |
| 70 return shell_command | 121 return shell_command |
| 71 | 122 |
| 123 def _connect_to_device(self): | |
| 124 device = android_commands.AndroidCommands( | |
| 125 android_commands.GetAttachedDevices()[0]) | |
| 126 device.EnableAdbRoot() | |
| 127 return device | |
| 128 | |
| 129 def sky_server_for_args(self, args): | |
| 130 # FIXME: This is a hack. sky_server should just take a build_dir | |
| 131 # not a magical "configuration" name. | |
| 132 configuration = os.path.basename(os.path.normpath(args.build_dir)) | |
| 133 server_root = self._server_root_for_url(args.url_or_path) | |
| 134 sky_server = SkyServer(self.paths, SKY_SERVER_PORT, | |
| 135 configuration, server_root) | |
| 136 return sky_server | |
| 137 | |
| 72 def start_command(self, args): | 138 def start_command(self, args): |
| 73 shell_command = self._build_mojo_shell_command(args) | 139 # FIXME: Lame that we use self for a command-specific variable. |
| 74 if args.show_command: | 140 self.paths = Paths(args.build_dir) |
|
eseidel
2015/01/09 22:58:50
I removed support for --show-command. Sorry Hans.
| |
| 75 print " ".join(shell_command) | |
| 76 return | |
| 77 | 141 |
| 78 self.stop_command(None) # Quit any existing process. | 142 self.stop_command(None) # Quit any existing process. |
| 143 self.pids = {} # Clear out our pid file. | |
| 79 | 144 |
| 80 print args.url_or_path | 145 # FIXME: This is probably not the right way to compute is_android |
| 81 # We only start a server for paths: | 146 # from the build directory? |
| 82 if not urlparse.urlparse(args.url_or_path).scheme: | 147 gn_args = gn_args_from_build_dir(args.build_dir) |
| 83 server_root = self._server_root_for_url(args.url_or_path) | 148 is_android = 'android_sdk_version' in gn_args |
| 84 sky_server = SkyServer(self.paths, SKY_SERVER_PORT, | |
| 85 args.configuration, server_root) | |
| 86 self.pids['sky_server_pid'] = sky_server.start() | |
| 87 self.pids['sky_server_port'] = sky_server.port | |
| 88 self.pids['sky_server_root'] = sky_server.root | |
| 89 | 149 |
| 150 sky_server = self.sky_server_for_args(args) | |
| 151 self.pids['sky_server_pid'] = sky_server.start() | |
| 152 self.pids['sky_server_port'] = sky_server.port | |
| 153 self.pids['sky_server_root'] = sky_server.root | |
| 154 | |
| 155 self.pids['build_dir'] = args.build_dir | |
| 156 self.pids['sky_command_port'] = args.command_port | |
| 157 | |
| 158 if is_android: | |
| 159 # Pray to the build/android gods in their misspelled tongue. | |
| 160 constants.SetOutputDirectort(args.build_dir) | |
| 161 | |
| 162 device = self._connect_to_device() | |
| 163 self.pids['device_serial'] = device.GetDevice() | |
| 164 | |
| 165 forwarder.Forwarder.Map([(0, sky_server.port)], device) | |
| 166 device_http_port = forwarder.Forwarder.DevicePortForHostPort( | |
| 167 sky_server.port) | |
| 168 self.pids['remote_sky_server_port'] = device_http_port | |
| 169 | |
| 170 port_string = 'tcp:%s' % args.command_port | |
| 171 subprocess.check_call([ | |
| 172 'adb', 'forward', port_string, port_string | |
| 173 ]) | |
| 174 self.pids['remote_sky_command_port'] = args.command_port | |
| 175 | |
| 176 shell_command = self._build_mojo_shell_command(args) | |
| 177 print ' '.join(map(pipes.quote, shell_command)) | |
| 90 self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid | 178 self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid |
| 91 self.pids['sky_command_port'] = args.command_port | |
| 92 | 179 |
| 93 if not self._wait_for_sky_command_port(): | 180 if not self._wait_for_sky_command_port(): |
| 94 logging.error('Failed to start sky') | 181 logging.error('Failed to start sky') |
| 95 self.stop_command(None) | 182 self.stop_command(None) |
| 96 else: | 183 else: |
| 97 self.load_command(args) | 184 self.load_command(args) |
| 98 | 185 |
| 99 def _kill_if_exists(self, key, name): | 186 def _kill_if_exists(self, key, name): |
| 100 pid = self.pids.pop(key, None) | 187 pid = self.pids.pop(key, None) |
| 101 if not pid: | 188 if not pid: |
| 102 logging.info('No pid for %s, nothing to do.' % name) | 189 logging.info('No pid for %s, nothing to do.' % name) |
| 103 return | 190 return |
| 104 logging.info('Killing %s (%s).' % (name, pid)) | 191 logging.info('Killing %s (%s).' % (name, pid)) |
| 105 try: | 192 try: |
| 106 os.kill(pid, signal.SIGTERM) | 193 os.kill(pid, signal.SIGTERM) |
| 107 except OSError: | 194 except OSError: |
| 108 logging.info('%s (%s) already gone.' % (name, pid)) | 195 logging.info('%s (%s) already gone.' % (name, pid)) |
| 109 | 196 |
| 110 def stop_command(self, args): | 197 def stop_command(self, args): |
| 111 # FIXME: Send /quit to sky prompt instead of killing. | 198 try: |
| 112 # self._send_command_to_sky('/quit') | 199 self._send_command_to_sky('/quit') |
| 200 except: | |
| 201 pass | |
| 202 # Kill the mojo process for good measure. :) | |
| 113 self._kill_if_exists('mojo_shell_pid', 'mojo_shell') | 203 self._kill_if_exists('mojo_shell_pid', 'mojo_shell') |
| 204 | |
| 114 self._kill_if_exists('sky_server_pid', 'sky_server') | 205 self._kill_if_exists('sky_server_pid', 'sky_server') |
| 206 # We could be much more surgical here: | |
| 207 if 'remote_sky_command_port' in self.pids: | |
| 208 device = android_commands.AndroidCommands( | |
| 209 self.pids['device_serial']) | |
| 210 forwarder.Forwarder.UnmapAllDevicePorts(device) | |
| 211 | |
| 212 if 'remote_sky_command_port' in self.pids: | |
| 213 # adb forward --remove takes the *host* port, not the remote port. | |
| 214 port_string = 'tcp:%s' % self.pids['sky_command_port'] | |
| 215 subprocess.call(['adb', 'forward', '--remove', port_string]) | |
| 115 | 216 |
| 116 def load_command(self, args): | 217 def load_command(self, args): |
| 117 if not urlparse.urlparse(args.url_or_path).scheme: | 218 if not urlparse.urlparse(args.url_or_path).scheme: |
| 118 url = SkyServer.url_for_path(self.pids['sky_server_port'], | 219 # The load happens on the remote device, use the remote port. |
| 220 remote_sky_server_port = self.pids.get('remote_sky_server_port', | |
| 221 self.pids['sky_server_port']) | |
| 222 url = SkyServer.url_for_path(remote_sky_server_port, | |
| 119 self.pids['sky_server_root'], args.url_or_path) | 223 self.pids['sky_server_root'], args.url_or_path) |
| 120 else: | 224 else: |
| 121 url = args.url_or_path | 225 url = args.url_or_path |
| 122 self._send_command_to_sky('/load', url) | 226 self._send_command_to_sky('/load', url) |
| 123 | 227 |
| 124 def _command_base_url(self): | 228 def _command_base_url(self): |
| 125 return 'http://localhost:%s' % self.pids['sky_command_port'] | 229 return 'http://localhost:%s' % self.pids['sky_command_port'] |
| 126 | 230 |
| 127 def _send_command_to_sky(self, command_path, payload=None): | 231 def _send_command_to_sky(self, command_path, payload=None): |
| 128 url = 'http://localhost:%s%s' % ( | 232 url = 'http://localhost:%s%s' % ( |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 139 with open(path, 'r') as pid_file: | 243 with open(path, 'r') as pid_file: |
| 140 return json.load(pid_file) | 244 return json.load(pid_file) |
| 141 except: | 245 except: |
| 142 if os.path.exists(path): | 246 if os.path.exists(path): |
| 143 logging.warn('Failed to read pid file: %s' % path) | 247 logging.warn('Failed to read pid file: %s' % path) |
| 144 return {} | 248 return {} |
| 145 | 249 |
| 146 def _write_pid_file(self, path, pids): | 250 def _write_pid_file(self, path, pids): |
| 147 try: | 251 try: |
| 148 with open(path, 'w') as pid_file: | 252 with open(path, 'w') as pid_file: |
| 149 json.dump(pids, pid_file) | 253 json.dump(pids, pid_file, indent=2, sort_keys=True) |
| 150 except: | 254 except: |
| 151 logging.warn('Failed to write pid file: %s' % path) | 255 logging.warn('Failed to write pid file: %s' % path) |
| 152 | 256 |
| 153 def _add_basic_command(self, subparsers, name, url_path, help_text): | 257 def _add_basic_command(self, subparsers, name, url_path, help_text): |
| 154 parser = subparsers.add_parser(name, help=help_text) | 258 parser = subparsers.add_parser(name, help=help_text) |
| 155 command = lambda args: self._send_command_to_sky(url_path) | 259 command = lambda args: self._send_command_to_sky(url_path) |
| 156 parser.set_defaults(func=command) | 260 parser.set_defaults(func=command) |
| 157 | 261 |
| 158 def _wait_for_sky_command_port(self): | 262 def _wait_for_sky_command_port(self): |
| 159 tries = 0 | 263 tries = 0 |
| 160 while True: | 264 while True: |
| 161 try: | 265 try: |
| 162 self._send_command_to_sky('/') | 266 self._send_command_to_sky('/') |
| 163 return True | 267 return True |
| 164 except: | 268 except: |
| 165 tries += 1 | 269 tries += 1 |
| 166 if tries == 3: | 270 if tries == 3: |
| 167 logging.warn('Still waiting for sky on port %s' % | 271 logging.warn('Still waiting for sky on port %s' % |
| 168 self.pids['sky_command_port']) | 272 self.pids['sky_command_port']) |
| 169 if tries > 10: | 273 if tries > 10: |
| 170 return False | 274 return False |
| 171 time.sleep(1) | 275 time.sleep(1) |
| 172 | 276 |
| 173 def main(self): | 277 def main(self): |
| 174 logging.basicConfig(level=logging.INFO) | 278 logging.basicConfig(level=logging.INFO) |
| 279 logging.getLogger("requests").setLevel(logging.WARNING) | |
| 175 | 280 |
| 176 self.pids = self._load_pid_file(PID_FILE_PATH) | 281 self.pids = self._load_pid_file(PID_FILE_PATH) |
| 177 | 282 |
| 178 parser = argparse.ArgumentParser(description='Sky launcher/debugger') | 283 parser = argparse.ArgumentParser(description='Sky launcher/debugger') |
| 179 subparsers = parser.add_subparsers(help='sub-command help') | 284 subparsers = parser.add_subparsers(help='sub-command help') |
| 180 | 285 |
| 181 start_parser = subparsers.add_parser('start', | 286 start_parser = subparsers.add_parser('start', |
| 182 help='launch a new mojo_shell with sky') | 287 help='launch a new mojo_shell with sky') |
| 183 configuration.add_arguments(start_parser) | |
| 184 start_parser.add_argument('--gdb', action='store_true') | 288 start_parser.add_argument('--gdb', action='store_true') |
| 185 start_parser.add_argument('--command-port', type=int, | 289 start_parser.add_argument('--command-port', type=int, |
| 186 default=DEFAULT_SKY_COMMAND_PORT) | 290 default=DEFAULT_SKY_COMMAND_PORT) |
| 187 start_parser.add_argument('--use-osmesa', action='store_true', | 291 start_parser.add_argument('--use-osmesa', action='store_true', |
| 188 default=self._in_chromoting()) | 292 default=self._in_chromoting()) |
| 293 start_parser.add_argument('build_dir', type=str) | |
| 189 start_parser.add_argument('url_or_path', nargs='?', type=str, | 294 start_parser.add_argument('url_or_path', nargs='?', type=str, |
| 190 default=DEFAULT_URL) | 295 default=DEFAULT_URL) |
| 191 start_parser.add_argument('--show-command', action='store_true', | 296 start_parser.add_argument('--show-command', action='store_true', |
| 192 help='Display the shell command and exit') | 297 help='Display the shell command and exit') |
| 193 start_parser.set_defaults(func=self.start_command) | 298 start_parser.set_defaults(func=self.start_command) |
| 194 | 299 |
| 195 stop_parser = subparsers.add_parser('stop', | 300 stop_parser = subparsers.add_parser('stop', |
| 196 help=('stop sky (as listed in %s)' % PID_FILE_PATH)) | 301 help=('stop sky (as listed in %s)' % PID_FILE_PATH)) |
| 197 stop_parser.set_defaults(func=self.stop_command) | 302 stop_parser.set_defaults(func=self.stop_command) |
| 198 | 303 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 209 load_parser.set_defaults(func=self.load_command) | 314 load_parser.set_defaults(func=self.load_command) |
| 210 | 315 |
| 211 args = parser.parse_args() | 316 args = parser.parse_args() |
| 212 args.func(args) | 317 args.func(args) |
| 213 | 318 |
| 214 self._write_pid_file(PID_FILE_PATH, self.pids) | 319 self._write_pid_file(PID_FILE_PATH, self.pids) |
| 215 | 320 |
| 216 | 321 |
| 217 if __name__ == '__main__': | 322 if __name__ == '__main__': |
| 218 SkyDebugger().main() | 323 SkyDebugger().main() |
| OLD | NEW |