Add validate command to gclient

Inspired by https://chromium-review.googlesource.com/c/411515/ .

Bug: 570091
Change-Id: I7bf9653178c06125ae8be1dee021acfc187b2bdc
Reviewed-on: https://chromium-review.googlesource.com/497848
Commit-Queue: Paweł Hajdan Jr. <phajdan.jr@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
diff --git a/gclient.py b/gclient.py
index 6d7988a..1270e03 100755
--- a/gclient.py
+++ b/gclient.py
@@ -96,6 +96,7 @@
 import urlparse
 
 import fix_encoding
+import gclient_eval
 import gclient_scm
 import gclient_utils
 import git_cache
@@ -606,6 +607,8 @@
         exec(deps_content, global_scope, local_scope)
       except SyntaxError as e:
         gclient_utils.SyntaxErrorToError(filepath, e)
+      if self._get_option('validate_syntax', False):
+        gclient_eval.Check(deps_content, filepath, global_scope, local_scope)
       if use_strict:
         for key, val in local_scope.iteritems():
           if not isinstance(val, (dict, list, tuple, str)):
@@ -721,6 +724,12 @@
     self.add_dependencies_and_close(deps_to_add, hooks_to_run)
     logging.info('ParseDepsFile(%s) done' % self.name)
 
+  def _get_option(self, attr, default):
+    obj = self
+    while not hasattr(obj, '_options'):
+      obj = obj.parent
+    return getattr(obj._options, attr, default)
+
   def add_dependencies_and_close(self, deps_to_add, hooks):
     """Adds the dependencies, hooks and mark the parsing as done."""
     for dep in deps_to_add:
@@ -757,7 +766,7 @@
     # When running runhooks, there's no need to consult the SCM.
     # All known hooks are expected to run unconditionally regardless of working
     # copy state, so skip the SCM status check.
-    run_scm = command not in ('runhooks', 'recurse', None)
+    run_scm = command not in ('runhooks', 'recurse', 'validate', None)
     parsed_url = self.LateOverride(self.url)
     file_list = [] if not options.nohooks else None
     revision_override = revision_overrides.pop(self.name, None)
@@ -1346,7 +1355,8 @@
     revision_overrides = {}
     # It's unnecessary to check for revision overrides for 'recurse'.
     # Save a few seconds by not calling _EnforceRevisions() in that case.
-    if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
+    if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
+                       'validate'):
       self._CheckConfig()
       revision_overrides = self._EnforceRevisions()
     pm = None
@@ -1354,7 +1364,7 @@
     if (setup_color.IS_TTY and not self._options.verbose and progress):
       if command in ('update', 'revert'):
         pm = Progress('Syncing projects', 1)
-      elif command == 'recurse':
+      elif command in ('recurse', 'validate'):
         pm = Progress(' '.join(args), 1)
     work_queue = gclient_utils.ExecutionQueue(
         self._options.jobs, pm, ignore_requirements=ignore_requirements,
@@ -1862,6 +1872,8 @@
                     help='DEPRECATED: This is a no-op.')
   parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
                     help='DEPRECATED: This is a no-op.')
+  parser.add_option('--validate-syntax', action='store_true',
+                    help='Validate the .gclient and DEPS syntax')
   (options, args) = parser.parse_args(args)
   client = GClient.LoadCurrentConfig(options)
 
@@ -1892,6 +1904,19 @@
 CMDupdate = CMDsync
 
 
+def CMDvalidate(parser, args):
+  """Validates the .gclient and DEPS syntax."""
+  options, args = parser.parse_args(args)
+  options.validate_syntax = True
+  client = GClient.LoadCurrentConfig(options)
+  rv = client.RunOnDeps('validate', args)
+  if rv == 0:
+    print('validate: SUCCESS')
+  else:
+    print('validate: FAILURE')
+  return rv
+
+
 def CMDdiff(parser, args):
   """Displays local diff for every dependencies."""
   parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',