Fix skydb to work for android

I changed skydb start to take a build directory
and read the configuration out of gn args instead
of --release, --debug, etc.  This should be more
flexable and handle all the crazy asan cases
mopy/config.py tries to.  Once we merge sky/tools
into mojo/tools we should make mopy/config use this
method too.

This follows similar patterns to what
mojo/tools/android_mojo_shell.py does, but doesn't
use as much of the (old) android_commands and forwarder
logic.  We could even remove all of that
build/android/pylib code by calling the (new)
adb reverse instead of using Forwarder (in a later patch).

This still only supports a single skydb running
at once, but it should be trivial to move the
skydb.pids file into the build directory or to have
it support more than one instance.  The big question
there is what the command-line usage should look like
when supporting more than one running instance.  See
the mojo-dev thread on the subject.

R=abarth@chromium.org

Review URL: https://codereview.chromium.org/816693006
This commit is contained in:
Eric Seidel 2015-01-12 11:16:20 -08:00
parent aaef6b64d8
commit 874aabe98b
2 changed files with 133 additions and 28 deletions

View File

@ -156,7 +156,7 @@ class Prompt : public mojo::ApplicationDelegate, public net::HttpServer::Delegat
std::string url_; std::string url_;
base::WeakPtrFactory<Prompt> weak_ptr_factory_; base::WeakPtrFactory<Prompt> weak_ptr_factory_;
scoped_ptr<net::HttpServer> web_server_; scoped_ptr<net::HttpServer> web_server_;
unsigned command_port_; uint32_t command_port_;
DISALLOW_COPY_AND_ASSIGN(Prompt); DISALLOW_COPY_AND_ASSIGN(Prompt);
}; };

View File

@ -9,12 +9,18 @@ import argparse
import json import json
import logging import logging
import os import os
import pipes
import requests import requests
import signal import signal
import skypy.configuration as configuration
import subprocess import subprocess
import urlparse import sys
import time import time
import urlparse
sys.path.insert(0, os.path.join(Paths('ignored').src_root, 'build', 'android'))
from pylib import android_commands
from pylib import constants
from pylib import forwarder
SUPPORTED_MIME_TYPES = [ SUPPORTED_MIME_TYPES = [
@ -29,6 +35,21 @@ PID_FILE_PATH = "/tmp/skydb.pids"
DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/examples/home.sky" DEFAULT_URL = "https://raw.githubusercontent.com/domokit/mojo/master/sky/examples/home.sky"
# FIXME: Move this into mopy.config
def gn_args_from_build_dir(build_dir):
gn_cmd = [
'gn', 'args',
build_dir,
'--list', '--short'
]
config = {}
for line in subprocess.check_output(gn_cmd).strip().split('\n'):
# FIXME: This doesn't handle = in values.
key, value = line.split(' = ')
config[key] = value
return config
class SkyDebugger(object): class SkyDebugger(object):
def __init__(self): def __init__(self):
self.paths = None self.paths = None
@ -48,48 +69,114 @@ class SkyDebugger(object):
def _in_chromoting(self): def _in_chromoting(self):
return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False) return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False)
def _build_mojo_shell_command(self, args): def _wrap_for_android(self, shell_args):
self.paths = Paths(os.path.join('out', args.configuration)) build_dir_url = SkyServer.url_for_path(
self.pids['remote_sky_server_port'],
self.pids['sky_server_root'],
self.pids['build_dir'])
shell_args += ['--origin=%s' % build_dir_url]
# am shell --esa: (someone shoot me now)
# [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]
# (to embed a comma into a string escape it using "\,")
escaped_args = map(lambda arg: arg.replace(',', '\\,'), shell_args)
return [
'adb', 'shell',
'am', 'start',
'-W',
'-S',
'-a', 'android.intent.action.VIEW',
'-n', 'org.chromium.mojo.shell/.MojoShellActivity',
# FIXME: This quoting is very error-prone. Perhaps we should read
# our args from a file instead?
'--esa', 'parameters', ','.join(escaped_args),
]
def _build_mojo_shell_command(self, args):
content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer') content_handlers = ['%s,%s' % (mime_type, 'mojo:sky_viewer')
for mime_type in SUPPORTED_MIME_TYPES] for mime_type in SUPPORTED_MIME_TYPES]
shell_command = [
self.paths.mojo_shell_path, remote_command_port = self.pids.get('remote_sky_command_port', self.pids['sky_command_port'])
shell_args = [
'--v=1', '--v=1',
'--content-handlers=%s' % ','.join(content_handlers), '--content-handlers=%s' % ','.join(content_handlers),
'--url-mappings=mojo:window_manager=mojo:sky_debugger', '--url-mappings=mojo:window_manager=mojo:sky_debugger',
'--args-for=mojo:sky_debugger_prompt %d' % args.command_port, '--args-for=mojo:sky_debugger_prompt %d' % remote_command_port,
'mojo:window_manager', 'mojo:window_manager',
] ]
# FIXME: This probably is wrong for android?
if args.use_osmesa: if args.use_osmesa:
shell_command.append('--args-for=mojo:native_viewport_service --use-osmesa') shell_args.append('--args-for=mojo:native_viewport_service --use-osmesa')
if 'remote_sky_server_port' in self.pids:
shell_command = self._wrap_for_android(shell_args)
else:
shell_command = [self.paths.mojo_shell_path] + shell_args
# FIXME: This doesn't work for android
if args.gdb: if args.gdb:
shell_command = ['gdb', '--args'] + shell_command shell_command = ['gdb', '--args'] + shell_command
return shell_command return shell_command
def start_command(self, args): def _connect_to_device(self):
shell_command = self._build_mojo_shell_command(args) device = android_commands.AndroidCommands(
if args.show_command: android_commands.GetAttachedDevices()[0])
print " ".join(shell_command) device.EnableAdbRoot()
return return device
self.stop_command(None) # Quit any existing process. def sky_server_for_args(self, args):
# FIXME: This is a hack. sky_server should just take a build_dir
print args.url_or_path # not a magical "configuration" name.
# We only start a server for paths: configuration = os.path.basename(os.path.normpath(args.build_dir))
if not urlparse.urlparse(args.url_or_path).scheme:
server_root = self._server_root_for_url(args.url_or_path) server_root = self._server_root_for_url(args.url_or_path)
sky_server = SkyServer(self.paths, SKY_SERVER_PORT, sky_server = SkyServer(self.paths, SKY_SERVER_PORT,
args.configuration, server_root) configuration, server_root)
return sky_server
def start_command(self, args):
# FIXME: Lame that we use self for a command-specific variable.
self.paths = Paths(args.build_dir)
self.stop_command(None) # Quit any existing process.
self.pids = {} # Clear out our pid file.
# FIXME: This is probably not the right way to compute is_android
# from the build directory?
gn_args = gn_args_from_build_dir(args.build_dir)
is_android = 'android_sdk_version' in gn_args
sky_server = self.sky_server_for_args(args)
self.pids['sky_server_pid'] = sky_server.start() self.pids['sky_server_pid'] = sky_server.start()
self.pids['sky_server_port'] = sky_server.port self.pids['sky_server_port'] = sky_server.port
self.pids['sky_server_root'] = sky_server.root self.pids['sky_server_root'] = sky_server.root
self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid self.pids['build_dir'] = args.build_dir
self.pids['sky_command_port'] = args.command_port self.pids['sky_command_port'] = args.command_port
if is_android:
# Pray to the build/android gods in their misspelled tongue.
constants.SetOutputDirectort(args.build_dir)
device = self._connect_to_device()
self.pids['device_serial'] = device.GetDevice()
forwarder.Forwarder.Map([(0, sky_server.port)], device)
device_http_port = forwarder.Forwarder.DevicePortForHostPort(
sky_server.port)
self.pids['remote_sky_server_port'] = device_http_port
port_string = 'tcp:%s' % args.command_port
subprocess.check_call([
'adb', 'forward', port_string, port_string
])
self.pids['remote_sky_command_port'] = args.command_port
shell_command = self._build_mojo_shell_command(args)
print ' '.join(map(pipes.quote, shell_command))
self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid
if not self._wait_for_sky_command_port(): if not self._wait_for_sky_command_port():
logging.error('Failed to start sky') logging.error('Failed to start sky')
self.stop_command(None) self.stop_command(None)
@ -108,14 +195,31 @@ class SkyDebugger(object):
logging.info('%s (%s) already gone.' % (name, pid)) logging.info('%s (%s) already gone.' % (name, pid))
def stop_command(self, args): def stop_command(self, args):
# FIXME: Send /quit to sky prompt instead of killing. try:
# self._send_command_to_sky('/quit') self._send_command_to_sky('/quit')
except:
pass
# Kill the mojo process for good measure. :)
self._kill_if_exists('mojo_shell_pid', 'mojo_shell') self._kill_if_exists('mojo_shell_pid', 'mojo_shell')
self._kill_if_exists('sky_server_pid', 'sky_server') self._kill_if_exists('sky_server_pid', 'sky_server')
# We could be much more surgical here:
if 'remote_sky_command_port' in self.pids:
device = android_commands.AndroidCommands(
self.pids['device_serial'])
forwarder.Forwarder.UnmapAllDevicePorts(device)
if 'remote_sky_command_port' in self.pids:
# adb forward --remove takes the *host* port, not the remote port.
port_string = 'tcp:%s' % self.pids['sky_command_port']
subprocess.call(['adb', 'forward', '--remove', port_string])
def load_command(self, args): def load_command(self, args):
if not urlparse.urlparse(args.url_or_path).scheme: if not urlparse.urlparse(args.url_or_path).scheme:
url = SkyServer.url_for_path(self.pids['sky_server_port'], # The load happens on the remote device, use the remote port.
remote_sky_server_port = self.pids.get('remote_sky_server_port',
self.pids['sky_server_port'])
url = SkyServer.url_for_path(remote_sky_server_port,
self.pids['sky_server_root'], args.url_or_path) self.pids['sky_server_root'], args.url_or_path)
else: else:
url = args.url_or_path url = args.url_or_path
@ -146,7 +250,7 @@ class SkyDebugger(object):
def _write_pid_file(self, path, pids): def _write_pid_file(self, path, pids):
try: try:
with open(path, 'w') as pid_file: with open(path, 'w') as pid_file:
json.dump(pids, pid_file) json.dump(pids, pid_file, indent=2, sort_keys=True)
except: except:
logging.warn('Failed to write pid file: %s' % path) logging.warn('Failed to write pid file: %s' % path)
@ -172,6 +276,7 @@ class SkyDebugger(object):
def main(self): def main(self):
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logging.getLogger("requests").setLevel(logging.WARNING)
self.pids = self._load_pid_file(PID_FILE_PATH) self.pids = self._load_pid_file(PID_FILE_PATH)
@ -180,12 +285,12 @@ class SkyDebugger(object):
start_parser = subparsers.add_parser('start', start_parser = subparsers.add_parser('start',
help='launch a new mojo_shell with sky') help='launch a new mojo_shell with sky')
configuration.add_arguments(start_parser)
start_parser.add_argument('--gdb', action='store_true') start_parser.add_argument('--gdb', action='store_true')
start_parser.add_argument('--command-port', type=int, start_parser.add_argument('--command-port', type=int,
default=DEFAULT_SKY_COMMAND_PORT) default=DEFAULT_SKY_COMMAND_PORT)
start_parser.add_argument('--use-osmesa', action='store_true', start_parser.add_argument('--use-osmesa', action='store_true',
default=self._in_chromoting()) default=self._in_chromoting())
start_parser.add_argument('build_dir', type=str)
start_parser.add_argument('url_or_path', nargs='?', type=str, start_parser.add_argument('url_or_path', nargs='?', type=str,
default=DEFAULT_URL) default=DEFAULT_URL)
start_parser.add_argument('--show-command', action='store_true', start_parser.add_argument('--show-command', action='store_true',