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`.