Automatically discover the mojoconfig file.

This patch makes devtools automatically discover the `mojoconfig` file
in the lowest ancestor that contains it, unless `--no-config-file` is
passed or the file is indicated explicitly through `--config-file`.

Fixes domokit/devtools#28.

R=qsr@chromium.org

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

Cr-Mirrored-From: https://github.com/domokit/mojo
Cr-Mirrored-Commit: db2433b49dffdb46a448d6e097bb85c7812b9fba
diff --git a/devtoolslib/paths.py b/devtoolslib/paths.py
index 1b0bff4..502636a 100644
--- a/devtoolslib/paths.py
+++ b/devtoolslib/paths.py
@@ -13,9 +13,9 @@
 import sys
 
 
-def find_ancestor_with(relpath):
+def find_ancestor_with(relpath, start_path=None):
   """Returns the lowest ancestor of this file that contains |relpath|."""
-  cur_dir_path = os.path.abspath(os.path.dirname(__file__))
+  cur_dir_path = start_path or os.path.abspath(os.path.dirname(__file__))
   while True:
     if os.path.exists(os.path.join(cur_dir_path, relpath)):
       return cur_dir_path
@@ -27,6 +27,16 @@
       return None
 
 
+def find_within_ancestors(target_relpath, start_path=None):
+  """Returns the absolute path to |target_relpath| in the lowest ancestor of
+  |start_path| that contains it.
+  """
+  ancestor = find_ancestor_with(target_relpath, start_path)
+  if not ancestor:
+    return None
+  return os.path.join(ancestor, target_relpath)
+
+
 def infer_paths(is_android, is_debug, target_cpu):
   """Infers the locations of select build output artifacts in a regular
   Chromium-like checkout. This should grow thinner or disappear as we introduce
diff --git a/devtoolslib/shell_arguments.py b/devtoolslib/shell_arguments.py
index bd13755..3863935 100644
--- a/devtoolslib/shell_arguments.py
+++ b/devtoolslib/shell_arguments.py
@@ -180,7 +180,7 @@
   """
   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 as %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
diff --git a/devtoolslib/shell_config.py b/devtoolslib/shell_config.py
index 702be64..2b55d54 100644
--- a/devtoolslib/shell_config.py
+++ b/devtoolslib/shell_config.py
@@ -90,6 +90,9 @@
       'file.')
   config_file_group.add_argument('--config-file', type=file,
                                  help='Path of the configuration file to use.')
+  config_file_group.add_argument('--no-config-file', action='store_true',
+                                 help='Pass to skip automatic discovery of the '
+                                 'mojoconfig file.')
 
   # Arguments allowing to indicate the build directory we are targeting when
   # running within a Chromium-like checkout (e.g. Mojo checkout). These will go
@@ -109,6 +112,13 @@
                                      choices=['x64', 'x86', 'arm'])
 
 
+def _discover_config_file():
+  config_file_path = paths.find_within_ancestors('mojoconfig')
+  if not config_file_path:
+    return None
+  return open(config_file_path, 'r')
+
+
 def _read_config_file(config_file, aliases):
   spec = config_file.read()
   for alias_pattern, alias_value in aliases:
@@ -151,22 +161,29 @@
     shell_config.origin = inferred_paths['build_dir_path']
 
   # Read the mojoconfig file.
-  if script_args.config_file:
-    config_file_aliases = []
-    if inferred_paths['build_dir_path']:
-      config_file_aliases.append(('@{BUILD_DIR}',
-                                  inferred_paths['build_dir_path']))
+  config_file = script_args.config_file
+  if not script_args.no_config_file:
+    config_file = config_file or _discover_config_file()
 
-    mojoconfig = None
-    try:
-      mojoconfig = _read_config_file(script_args.config_file,
-                                     config_file_aliases)
-    except SyntaxError:
-      raise ShellConfigurationException('Failed to parse the mojoconfig file.')
+  if config_file:
+    with config_file:
+      config_file_aliases = []
+      if inferred_paths['build_dir_path']:
+        config_file_aliases.append(('@{BUILD_DIR}',
+                                    inferred_paths['build_dir_path']))
 
-    if 'dev_servers' in mojoconfig:
+      config = None
       try:
-        for dev_server_spec in mojoconfig['dev_servers']:
+        if script_args.verbose:
+          print 'Reading config file from: ' + config_file.name
+        config = _read_config_file(config_file, config_file_aliases)
+      except SyntaxError:
+        raise ShellConfigurationException('Failed to parse the mojoconfig '
+                                          'file.')
+
+    if 'dev_servers' in config:
+      try:
+        for dev_server_spec in config['dev_servers']:
           dev_server_config = DevServerConfig()
           dev_server_config.host = dev_server_spec['host']
           dev_server_config.mappings = []