mojo_run: support parallel Android runs of mojo_run.
This patch adds a `--free-host-ports` flag to devtools that allows to
run dev servers on system-allocated ports (so that we can have parallel
runs) while still using fixed ports on the device (so that the caching
still works).
Running with `--free-host-ports` makes `adb_remote_setup` unable to work
correctly, which is why this is being introduced behind a flag.
Fixes #470.
R=qsr@chromium.org
Review URL: https://codereview.chromium.org/1397193005 .
Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: eec06de72752abcae761e709de28e9eebd3fa52a
diff --git a/devtoolslib/android_shell.py b/devtoolslib/android_shell.py
index 275a3dc..611732e 100644
--- a/devtoolslib/android_shell.py
+++ b/devtoolslib/android_shell.py
@@ -408,18 +408,20 @@
logcat_watch_thread.start()
@overrides(Shell)
- def serve_local_directory(self, local_dir_path, port=0):
+ def serve_local_directory(self, local_dir_path, port=0, free_host_port=False):
assert local_dir_path
mappings = [('', [local_dir_path])]
- server_address = start_http_server(mappings, host_port=port)
+ host_port = 0 if free_host_port else port
+ server_address = start_http_server(mappings, host_port=host_port)
return 'http://127.0.0.1:%d/' % self._forward_device_port_to_host(
port, server_address[1])
@overrides(Shell)
- def serve_local_directories(self, mappings, port=0):
+ def serve_local_directories(self, mappings, port=0, free_host_port=False):
assert mappings
- server_address = start_http_server(mappings, host_port=port)
+ host_port = 0 if free_host_port else port
+ server_address = start_http_server(mappings, host_port=host_port)
return 'http://127.0.0.1:%d/' % self._forward_device_port_to_host(
port, server_address[1])
diff --git a/devtoolslib/linux_shell.py b/devtoolslib/linux_shell.py
index a4a83a9..95161e7 100644
--- a/devtoolslib/linux_shell.py
+++ b/devtoolslib/linux_shell.py
@@ -25,12 +25,12 @@
self.command_prefix = command_prefix if command_prefix else []
@overrides(Shell)
- def serve_local_directory(self, local_dir_path, port=0):
+ def serve_local_directory(self, local_dir_path, port=0, free_host_port=False):
mappings = [('', [local_dir_path])]
return 'http://%s:%d/' % http_server.start_http_server(mappings, port)
@overrides(Shell)
- def serve_local_directories(self, mappings, port=0):
+ def serve_local_directories(self, mappings, port=0, free_host_port=False):
return 'http://%s:%d/' % http_server.start_http_server(mappings, port)
@overrides(Shell)
diff --git a/devtoolslib/shell.py b/devtoolslib/shell.py
index c142611..b7f39d8 100644
--- a/devtoolslib/shell.py
+++ b/devtoolslib/shell.py
@@ -6,7 +6,7 @@
class Shell(object):
"""Represents an abstract Mojo shell."""
- def serve_local_directory(self, local_dir_path, port=0):
+ def serve_local_directory(self, local_dir_path, port=0, free_host_port=False):
"""Serves the content of the local (host) directory, making it available to
the shell under the url returned by the function.
@@ -15,14 +15,19 @@
Args:
local_dir_path: path to the directory to be served
- port: port at which the server will be available to the shell
+ port: port at which the server will be available to the shell. On Android
+ this can be different from the port on which the server runs on the
+ host.
+ free_host_port: spawn the server a system allocated port. This is ignored
+ on Linux, where |port| indicates the port on which the server will be
+ spawned.
Returns:
The url that the shell can use to access the content of |local_dir_path|.
"""
raise NotImplementedError()
- def serve_local_directories(self, mappings, port=0):
+ def serve_local_directories(self, mappings, port=0, free_host_port=False):
"""Serves the content of the local (host) directories, making it available
to the shell under the url returned by the function.
@@ -35,7 +40,12 @@
|local_base_path_list|. The prefixes should skip the leading slash.
The first matching prefix and the first location that contains the
requested file will be used each time.
- port: port at which the server will be available to the shell
+ port: port at which the server will be available to the shell. On Android
+ this can be different from the port on which the server runs on the
+ host.
+ free_host_port: spawn the server a system allocated port. This is ignored
+ on Linux, where |port| indicates the port on which the server will be
+ spawned.
Returns:
The url that the shell can use to access the server.
diff --git a/devtoolslib/shell_arguments.py b/devtoolslib/shell_arguments.py
index 4a12fd5..88f3f0b 100644
--- a/devtoolslib/shell_arguments.py
+++ b/devtoolslib/shell_arguments.py
@@ -26,7 +26,7 @@
return True if urlparse.urlparse(dest).scheme else False
-def _host_local_url_destination(shell, dest_file, port):
+def _host_local_url_destination(shell, dest_file, port, free_host_port):
"""Starts a local server to host |dest_file|.
Returns:
@@ -36,20 +36,20 @@
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)
+ server_url = shell.serve_local_directory(directory, port, free_host_port)
return server_url + os.path.relpath(dest_file, directory)
-def _host_local_origin_destination(shell, dest_dir, port):
+def _host_local_origin_destination(shell, dest_dir, port, free_host_port):
"""Starts a local server to host |dest_dir|.
Returns:
Url of the hosted directory.
"""
- return shell.serve_local_directory(dest_dir, port)
+ return shell.serve_local_directory(dest_dir, port, free_host_port)
-def _rewrite(mapping, host_destination_functon, shell, port):
+def _rewrite(mapping, host_destination_functon, shell, port, free_host_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.
"""
@@ -62,11 +62,12 @@
return mapping
src = parts[0]
- dest = host_destination_functon(shell, parts[1], port)
+ dest = host_destination_functon(shell, parts[1], port, free_host_port)
return src + '=' + dest
-def _apply_mappings(shell, original_arguments, map_urls, map_origins):
+def _apply_mappings(shell, original_arguments, map_urls, map_origins,
+ free_host_ports):
"""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.
@@ -75,7 +76,7 @@
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>.
+ <url>=<url-or-local-path>.
map_origins: List of origin mappings, each in the form of
<origin>=<url-or-local-path>.
@@ -87,7 +88,8 @@
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)
+ mapping = _rewrite(map_url, _host_local_url_destination, shell, next_port,
+ free_host_ports)
next_port += 1
# All url mappings need to be coalesced into one shell argument.
args = append_to_argument(args, '--url-mappings=', mapping)
@@ -95,14 +97,14 @@
if map_origins:
for map_origin in sorted(map_origins):
mapping = _rewrite(map_origin, _host_local_origin_destination, shell,
- next_port)
+ next_port, free_host_ports)
next_port += 1
# Origin mappings are specified as separate, repeated shell arguments.
args.append('--map-origin=' + mapping)
return args
-def configure_local_origin(shell, local_dir, fixed_port=True):
+def configure_local_origin(shell, local_dir, free_host_port):
"""Sets up a local http server to serve files in |local_dir| along with
device port forwarding if needed.
@@ -111,7 +113,7 @@
"""
origin_url = shell.serve_local_directory(
- local_dir, _LOCAL_ORIGIN_PORT if fixed_port else 0)
+ local_dir, _LOCAL_ORIGIN_PORT, free_host_port)
return ["--origin=" + origin_url]
@@ -145,7 +147,8 @@
return arguments
-def _configure_dev_server(shell, shell_args, dev_server_config, verbose):
+def _configure_dev_server(shell, shell_args, dev_server_config, free_host_port,
+ verbose):
"""Sets up a dev server on the host according to |dev_server_config|.
Args:
@@ -159,7 +162,8 @@
"""
port = dev_server_config.port if dev_server_config.port else 0
server_url = shell.serve_local_directories(dev_server_config.mappings,
- port=port)
+ port=port,
+ free_host_port=free_host_port)
shell_args.append('--map-origin=%s=%s' % (dev_server_config.host, server_url))
if verbose:
@@ -210,14 +214,15 @@
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)
+ shell_config.map_origin_list,
+ shell_config.free_host_ports)
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))
+ shell_config.free_host_ports))
if shell_config.content_handlers:
for (mime_type,
@@ -228,6 +233,7 @@
for dev_server_config in shell_config.dev_servers:
shell_args = _configure_dev_server(shell, shell_args, dev_server_config,
+ shell_config.free_host_ports,
shell_config.verbose)
return shell, shell_args
diff --git a/devtoolslib/shell_config.py b/devtoolslib/shell_config.py
index b7f59d2..b3faa05 100644
--- a/devtoolslib/shell_config.py
+++ b/devtoolslib/shell_config.py
@@ -37,6 +37,7 @@
self.target_device = None
self.logcat_tags = None
self.require_root = False
+ self.free_host_ports = False
# Desktop-only.
self.use_osmesa = None
@@ -77,6 +78,9 @@
android_group.add_argument('--target-device', help='Device to run on.')
android_group.add_argument('--logcat-tags', help='Comma-separated list of '
'additional logcat tags to display.')
+ android_group.add_argument('--free-host-ports', action='store_true',
+ help='Use system-allocated ports on the host when '
+ 'spawning local servers.')
desktop_group = parser.add_argument_group('Desktop-only',
'These arguments apply only when running on desktop.')
@@ -156,6 +160,7 @@
'adb')
shell_config.target_device = script_args.target_device
shell_config.logcat_tags = script_args.logcat_tags
+ shell_config.free_host_ports = script_args.free_host_ports
# Desktop-only.
shell_config.use_osmesa = script_args.use_osmesa
diff --git a/docs/mojo_run.md b/docs/mojo_run.md
index 1b96f67..0675c9c 100644
--- a/docs/mojo_run.md
+++ b/docs/mojo_run.md
@@ -26,3 +26,23 @@
By default, `mojo_run` uses mojo:kiosk_wm as the window manager. You can pass a
different window manager url using the `--window-manager` flag to override this.
+
+## Running on multiple Android devices
+
+Two or more instances of `mojo_run` can simultaneously run on separate Android
+devices. For that, run in individual shells:
+
+```sh
+mojo_run APP_URL --android --target-device DEVICE_ID --free-host-ports
+```
+
+```sh
+mojo_run APP_URL --android --target-device ANOTHER_DEVICE_ID --free-host-ports
+```
+
+`--free-host-ports` makes `mojo_run` spawn the development servers on
+system-allocated ports on the server (so that multiple instances can run in
+parallel) while still forwarding them to fixed ports on the device (so that
+caching still works). This breaks the remote workflow over `adb_remote_setup`.
+
+DEVICE_ID can be obtained from `adb devices`.