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.skyserver import SkyServer | 6 from skypy.skyserver import SkyServer |
7 import argparse | 7 import argparse |
8 import json | 8 import json |
9 import logging | 9 import logging |
10 import os | 10 import os |
11 import pipes | 11 import pipes |
12 import requests | 12 import requests |
13 import signal | 13 import signal |
14 import skypy.paths | 14 import skypy.paths |
15 import StringIO | 15 import StringIO |
16 import subprocess | 16 import subprocess |
17 import sys | 17 import sys |
18 import time | 18 import time |
19 import urlparse | 19 import urlparse |
20 import re | |
20 | 21 |
21 SRC_ROOT = skypy.paths.Paths('ignored').src_root | 22 SRC_ROOT = skypy.paths.Paths('ignored').src_root |
22 sys.path.insert(0, os.path.join(SRC_ROOT, 'build', 'android')) | 23 sys.path.insert(0, os.path.join(SRC_ROOT, 'build', 'android')) |
23 from pylib import android_commands | 24 from pylib import android_commands |
24 from pylib import constants | 25 from pylib import constants |
25 from pylib import forwarder | 26 from pylib import forwarder |
26 | 27 |
27 | 28 |
28 SUPPORTED_MIME_TYPES = [ | 29 SUPPORTED_MIME_TYPES = [ |
29 'text/html', | 30 'text/html', |
30 'text/sky', | 31 'text/sky', |
31 'text/plain', | 32 'text/plain', |
32 ] | 33 ] |
33 | 34 |
34 DEFAULT_SKY_COMMAND_PORT = 7777 | 35 DEFAULT_SKY_COMMAND_PORT = 7777 |
35 GDB_PORT = 8888 | 36 GDB_PORT = 8888 |
36 SKY_SERVER_PORT = 9999 | 37 SKY_SERVER_PORT = 9999 |
37 PID_FILE_PATH = "/tmp/skydb.pids" | 38 PID_FILE_PATH = "/tmp/skydb.pids" |
38 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky" | 39 DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/example s/home.sky" |
39 | 40 |
40 ANDROID_PACKAGE = "org.chromium.mojo.shell" | 41 ANDROID_PACKAGE = "org.chromium.mojo.shell" |
41 ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE | 42 ANDROID_ACTIVITY = "%s/.MojoShellActivity" % ANDROID_PACKAGE |
42 | 43 |
44 | |
43 # FIXME: Move this into mopy.config | 45 # FIXME: Move this into mopy.config |
44 def gn_args_from_build_dir(build_dir): | 46 def gn_args_from_build_dir(build_dir): |
45 gn_cmd = [ | 47 gn_cmd = [ |
46 'gn', 'args', | 48 'gn', 'args', |
47 build_dir, | 49 build_dir, |
48 '--list', '--short' | 50 '--list', '--short' |
49 ] | 51 ] |
50 config = {} | 52 config = {} |
51 for line in subprocess.check_output(gn_cmd).strip().split('\n'): | 53 for line in subprocess.check_output(gn_cmd).strip().split('\n'): |
52 # FIXME: This doesn't handle = in values. | 54 # FIXME: This doesn't handle = in values. |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
90 'am', 'start', | 92 'am', 'start', |
91 '-W', | 93 '-W', |
92 '-S', | 94 '-S', |
93 '-a', 'android.intent.action.VIEW', | 95 '-a', 'android.intent.action.VIEW', |
94 '-n', ANDROID_ACTIVITY, | 96 '-n', ANDROID_ACTIVITY, |
95 # FIXME: This quoting is very error-prone. Perhaps we should read | 97 # FIXME: This quoting is very error-prone. Perhaps we should read |
96 # our args from a file instead? | 98 # our args from a file instead? |
97 '--esa', 'parameters', ','.join(escaped_args), | 99 '--esa', 'parameters', ','.join(escaped_args), |
98 ] | 100 ] |
99 | 101 |
100 def _build_mojo_shell_command(self, args): | 102 def _build_mojo_shell_command(self, args, is_android): |
101 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') | 103 content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') |
102 for mime_type in SUPPORTED_MIME_TYPES] | 104 for mime_type in SUPPORTED_MIME_TYPES] |
103 | 105 |
104 remote_command_port = self.pids.get('remote_sky_command_port', self.pids ['sky_command_port']) | 106 remote_command_port = self.pids.get('remote_sky_command_port', self.pids ['sky_command_port']) |
105 | 107 |
106 shell_args = [ | 108 shell_args = [ |
107 '--v=1', | 109 '--v=1', |
108 '--content-handlers=%s' % ','.join(content_handlers), | 110 '--content-handlers=%s' % ','.join(content_handlers), |
109 '--url-mappings=mojo:window_manager=mojo:sky_debugger', | 111 '--url-mappings=mojo:window_manager=mojo:sky_debugger', |
110 '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port, | 112 '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port, |
111 'mojo:window_manager', | 113 'mojo:window_manager', |
112 ] | 114 ] |
113 | 115 |
116 # Desktop-only work-around for mojo crashing under chromoting. | |
117 if not is_android and args.use_osmesa: | |
118 shell_args.append( | |
119 '--args-for=mojo:native_viewport_service --use-osmesa') | |
120 | |
121 if is_android and args.gdb: | |
122 shell_args.append('--wait_for_debugger') | |
123 | |
114 if 'remote_sky_server_port' in self.pids: | 124 if 'remote_sky_server_port' in self.pids: |
115 shell_command = self._wrap_for_android(shell_args) | 125 shell_command = self._wrap_for_android(shell_args) |
116 else: | 126 else: |
117 shell_command = [self.paths.mojo_shell_path] + shell_args | 127 shell_command = [self.paths.mojo_shell_path] + shell_args |
118 | 128 |
119 return shell_command | 129 return shell_command |
120 | 130 |
121 def _connect_to_device(self): | 131 def _connect_to_device(self): |
122 device = android_commands.AndroidCommands( | 132 device = android_commands.AndroidCommands( |
123 android_commands.GetAttachedDevices()[0]) | 133 android_commands.GetAttachedDevices()[0]) |
124 device.EnableAdbRoot() | 134 device.EnableAdbRoot() |
125 return device | 135 return device |
126 | 136 |
127 def sky_server_for_args(self, args): | 137 def sky_server_for_args(self, args): |
128 # FIXME: This is a hack. sky_server should just take a build_dir | 138 # FIXME: This is a hack. sky_server should just take a build_dir |
129 # not a magical "configuration" name. | 139 # not a magical "configuration" name. |
130 configuration = os.path.basename(os.path.normpath(args.build_dir)) | 140 configuration = os.path.basename(os.path.normpath(args.build_dir)) |
131 server_root = self._server_root_for_url(args.url_or_path) | 141 server_root = self._server_root_for_url(args.url_or_path) |
132 sky_server = SkyServer(self.paths, SKY_SERVER_PORT, | 142 sky_server = SkyServer(self.paths, SKY_SERVER_PORT, |
133 configuration, server_root) | 143 configuration, server_root) |
134 return sky_server | 144 return sky_server |
135 | 145 |
136 def _create_paths_for_build_dir(self, build_dir): | 146 def _create_paths_for_build_dir(self, build_dir): |
137 # skypy.paths.Paths takes a root-relative build_dir argument. :( | 147 # skypy.paths.Paths takes a root-relative build_dir argument. :( |
138 abs_build_dir = os.path.abspath(build_dir) | 148 abs_build_dir = os.path.abspath(build_dir) |
139 root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT) | 149 root_relative_build_dir = os.path.relpath(abs_build_dir, SRC_ROOT) |
140 return skypy.paths.Paths(root_relative_build_dir) | 150 return skypy.paths.Paths(root_relative_build_dir) |
141 | 151 |
152 def _find_remote_pid_for_package(self, package): | |
153 ps_output = subprocess.check_output(['adb', 'shell', 'ps']) | |
154 for line in ps_output.split('\n'): | |
155 fields = line.split() | |
156 if fields and fields[-1] == package: | |
157 return fields[1] | |
158 return None | |
159 | |
160 def _find_install_location_for_package(self, package): | |
161 pm_command = ['adb', 'shell', 'pm', 'path', package] | |
162 pm_output = subprocess.check_output(pm_command) | |
163 # e.g. package:/data/app/org.chromium.mojo.shell-1/base.apk | |
164 return pm_output.split(':')[-1] | |
165 | |
142 def start_command(self, args): | 166 def start_command(self, args): |
143 # FIXME: Lame that we use self for a command-specific variable. | 167 # FIXME: Lame that we use self for a command-specific variable. |
144 self.paths = self._create_paths_for_build_dir(args.build_dir) | 168 self.paths = self._create_paths_for_build_dir(args.build_dir) |
145 self.stop_command(None) # Quit any existing process. | 169 self.stop_command(None) # Quit any existing process. |
146 self.pids = {} # Clear out our pid file. | 170 self.pids = {} # Clear out our pid file. |
147 | 171 |
148 if not os.path.exists(self.paths.mojo_shell_path): | 172 if not os.path.exists(self.paths.mojo_shell_path): |
149 print "mojo_shell not found in build_dir '%s'" % args.build_dir | 173 print "mojo_shell not found in build_dir '%s'" % args.build_dir |
150 print "Are you sure you sure that's a valid build_dir location?" | 174 print "Are you sure you sure that's a valid build_dir location?" |
151 print "See skydb start --help for more info" | 175 print "See skydb start --help for more info" |
(...skipping 23 matching lines...) Expand all Loading... | |
175 device_http_port = forwarder.Forwarder.DevicePortForHostPort( | 199 device_http_port = forwarder.Forwarder.DevicePortForHostPort( |
176 sky_server.port) | 200 sky_server.port) |
177 self.pids['remote_sky_server_port'] = device_http_port | 201 self.pids['remote_sky_server_port'] = device_http_port |
178 | 202 |
179 port_string = 'tcp:%s' % args.command_port | 203 port_string = 'tcp:%s' % args.command_port |
180 subprocess.check_call([ | 204 subprocess.check_call([ |
181 'adb', 'forward', port_string, port_string | 205 'adb', 'forward', port_string, port_string |
182 ]) | 206 ]) |
183 self.pids['remote_sky_command_port'] = args.command_port | 207 self.pids['remote_sky_command_port'] = args.command_port |
184 | 208 |
185 shell_command = self._build_mojo_shell_command(args) | 209 shell_command = self._build_mojo_shell_command(args, is_android) |
186 | 210 |
187 if not is_android: | 211 # On android we can't launch inside gdb, but rather have to attach. |
188 # Desktop-only work-around for mojo crashing under chromoting. | 212 if not is_android and args.gdb: |
189 if args.use_osmesa: | 213 shell_command = ['gdbserver', ':%d' % GDB_PORT] + shell_command |
190 shell_args.append( | |
191 '--args-for=mojo:native_viewport_service --use-osmesa') | |
192 | |
193 # On android we can't launch inside gdb, but rather have to attach. | |
194 if args.gdb: | |
195 shell_command = ['gdbserver', ':%s' % GDB_PORT] + shell_command | |
196 | 214 |
197 print ' '.join(map(pipes.quote, shell_command)) | 215 print ' '.join(map(pipes.quote, shell_command)) |
198 self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid | 216 # This pid is meaningless on android (it's the adb shell pid) |
217 start_command_pid = subprocess.Popen(shell_command).pid | |
218 | |
219 if is_android: | |
220 # TODO(eseidel): am start -W does not seem to work? | |
221 pid_tries = 0 | |
222 while True: | |
223 pid = self._find_remote_pid_for_package(ANDROID_PACKAGE) | |
224 if pid or pid_tries > 3: | |
225 break | |
226 logging.warn('No pid for %s yet, waiting' % ANDROID_PACKAGE) | |
227 time.sleep(5) | |
228 pid_tries += 1 | |
229 | |
230 if not pid: | |
231 logging.error('Failed to find mojo_shell pid on device!') | |
232 return | |
233 self.pids['mojo_shell_pid'] = pid | |
234 else: | |
235 self.pids['mojo_shell_pid'] = start_command_pid | |
199 | 236 |
200 if args.gdb and is_android: | 237 if args.gdb and is_android: |
201 gdbserver_cmd = ['gdbserver', '--attach', ':%s' % GDB_PORT] | 238 # We push our own copy of gdbserver with the package since |
202 self.pids['remote_gdbserver_pid'] = subprocess.Popen(shell_command). pid | 239 # the default gdbserver is a different version from our gdb. |
240 package_path = \ | |
241 self._find_install_location_for_package(ANDROID_PACKAGE) | |
242 gdb_server_path = os.path.join( | |
243 os.path.dirname(package_path), 'lib/arm/gdbserver') | |
244 gdbserver_cmd = [ | |
245 'adb', 'shell', | |
246 gdb_server_path, '--attach', | |
247 ':%s' % GDB_PORT, | |
qsr
2015/01/16 00:46:40
%d ?
eseidel
2015/01/16 19:17:18
I think this makes the code needlessly fragile for
| |
248 str(self.pids['mojo_shell_pid']) | |
249 ] | |
250 print ' '.join(map(pipes.quote, gdbserver_cmd)) | |
251 self.pids['adb_shell_gdbserver_pid'] = \ | |
252 subprocess.Popen(gdbserver_cmd).pid | |
203 | 253 |
204 port_string = 'tcp:%s' % GDB_PORT | 254 port_string = 'tcp:%s' % GDB_PORT |
205 subprocess.check_call([ | 255 subprocess.check_call([ |
206 'adb', 'forward', port_string, port_string | 256 'adb', 'forward', port_string, port_string |
207 ]) | 257 ]) |
208 self.pids['remote_gdbserver_port'] = GDB_PORT | 258 self.pids['remote_gdbserver_port'] = GDB_PORT |
209 | 259 |
210 if not args.gdb: | 260 if not args.gdb: |
211 if not self._wait_for_sky_command_port(): | 261 if not self._wait_for_sky_command_port(): |
212 logging.error('Failed to start sky') | 262 logging.error('Failed to start sky') |
(...skipping 10 matching lines...) Expand all Loading... | |
223 return | 273 return |
224 logging.info('Killing %s (%s).' % (name, pid)) | 274 logging.info('Killing %s (%s).' % (name, pid)) |
225 try: | 275 try: |
226 os.kill(pid, signal.SIGTERM) | 276 os.kill(pid, signal.SIGTERM) |
227 except OSError: | 277 except OSError: |
228 logging.info('%s (%s) already gone.' % (name, pid)) | 278 logging.info('%s (%s) already gone.' % (name, pid)) |
229 | 279 |
230 def stop_command(self, args): | 280 def stop_command(self, args): |
231 # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown. | 281 # TODO(eseidel): mojo_shell crashes when attempting graceful shutdown. |
232 # self._send_command_to_sky('/quit') | 282 # self._send_command_to_sky('/quit') |
233 self._kill_if_exists('mojo_shell_pid', 'mojo_shell') | |
234 | 283 |
235 self._kill_if_exists('sky_server_pid', 'sky_server') | 284 self._kill_if_exists('sky_server_pid', 'sky_server') |
285 | |
236 # We could be much more surgical here: | 286 # We could be much more surgical here: |
237 if 'remote_sky_server_port' in self.pids: | 287 if 'remote_sky_server_port' in self.pids: |
238 device = android_commands.AndroidCommands( | 288 device = android_commands.AndroidCommands( |
239 self.pids['device_serial']) | 289 self.pids['device_serial']) |
240 forwarder.Forwarder.UnmapAllDevicePorts(device) | 290 forwarder.Forwarder.UnmapAllDevicePorts(device) |
241 | 291 |
242 if 'remote_sky_command_port' in self.pids: | 292 if 'remote_sky_command_port' in self.pids: |
243 # adb forward --remove takes the *host* port, not the remote port. | 293 # adb forward --remove takes the *host* port, not the remote port. |
244 port_string = 'tcp:%s' % self.pids['sky_command_port'] | 294 port_string = 'tcp:%s' % self.pids['sky_command_port'] |
245 subprocess.call(['adb', 'forward', '--remove', port_string]) | 295 subprocess.call(['adb', 'forward', '--remove', port_string]) |
246 | 296 |
247 subprocess.call([ | 297 subprocess.call([ |
248 'adb', 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) | 298 'adb', 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) |
299 else: | |
300 # Only try to kill mojo_shell if it's running locally. | |
301 self._kill_if_exists('mojo_shell_pid', 'mojo_shell') | |
249 | 302 |
250 if 'remote_gdbserver_port' in self.pids: | 303 if 'remote_gdbserver_port' in self.pids: |
304 self._kill_if_exists('adb_shell_gdbserver_pid', | |
305 'adb shell gdbserver') | |
306 | |
251 port_string = 'tcp:%s' % self.pids['remote_gdbserver_port'] | 307 port_string = 'tcp:%s' % self.pids['remote_gdbserver_port'] |
252 subprocess.call(['adb', 'forward', '--remove', port_string]) | 308 subprocess.call(['adb', 'forward', '--remove', port_string]) |
253 | 309 |
310 self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker') | |
311 | |
254 def load_command(self, args): | 312 def load_command(self, args): |
255 if not urlparse.urlparse(args.url_or_path).scheme: | 313 if not urlparse.urlparse(args.url_or_path).scheme: |
256 # The load happens on the remote device, use the remote port. | 314 # The load happens on the remote device, use the remote port. |
257 remote_sky_server_port = self.pids.get('remote_sky_server_port', | 315 remote_sky_server_port = self.pids.get('remote_sky_server_port', |
258 self.pids['sky_server_port']) | 316 self.pids['sky_server_port']) |
259 url = SkyServer.url_for_path(remote_sky_server_port, | 317 url = SkyServer.url_for_path(remote_sky_server_port, |
260 self.pids['sky_server_root'], args.url_or_path) | 318 self.pids['sky_server_root'], args.url_or_path) |
261 else: | 319 else: |
262 url = args.url_or_path | 320 url = args.url_or_path |
263 self._send_command_to_sky('/load', url) | 321 self._send_command_to_sky('/load', url) |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
316 'AndroidHandler', | 374 'AndroidHandler', |
317 'MojoMain', | 375 'MojoMain', |
318 'MojoShellActivity', | 376 'MojoShellActivity', |
319 'MojoShellApplication', | 377 'MojoShellApplication', |
320 'chromium', | 378 'chromium', |
321 ] | 379 ] |
322 subprocess.call(['adb', 'logcat', '-d', '-s'] + TAGS) | 380 subprocess.call(['adb', 'logcat', '-d', '-s'] + TAGS) |
323 | 381 |
324 def gdb_attach_command(self, args): | 382 def gdb_attach_command(self, args): |
325 self.paths = self._create_paths_for_build_dir(self.pids['build_dir']) | 383 self.paths = self._create_paths_for_build_dir(self.pids['build_dir']) |
384 | |
385 self._kill_if_exists('mojo_cache_linker_pid', 'mojo cache linker') | |
386 | |
387 links_path = '/tmp/mojo_cache_links' | |
388 if not os.path.exists(links_path): | |
389 os.makedirs(links_path) | |
390 shell_link_path = os.path.join(links_path, 'libmojo_shell.so') | |
391 if os.path.lexists(shell_link_path): | |
392 os.unlink(shell_link_path) | |
393 os.symlink(self.paths.mojo_shell_path, shell_link_path) | |
394 | |
395 logcat_cmd = ['adb', 'logcat'] | |
396 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) | |
397 | |
398 mojo_cache_linker_path = os.path.join( | |
399 self.paths.sky_tools_directory, 'mojo_cache_linker.py') | |
400 cache_linker_cmd = [ | |
401 mojo_cache_linker_path, | |
402 links_path, | |
403 self.pids['build_dir'], | |
404 'http://localhost:%s' % self.pids['remote_sky_server_port'] | |
405 ] | |
406 self.pids['mojo_cache_linker_pid'] = \ | |
407 subprocess.Popen(cache_linker_cmd, stdin=logcat.stdout).pid | |
408 | |
409 # Write out our pid file before we exec ourselves. | |
410 self._write_pid_file(PID_FILE_PATH, self.pids) | |
411 | |
412 # TODO(eseidel): Need to sync down system libraries into a directory. | |
413 symbol_search_paths = [ | |
414 links_path, | |
415 self.pids['build_dir'], | |
416 ] | |
417 # TODO(eseidel): We need to look up the toolchain somehow? | |
418 gdb_path = os.path.join(SRC_ROOT, 'third_party/android_tools/ndk/' | |
419 'toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/' | |
420 'bin/arm-linux-androideabi-gdb') | |
326 gdb_command = [ | 421 gdb_command = [ |
327 '/usr/bin/gdb', self.paths.mojo_shell_path, | 422 gdb_path, |
328 '--eval-command', 'target remote localhost:%s' % GDB_PORT | 423 '--eval-command', 'file %s' % self.paths.mojo_shell_path, |
424 '--eval-command', 'directory %s' % self.paths.src_root, | |
425 '--eval-command', 'target remote localhost:%s' % GDB_PORT, | |
426 '--eval-command', 'set solib-search-path %s' % | |
427 ':'.join(symbol_search_paths), | |
329 ] | 428 ] |
330 print " ".join(gdb_command) | 429 print " ".join(gdb_command) |
331 # We don't want python listenting for signals or anything, so exec | 430 # We don't want python listenting for signals or anything, so exec |
332 # gdb and let it take the entire process. | 431 # gdb and let it take the entire process. |
333 os.execv(gdb_command[0], gdb_command) | 432 os.execv(gdb_command[0], gdb_command) |
334 | 433 |
335 def print_crash_command(self, args): | 434 def print_crash_command(self, args): |
336 logcat_cmd = ['adb', 'logcat', '-d'] | 435 logcat_cmd = ['adb', 'logcat', '-d'] |
337 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) | 436 logcat = subprocess.Popen(logcat_cmd, stdout=subprocess.PIPE) |
338 | 437 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
394 load_parser.set_defaults(func=self.load_command) | 493 load_parser.set_defaults(func=self.load_command) |
395 | 494 |
396 args = parser.parse_args() | 495 args = parser.parse_args() |
397 args.func(args) | 496 args.func(args) |
398 | 497 |
399 self._write_pid_file(PID_FILE_PATH, self.pids) | 498 self._write_pid_file(PID_FILE_PATH, self.pids) |
400 | 499 |
401 | 500 |
402 if __name__ == '__main__': | 501 if __name__ == '__main__': |
403 SkyDebugger().main() | 502 SkyDebugger().main() |
OLD | NEW |