ソースを参照

gclient: implement exporting variables to .gni files

Bug: 570091
Change-Id: Ib2b966b5bc967de11a295b1636c1901faabea55f
Reviewed-on: https://chromium-review.googlesource.com/525540
Commit-Queue: Paweł Hajdan Jr. <phajdan.jr@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Paweł Hajdan, Jr 8 年 前
コミット
572537307e
4 ファイル変更75 行追加0 行削除
  1. 47 0
      gclient.py
  2. 6 0
      gclient_eval.py
  3. 2 0
      testing_support/fake_repos.py
  4. 20 0
      tests/gclient_smoketest.py

+ 47 - 0
gclient.py

@@ -157,6 +157,36 @@ def ast2str(node, indent=0):
                               % (node.lineno, node.col_offset, t))
 
 
+class GNException(Exception):
+  pass
+
+
+def ToGNString(value, allow_dicts = True):
+  """Returns a stringified GN equivalent of the Python value.
+
+  allow_dicts indicates if this function will allow converting dictionaries
+  to GN scopes. This is only possible at the top level, you can't nest a
+  GN scope in a list, so this should be set to False for recursive calls."""
+  if isinstance(value, basestring):
+    if value.find('\n') >= 0:
+      raise GNException("Trying to print a string with a newline in it.")
+    return '"' + \
+        value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
+        '"'
+
+  if isinstance(value, unicode):
+    return ToGNString(value.encode('utf-8'))
+
+  if isinstance(value, bool):
+    if value:
+      return "true"
+    return "false"
+
+  # NOTE: some type handling removed compared to chromium/src copy.
+
+  raise GNException("Unsupported type when printing to GN.")
+
+
 class GClientKeywords(object):
   class VarImpl(object):
     def __init__(self, custom_vars, local_scope):
@@ -307,6 +337,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
     # Calculates properties:
     self._parsed_url = None
     self._dependencies = []
+    self._vars = {}
     # A cache of the files affected by the current operation, necessary for
     # hooks.
     self._file_list = []
@@ -315,6 +346,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
     # hosts will be allowed. Non-empty set means whitelist of hosts.
     # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
     self._allowed_hosts = frozenset()
+    # Spec for .gni output to write (if any).
+    self._gn_args_file = None
+    self._gn_args = []
     # If it is not set to True, the dependency wasn't processed for its child
     # dependency, i.e. its DEPS wasn't read.
     self._deps_parsed = False
@@ -582,6 +616,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
               'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
               (self.name, key, val))
 
+    self._vars = local_scope.get('vars', {})
+
     deps = local_scope.get('deps', {})
     if 'recursion' in local_scope:
       self.recursion_override = local_scope.get('recursion')
@@ -657,6 +693,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
             'ParseDepsFile(%s): allowed_hosts must be absent '
             'or a non-empty iterable' % self.name)
 
+    self._gn_args_file = local_scope.get('gclient_gn_args_file')
+    self._gn_args = local_scope.get('gclient_gn_args', [])
+
     # Convert the deps into real Dependency.
     deps_to_add = []
     for name, dep_value in deps.iteritems():
@@ -790,6 +829,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
 
     # Always parse the DEPS file.
     self.ParseDepsFile()
+    if self._gn_args_file and command == 'update':
+      self.WriteGNArgsFile()
     self._run_is_done(file_list or [], parsed_url)
     if command in ('update', 'revert') and not options.noprehooks:
       self.RunPreDepsHooks()
@@ -855,6 +896,12 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
         else:
           print('Skipped missing %s' % cwd, file=sys.stderr)
 
+  def WriteGNArgsFile(self):
+    lines = ['# Generated from %r' % self.deps_file]
+    for arg in self._gn_args:
+      lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
+    with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
+      f.write('\n'.join(lines))
 
   @gclient_utils.lockedmethod
   def _run_is_done(self, file_list, parsed_url):

+ 6 - 0
gclient_eval.py

@@ -59,6 +59,12 @@ _GCLIENT_SCHEMA = schema.Schema({
         }
     },
 
+    # Path to GN args file to write selected variables.
+    schema.Optional('gclient_gn_args_file'): basestring,
+
+    # Subset of variables to write to the GN args file (see above).
+    schema.Optional('gclient_gn_args'): [schema.Optional(basestring)],
+
     # Hooks executed after gclient sync (unless suppressed), or explicitly
     # on gclient hooks. See _GCLIENT_HOOKS_SCHEMA for details.
     # Also see 'pre_deps_hooks'.

+ 2 - 0
testing_support/fake_repos.py

@@ -324,6 +324,8 @@ class FakeRepos(FakeReposBase):
 vars = {
   'DummyVariable': 'repo',
 }
+gclient_gn_args_file = 'src/gclient.args'
+gclient_gn_args = ['DummyVariable']
 deps = {
   'src/repo2': {
     'url': '%(git_base)srepo_2',

+ 20 - 0
tests/gclient_smoketest.py

@@ -319,6 +319,10 @@ class GClientSmokeGIT(GClientSmokeBase):
                                 ('repo_3@1', 'src/repo2/repo3'),
                                 ('repo_4@2', 'src/repo4'))
     tree['src/git_hooked2'] = 'git_hooked2'
+    tree['src/gclient.args'] = '\n'.join([
+        '# Generated from \'DEPS\'',
+        'DummyVariable = "repo"',
+    ])
     self.assertTree(tree)
     # Test incremental sync: delete-unversioned_trees isn't there.
     self.parseGclient(
@@ -331,6 +335,10 @@ class GClientSmokeGIT(GClientSmokeBase):
                                 ('repo_4@2', 'src/repo4'))
     tree['src/git_hooked1'] = 'git_hooked1'
     tree['src/git_hooked2'] = 'git_hooked2'
+    tree['src/gclient.args'] = '\n'.join([
+        '# Generated from \'DEPS\'',
+        'DummyVariable = "repo"',
+    ])
     self.assertTree(tree)
 
   def testSyncIgnoredSolutionName(self):
@@ -364,6 +372,10 @@ class GClientSmokeGIT(GClientSmokeBase):
                                 ('repo_2@2', 'src/repo2'),
                                 ('repo_3@1', 'src/repo2/repo3'),
                                 ('repo_4@2', 'src/repo4'))
+    tree['src/gclient.args'] = '\n'.join([
+        '# Generated from \'DEPS\'',
+        'DummyVariable = "repo"',
+    ])
     self.assertTree(tree)
 
   def testSyncJobs(self):
@@ -400,6 +412,10 @@ class GClientSmokeGIT(GClientSmokeBase):
                                 ('repo_3@1', 'src/repo2/repo3'),
                                 ('repo_4@2', 'src/repo4'))
     tree['src/git_hooked2'] = 'git_hooked2'
+    tree['src/gclient.args'] = '\n'.join([
+        '# Generated from \'DEPS\'',
+        'DummyVariable = "repo"',
+    ])
     self.assertTree(tree)
     # Test incremental sync: delete-unversioned_trees isn't there.
     self.parseGclient(
@@ -413,6 +429,10 @@ class GClientSmokeGIT(GClientSmokeBase):
                                 ('repo_4@2', 'src/repo4'))
     tree['src/git_hooked1'] = 'git_hooked1'
     tree['src/git_hooked2'] = 'git_hooked2'
+    tree['src/gclient.args'] = '\n'.join([
+        '# Generated from \'DEPS\'',
+        'DummyVariable = "repo"',
+    ])
     self.assertTree(tree)
 
   def testRunHooks(self):