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_;
base::WeakPtrFactory<Prompt> weak_ptr_factory_;
scoped_ptr<net::HttpServer> web_server_;
unsigned command_port_;
uint32_t command_port_;
DISALLOW_COPY_AND_ASSIGN(Prompt);
};

View File

@ -9,12 +9,18 @@ import argparse
import json
import logging
import os
import pipes
import requests
import signal
import skypy.configuration as configuration
import subprocess
import urlparse
import sys
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 = [
@ -29,6 +35,21 @@ PID_FILE_PATH = "/tmp/skydb.pids"
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):
def __init__(self):
self.paths = None
@ -48,48 +69,114 @@ class SkyDebugger(object):
def _in_chromoting(self):
return os.environ.get('CHROME_REMOTE_DESKTOP_SESSION', False)
def _build_mojo_shell_command(self, args):
self.paths = Paths(os.path.join('out', args.configuration))
def _wrap_for_android(self, shell_args):
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')
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',
'--content-handlers=%s' % ','.join(content_handlers),
'--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',
]
# FIXME: This probably is wrong for android?
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:
shell_command = ['gdb', '--args'] + shell_command
return shell_command
def _connect_to_device(self):
device = android_commands.AndroidCommands(
android_commands.GetAttachedDevices()[0])
device.EnableAdbRoot()
return device
def sky_server_for_args(self, args):
# FIXME: This is a hack. sky_server should just take a build_dir
# not a magical "configuration" name.
configuration = os.path.basename(os.path.normpath(args.build_dir))
server_root = self._server_root_for_url(args.url_or_path)
sky_server = SkyServer(self.paths, SKY_SERVER_PORT,
configuration, server_root)
return sky_server
def start_command(self, args):
shell_command = self._build_mojo_shell_command(args)
if args.show_command:
print " ".join(shell_command)
return
# 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.
print args.url_or_path
# We only start a server for paths:
if not urlparse.urlparse(args.url_or_path).scheme:
server_root = self._server_root_for_url(args.url_or_path)
sky_server = SkyServer(self.paths, SKY_SERVER_PORT,
args.configuration, server_root)
self.pids['sky_server_pid'] = sky_server.start()
self.pids['sky_server_port'] = sky_server.port
self.pids['sky_server_root'] = sky_server.root
# 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
self.pids['mojo_shell_pid'] = subprocess.Popen(shell_command).pid
sky_server = self.sky_server_for_args(args)
self.pids['sky_server_pid'] = sky_server.start()
self.pids['sky_server_port'] = sky_server.port
self.pids['sky_server_root'] = sky_server.root
self.pids['build_dir'] = args.build_dir
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():
logging.error('Failed to start sky')
self.stop_command(None)
@ -108,14 +195,31 @@ class SkyDebugger(object):
logging.info('%s (%s) already gone.' % (name, pid))
def stop_command(self, args):
# FIXME: Send /quit to sky prompt instead of killing.
# self._send_command_to_sky('/quit')
try:
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('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):
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)
else:
url = args.url_or_path
@ -146,7 +250,7 @@ class SkyDebugger(object):
def _write_pid_file(self, path, pids):
try:
with open(path, 'w') as pid_file:
json.dump(pids, pid_file)
json.dump(pids, pid_file, indent=2, sort_keys=True)
except:
logging.warn('Failed to write pid file: %s' % path)
@ -172,6 +276,7 @@ class SkyDebugger(object):
def main(self):
logging.basicConfig(level=logging.INFO)
logging.getLogger("requests").setLevel(logging.WARNING)
self.pids = self._load_pid_file(PID_FILE_PATH)
@ -180,12 +285,12 @@ class SkyDebugger(object):
start_parser = subparsers.add_parser('start',
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('--command-port', type=int,
default=DEFAULT_SKY_COMMAND_PORT)
start_parser.add_argument('--use-osmesa', action='store_true',
default=self._in_chromoting())
start_parser.add_argument('build_dir', type=str)
start_parser.add_argument('url_or_path', nargs='?', type=str,
default=DEFAULT_URL)
start_parser.add_argument('--show-command', action='store_true',