Add `mojo_run --free-ports` to run servers on system-allocated ports.

This makes mojo devtools use system-allocated ports for all dev servers
that normally are run on fixed ports to facilitate caching (ie. dev
servers spawned for local --origin, --map-origin and --map-url).

Note that this does not affect dev servers with explicitly assigned
fixed ports in a mojoconfig file.

Fixes #477.

R=qsr@chromium.org

Review URL: https://codereview.chromium.org/1419333005 .

Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: 6f424d79b6c6a87827fcd5a119d7c87c96476bc2
diff --git a/devtoolslib/shell_arguments.py b/devtoolslib/shell_arguments.py
index 88f3f0b..9df58b3 100644
--- a/devtoolslib/shell_arguments.py
+++ b/devtoolslib/shell_arguments.py
@@ -67,7 +67,7 @@
 
 
 def _apply_mappings(shell, original_arguments, map_urls, map_origins,
-                    free_host_ports):
+                    free_ports, 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.
@@ -79,18 +79,24 @@
         <url>=<url-or-local-path>.
     map_origins: List of origin mappings, each in the form of
         <origin>=<url-or-local-path>.
+    free_ports: Iff True, run local development servers on system-allocated
+        ports. This defeats any performance benefits from caching.
+    free_host_ports: Only applicable on Android. Iff True, local development
+        servers are run on system-allocated ports, but are still forwarded from
+        fixed ports on the device.
 
   Returns:
     The updated argument list.
   """
-  next_port = _MAPPINGS_BASE_PORT
+  next_port = 0 if free_ports else _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,
                          free_host_ports)
-      next_port += 1
+      if not free_ports:
+        next_port += 1
       # All url mappings need to be coalesced into one shell argument.
       args = append_to_argument(args, '--url-mappings=', mapping)
 
@@ -98,13 +104,14 @@
     for map_origin in sorted(map_origins):
       mapping = _rewrite(map_origin, _host_local_origin_destination, shell,
                          next_port, free_host_ports)
-      next_port += 1
+      if not free_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, free_host_port):
+def configure_local_origin(shell, local_dir, port, free_host_port):
   """Sets up a local http server to serve files in |local_dir| along with
   device port forwarding if needed.
 
@@ -112,8 +119,7 @@
     The list of arguments to be appended to the shell argument list.
   """
 
-  origin_url = shell.serve_local_directory(
-      local_dir, _LOCAL_ORIGIN_PORT, free_host_port)
+  origin_url = shell.serve_local_directory(local_dir, port, free_host_port)
   return ["--origin=" + origin_url]
 
 
@@ -215,13 +221,16 @@
 
   shell_args = _apply_mappings(shell, shell_args, shell_config.map_url_list,
                                shell_config.map_origin_list,
+                               shell_config.free_ports,
                                shell_config.free_host_ports)
 
   if shell_config.origin:
     if _is_web_url(shell_config.origin):
       shell_args.append('--origin=' + shell_config.origin)
     else:
+      local_origin_port = 0 if shell_config.free_ports else _LOCAL_ORIGIN_PORT
       shell_args.extend(configure_local_origin(shell, shell_config.origin,
+                                               local_origin_port,
                                                shell_config.free_host_ports))
 
   if shell_config.content_handlers:
diff --git a/devtoolslib/shell_config.py b/devtoolslib/shell_config.py
index b3faa05..79a14c2 100644
--- a/devtoolslib/shell_config.py
+++ b/devtoolslib/shell_config.py
@@ -29,6 +29,7 @@
     self.map_url_list = []
     self.map_origin_list = []
     self.dev_servers = []
+    self.free_ports = False
     self.content_handlers = dict()
     self.verbose = None
 
@@ -69,6 +70,10 @@
   parser.add_argument('--map-origin', action='append',
                       help='Define a mapping for a url origin in the format '
                       '<origin>=<url-or-local-file-path>')
+  parser.add_argument('--free-ports', action='store_true',
+                      help='Use system-allocated ports when spawning local '
+                      'servers. This defeats caching and thus hurts '
+                      'performance.')
   parser.add_argument('-v', '--verbose', action="store_true",
                       help="Increase output verbosity")
 
@@ -80,7 +85,9 @@
                              '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.')
+                             'spawning local servers. This still forwards to '
+                             'fixed ports on the device, so that caching '
+                             'works.')
 
   desktop_group = parser.add_argument_group('Desktop-only',
       'These arguments apply only when running on desktop.')
@@ -153,6 +160,7 @@
   shell_config.origin = script_args.origin
   shell_config.map_url_list = script_args.map_url
   shell_config.map_origin_list = script_args.map_origin
+  shell_config.free_ports = script_args.free_ports
   shell_config.verbose = script_args.verbose
 
   # Android-only.