From 572537307e46403e256d862a8309f1aa1fcb45bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Hajdan=2C=20Jr?= Date: Tue, 6 Jun 2017 23:49:11 +0200 Subject: [PATCH] gclient: implement exporting variables to .gni files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: 570091 Change-Id: Ib2b966b5bc967de11a295b1636c1901faabea55f Reviewed-on: https://chromium-review.googlesource.com/525540 Commit-Queue: Paweł Hajdan Jr. Reviewed-by: Dirk Pranke --- gclient.py | 47 +++++++++++++++++++++++++++++++++++ gclient_eval.py | 6 +++++ testing_support/fake_repos.py | 2 ++ tests/gclient_smoketest.py | 20 +++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/gclient.py b/gclient.py index e651c6cb2..ad8915b54 100755 --- a/gclient.py +++ b/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): diff --git a/gclient_eval.py b/gclient_eval.py index 81930b0d0..182ff9a28 100644 --- a/gclient_eval.py +++ b/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'. diff --git a/testing_support/fake_repos.py b/testing_support/fake_repos.py index 16ba5bac2..b2f13e695 100755 --- a/testing_support/fake_repos.py +++ b/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', diff --git a/tests/gclient_smoketest.py b/tests/gclient_smoketest.py index 0677f08b7..46b9b668b 100755 --- a/tests/gclient_smoketest.py +++ b/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):