blob: 3863935b4b0cfec5365fb1f2e2b3b5c9c2815678 [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Produces configured shell abstractions.
This module knows how to produce a configured shell abstraction based on
shell_config.ShellConfig.
"""
import os.path
import sys
import urlparse
from devtoolslib.android_shell import AndroidShell
from devtoolslib.linux_shell import LinuxShell
from devtoolslib.shell_config import ShellConfigurationException
# When spinning up servers for local origins, we want to use predictable ports
# so that caching works between subsequent runs with the same command line.
_LOCAL_ORIGIN_PORT = 31840
_MAPPINGS_BASE_PORT = 31841
def _is_web_url(dest):
return True if urlparse.urlparse(dest).scheme else False
def _host_local_url_destination(shell, dest_file, port):
"""Starts a local server to host |dest_file|.
Returns:
Url of the hosted file.
"""
directory = os.path.dirname(dest_file)
if not os.path.exists(directory):
raise ValueError('local path passed as --map-url destination '
'does not exist')
server_url = shell.serve_local_directory(directory, port)
return server_url + os.path.relpath(dest_file, directory)
def _host_local_origin_destination(shell, dest_dir, port):
"""Starts a local server to host |dest_dir|.
Returns:
Url of the hosted directory.
"""
return shell.serve_local_directory(dest_dir, port)
def _rewrite(mapping, host_destination_functon, shell, port):
"""Takes a mapping given as <src>=<dest> and rewrites the <dest> part to be
hosted locally using the given function if <dest> is not a web url.
"""
parts = mapping.split('=')
if len(parts) != 2:
raise ValueError('each mapping value should be in format '
'"<url>=<url-or-local-path>"')
if _is_web_url(parts[1]):
# The destination is a web url, do nothing.
return mapping
src = parts[0]
dest = host_destination_functon(shell, parts[1], port)
return src + '=' + dest
def _apply_mappings(shell, original_arguments, map_urls, map_origins):
"""Applies mappings for specified urls and origins. For each local path
specified as destination a local server will be spawned and the mapping will
be rewritten accordingly.
Args:
shell: The shell that is being configured.
original_arguments: Current list of shell arguments.
map_urls: List of url mappings, each in the form of
<url>=<url-or-local-path>.
map_origins: List of origin mappings, each in the form of
<origin>=<url-or-local-path>.
Returns:
The updated argument list.
"""
next_port = _MAPPINGS_BASE_PORT
args = original_arguments
if map_urls:
# Sort the mappings to preserve caching regardless of argument order.
for map_url in sorted(map_urls):
mapping = _rewrite(map_url, _host_local_url_destination, shell, next_port)
next_port += 1
# All url mappings need to be coalesced into one shell argument.
args = append_to_argument(args, '--url-mappings=', mapping)
if map_origins:
for map_origin in sorted(map_origins):
mapping = _rewrite(map_origin, _host_local_origin_destination, shell,
next_port)
next_port += 1
# Origin mappings are specified as separate, repeated shell arguments.
args.append('--map-origin=' + mapping)
return args
def _configure_sky(shell_args):
"""Maps mojo:sky_viewer as a content handler for dart applications.
app.
Args:
shell_args: Current list of shell arguments.
Returns:
Updated list of shell arguments.
"""
# Configure the content type mappings for the sky_viewer. This is needed
# only for the Sky apps that do not declare mojo:sky_viewer in a shebang.
# TODO(ppi): drop this part once we can rely on the Sky files declaring
# correct shebang.
shell_args = append_to_argument(shell_args, '--content-handlers=',
'text/sky,mojo:sky_viewer')
shell_args = append_to_argument(shell_args, '--content-handlers=',
'application/dart,mojo:sky_viewer')
return shell_args
def configure_local_origin(shell, local_dir, fixed_port=True):
"""Sets up a local http server to serve files in |local_dir| along with
device port forwarding if needed.
Returns:
The list of arguments to be appended to the shell argument list.
"""
origin_url = shell.serve_local_directory(
local_dir, _LOCAL_ORIGIN_PORT if fixed_port else 0)
return ["--origin=" + origin_url]
def append_to_argument(arguments, key, value, delimiter=","):
"""Looks for an argument of the form "key=val1,val2" within |arguments| and
appends |value| to it.
If the argument is not present in |arguments| it is added.
Args:
arguments: List of arguments for the shell.
key: Identifier of the argument, including the equal sign, eg.
"--content-handlers=".
value: The value to be appended, after |delimeter|, to the argument.
delimiter: The string used to separate values within the argument.
Returns:
The updated argument list.
"""
assert key and key.endswith('=')
assert value
for i, argument in enumerate(arguments):
if not argument.startswith(key):
continue
arguments[i] = argument + delimiter + value
break
else:
arguments.append(key + value)
return arguments
def _configure_dev_server(shell, shell_args, dev_server_config):
"""Sets up a dev server on the host according to |dev_server_config|.
Args:
shell: The shell that is being configured.
shell_arguments: Current list of shell arguments.
dev_server_config: Instance of shell_config.DevServerConfig describing the
dev server to be set up.
Returns:
The updated argument list.
"""
server_url = shell.serve_local_directories(dev_server_config.mappings)
shell_args.append('--map-origin=%s=%s' % (dev_server_config.host, server_url))
print "Configured %s locally to serve:" % (dev_server_config.host)
for mapping_prefix, mapping_path in dev_server_config.mappings:
print " /%s -> %s" % (mapping_prefix, mapping_path)
return shell_args
def get_shell(shell_config, shell_args):
"""
Produces a shell abstraction configured according to |shell_config|.
Args:
shell_config: Instance of shell_config.ShellConfig.
shell_args: Additional raw shell arguments to be passed to the shell. We
need to take these into account as some parameters need to appear only
once on the argument list (e.g. url-mappings) so we need to coalesce any
overrides and the existing value into just one argument.
Returns:
A tuple of (shell, shell_args). |shell| is the configured shell abstraction,
|shell_args| is updated list of shell arguments.
Throws:
ShellConfigurationException if shell abstraction could not be configured.
"""
if shell_config.android:
verbose_pipe = sys.stdout if shell_config.verbose else None
shell = AndroidShell(shell_config.adb_path, shell_config.target_device,
logcat_tags=shell_config.logcat_tags,
verbose_pipe=verbose_pipe)
device_status, error = shell.check_device()
if not device_status:
raise ShellConfigurationException('Device check failed: ' + error)
if shell_config.shell_path:
shell.install_apk(shell_config.shell_path)
else:
if not shell_config.shell_path:
raise ShellConfigurationException('Can not run without a shell binary. '
'Please pass --shell-path.')
shell = LinuxShell(shell_config.shell_path)
if shell_config.use_osmesa:
shell_args.append('--args-for=mojo:native_viewport_service --use-osmesa')
shell_args = _apply_mappings(shell, shell_args, shell_config.map_url_list,
shell_config.map_origin_list)
if shell_config.origin:
if _is_web_url(shell_config.origin):
shell_args.append('--origin=' + shell_config.origin)
else:
shell_args.extend(configure_local_origin(shell, shell_config.origin,
fixed_port=True))
if shell_config.sky:
shell_args = _configure_sky(shell_args)
for dev_server_config in shell_config.dev_servers:
shell_args = _configure_dev_server(shell, shell_args, dev_server_config)
return shell, shell_args