blob: 100840ed747657ba6a3440b0f64f443351d37712 [file] [log] [blame]
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001# Copyright 2017 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import ast
Paweł Hajdan, Jr7cf96a42017-05-26 20:28:35 +02006import collections
Edward Lemur16f4bad2018-05-16 16:53:49 -04007import logging
Edward Lemurd52b3062019-08-06 17:55:59 +00008import six
Raul Tambreb946b232019-03-26 14:48:46 +00009import sys
Edward Lesmes6f64a052018-03-20 17:35:49 -040010import tokenize
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020011
Edward Lemur16f4bad2018-05-16 16:53:49 -040012import gclient_utils
13
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +020014from third_party import schema
15
Edward Lemurba1b1f72019-07-27 00:41:59 +000016if six.PY2:
Raul Tambreb946b232019-03-26 14:48:46 +000017 # We use cStringIO.StringIO because it is equivalent to Py3's io.StringIO.
18 from cStringIO import StringIO
19else:
20 from io import StringIO
Aaron Gableac9b0f32019-04-18 17:38:37 +000021 # pylint: disable=redefined-builtin
22 basestring = str
23
24
Edward Lesmes6f64a052018-03-20 17:35:49 -040025class _NodeDict(collections.MutableMapping):
26 """Dict-like type that also stores information on AST nodes and tokens."""
27 def __init__(self, data, tokens=None):
28 self.data = collections.OrderedDict(data)
29 self.tokens = tokens
30
31 def __str__(self):
Raul Tambreb946b232019-03-26 14:48:46 +000032 return str({k: v[0] for k, v in self.data.items()})
Edward Lesmes6f64a052018-03-20 17:35:49 -040033
Edward Lemura1e4d482018-12-17 19:01:03 +000034 def __repr__(self):
35 return self.__str__()
36
Edward Lesmes6f64a052018-03-20 17:35:49 -040037 def __getitem__(self, key):
38 return self.data[key][0]
39
40 def __setitem__(self, key, value):
41 self.data[key] = (value, None)
42
43 def __delitem__(self, key):
44 del self.data[key]
45
46 def __iter__(self):
47 return iter(self.data)
48
49 def __len__(self):
50 return len(self.data)
51
Edward Lesmes3d993812018-04-02 12:52:49 -040052 def MoveTokens(self, origin, delta):
53 if self.tokens:
54 new_tokens = {}
Edward Lemurba1b1f72019-07-27 00:41:59 +000055 for pos, token in six.iteritems(self.tokens):
Edward Lesmes3d993812018-04-02 12:52:49 -040056 if pos[0] >= origin:
57 pos = (pos[0] + delta, pos[1])
58 token = token[:2] + (pos,) + token[3:]
59 new_tokens[pos] = token
60
61 for value, node in self.data.values():
62 if node.lineno >= origin:
63 node.lineno += delta
64 if isinstance(value, _NodeDict):
65 value.MoveTokens(origin, delta)
66
Edward Lesmes6f64a052018-03-20 17:35:49 -040067 def GetNode(self, key):
68 return self.data[key][1]
69
Edward Lesmes6c24d372018-03-28 12:52:29 -040070 def SetNode(self, key, value, node):
Edward Lesmes6f64a052018-03-20 17:35:49 -040071 self.data[key] = (value, node)
72
73
74def _NodeDictSchema(dict_schema):
75 """Validate dict_schema after converting _NodeDict to a regular dict."""
76 def validate(d):
77 schema.Schema(dict_schema).validate(dict(d))
78 return True
79 return validate
80
81
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +020082# See https://github.com/keleshev/schema for docs how to configure schema.
Edward Lesmes6f64a052018-03-20 17:35:49 -040083_GCLIENT_DEPS_SCHEMA = _NodeDictSchema({
Aaron Gableac9b0f32019-04-18 17:38:37 +000084 schema.Optional(basestring):
Raul Tambreb946b232019-03-26 14:48:46 +000085 schema.Or(
86 None,
Aaron Gableac9b0f32019-04-18 17:38:37 +000087 basestring,
Raul Tambreb946b232019-03-26 14:48:46 +000088 _NodeDictSchema({
89 # Repo and revision to check out under the path
90 # (same as if no dict was used).
Aaron Gableac9b0f32019-04-18 17:38:37 +000091 'url': schema.Or(None, basestring),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +020092
Raul Tambreb946b232019-03-26 14:48:46 +000093 # Optional condition string. The dep will only be processed
94 # if the condition evaluates to True.
Aaron Gableac9b0f32019-04-18 17:38:37 +000095 schema.Optional('condition'): basestring,
96 schema.Optional('dep_type', default='git'): basestring,
Raul Tambreb946b232019-03-26 14:48:46 +000097 }),
98 # CIPD package.
99 _NodeDictSchema({
100 'packages': [
101 _NodeDictSchema({
Aaron Gableac9b0f32019-04-18 17:38:37 +0000102 'package': basestring,
103 'version': basestring,
Raul Tambreb946b232019-03-26 14:48:46 +0000104 })
105 ],
Aaron Gableac9b0f32019-04-18 17:38:37 +0000106 schema.Optional('condition'): basestring,
107 schema.Optional('dep_type', default='cipd'): basestring,
Raul Tambreb946b232019-03-26 14:48:46 +0000108 }),
109 ),
Edward Lesmes6f64a052018-03-20 17:35:49 -0400110})
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +0200111
Raul Tambreb946b232019-03-26 14:48:46 +0000112_GCLIENT_HOOKS_SCHEMA = [
113 _NodeDictSchema({
114 # Hook action: list of command-line arguments to invoke.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000115 'action': [basestring],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200116
Raul Tambreb946b232019-03-26 14:48:46 +0000117 # Name of the hook. Doesn't affect operation.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000118 schema.Optional('name'): basestring,
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200119
Raul Tambreb946b232019-03-26 14:48:46 +0000120 # Hook pattern (regex). Originally intended to limit some hooks to run
121 # only when files matching the pattern have changed. In practice, with
122 # git, gclient runs all the hooks regardless of this field.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000123 schema.Optional('pattern'): basestring,
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200124
Raul Tambreb946b232019-03-26 14:48:46 +0000125 # Working directory where to execute the hook.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000126 schema.Optional('cwd'): basestring,
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200127
Raul Tambreb946b232019-03-26 14:48:46 +0000128 # Optional condition string. The hook will only be run
129 # if the condition evaluates to True.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000130 schema.Optional('condition'): basestring,
Raul Tambreb946b232019-03-26 14:48:46 +0000131 })
132]
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200133
Raul Tambreb946b232019-03-26 14:48:46 +0000134_GCLIENT_SCHEMA = schema.Schema(
135 _NodeDictSchema({
136 # List of host names from which dependencies are allowed (whitelist).
137 # NOTE: when not present, all hosts are allowed.
138 # NOTE: scoped to current DEPS file, not recursive.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000139 schema.Optional('allowed_hosts'): [schema.Optional(basestring)],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200140
Raul Tambreb946b232019-03-26 14:48:46 +0000141 # Mapping from paths to repo and revision to check out under that path.
142 # Applying this mapping to the on-disk checkout is the main purpose
143 # of gclient, and also why the config file is called DEPS.
144 #
145 # The following functions are allowed:
146 #
147 # Var(): allows variable substitution (either from 'vars' dict below,
148 # or command-line override)
Aaron Gableac9b0f32019-04-18 17:38:37 +0000149 schema.Optional('deps'): _GCLIENT_DEPS_SCHEMA,
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200150
Raul Tambreb946b232019-03-26 14:48:46 +0000151 # Similar to 'deps' (see above) - also keyed by OS (e.g. 'linux').
152 # Also see 'target_os'.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000153 schema.Optional('deps_os'): _NodeDictSchema({
154 schema.Optional(basestring): _GCLIENT_DEPS_SCHEMA,
155 }),
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200156
Raul Tambreb946b232019-03-26 14:48:46 +0000157 # Dependency to get gclient_gn_args* settings from. This allows these
158 # values to be set in a recursedeps file, rather than requiring that
159 # they exist in the top-level solution.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000160 schema.Optional('gclient_gn_args_from'): basestring,
Michael Moss848c86e2018-05-03 16:05:50 -0700161
Raul Tambreb946b232019-03-26 14:48:46 +0000162 # Path to GN args file to write selected variables.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000163 schema.Optional('gclient_gn_args_file'): basestring,
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200164
Raul Tambreb946b232019-03-26 14:48:46 +0000165 # Subset of variables to write to the GN args file (see above).
Aaron Gableac9b0f32019-04-18 17:38:37 +0000166 schema.Optional('gclient_gn_args'): [schema.Optional(basestring)],
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200167
Raul Tambreb946b232019-03-26 14:48:46 +0000168 # Hooks executed after gclient sync (unless suppressed), or explicitly
169 # on gclient hooks. See _GCLIENT_HOOKS_SCHEMA for details.
170 # Also see 'pre_deps_hooks'.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000171 schema.Optional('hooks'): _GCLIENT_HOOKS_SCHEMA,
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200172
Raul Tambreb946b232019-03-26 14:48:46 +0000173 # Similar to 'hooks', also keyed by OS.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000174 schema.Optional('hooks_os'): _NodeDictSchema({
175 schema.Optional(basestring): _GCLIENT_HOOKS_SCHEMA
176 }),
Scott Grahamc4826742017-05-11 16:59:23 -0700177
Raul Tambreb946b232019-03-26 14:48:46 +0000178 # Rules which #includes are allowed in the directory.
179 # Also see 'skip_child_includes' and 'specific_include_rules'.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000180 schema.Optional('include_rules'): [schema.Optional(basestring)],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200181
Raul Tambreb946b232019-03-26 14:48:46 +0000182 # Hooks executed before processing DEPS. See 'hooks' for more details.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000183 schema.Optional('pre_deps_hooks'): _GCLIENT_HOOKS_SCHEMA,
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200184
Raul Tambreb946b232019-03-26 14:48:46 +0000185 # Recursion limit for nested DEPS.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000186 schema.Optional('recursion'): int,
Paweł Hajdan, Jr6f796792017-06-02 08:40:06 +0200187
Raul Tambreb946b232019-03-26 14:48:46 +0000188 # Whitelists deps for which recursion should be enabled.
189 schema.Optional('recursedeps'): [
Aaron Gableac9b0f32019-04-18 17:38:37 +0000190 schema.Optional(schema.Or(
191 basestring,
192 (basestring, basestring),
193 [basestring, basestring]
194 )),
Raul Tambreb946b232019-03-26 14:48:46 +0000195 ],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200196
Raul Tambreb946b232019-03-26 14:48:46 +0000197 # Blacklists directories for checking 'include_rules'.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000198 schema.Optional('skip_child_includes'): [schema.Optional(basestring)],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200199
Raul Tambreb946b232019-03-26 14:48:46 +0000200 # Mapping from paths to include rules specific for that path.
201 # See 'include_rules' for more details.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000202 schema.Optional('specific_include_rules'): _NodeDictSchema({
203 schema.Optional(basestring): [basestring]
204 }),
Paweł Hajdan, Jrb7e53332017-05-23 16:57:37 +0200205
Raul Tambreb946b232019-03-26 14:48:46 +0000206 # List of additional OS names to consider when selecting dependencies
207 # from deps_os.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000208 schema.Optional('target_os'): [schema.Optional(basestring)],
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200209
Raul Tambreb946b232019-03-26 14:48:46 +0000210 # For recursed-upon sub-dependencies, check out their own dependencies
211 # relative to the parent's path, rather than relative to the .gclient
212 # file.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000213 schema.Optional('use_relative_paths'): bool,
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200214
Raul Tambreb946b232019-03-26 14:48:46 +0000215 # For recursed-upon sub-dependencies, run their hooks relative to the
216 # parent's path instead of relative to the .gclient file.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000217 schema.Optional('use_relative_hooks'): bool,
Corentin Walleza68660d2018-09-10 17:33:24 +0000218
Raul Tambreb946b232019-03-26 14:48:46 +0000219 # Variables that can be referenced using Var() - see 'deps'.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000220 schema.Optional('vars'): _NodeDictSchema({
221 schema.Optional(basestring): schema.Or(basestring, bool),
222 }),
Raul Tambreb946b232019-03-26 14:48:46 +0000223 }))
Paweł Hajdan, Jrbeec0062017-05-10 21:51:05 +0200224
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200225
Edward Lemure05f18d2018-06-08 17:36:53 +0000226def _gclient_eval(node_or_string, filename='<unknown>', vars_dict=None):
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200227 """Safely evaluates a single expression. Returns the result."""
228 _allowed_names = {'None': None, 'True': True, 'False': False}
Aaron Gableac9b0f32019-04-18 17:38:37 +0000229 if isinstance(node_or_string, basestring):
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200230 node_or_string = ast.parse(node_or_string, filename=filename, mode='eval')
231 if isinstance(node_or_string, ast.Expression):
232 node_or_string = node_or_string.body
233 def _convert(node):
234 if isinstance(node, ast.Str):
Edward Lemure05f18d2018-06-08 17:36:53 +0000235 if vars_dict is None:
Edward Lesmes01cb5102018-06-05 00:45:44 +0000236 return node.s
Edward Lesmes6c24d372018-03-28 12:52:29 -0400237 try:
238 return node.s.format(**vars_dict)
239 except KeyError as e:
Edward Lemure05f18d2018-06-08 17:36:53 +0000240 raise KeyError(
Edward Lesmes6c24d372018-03-28 12:52:29 -0400241 '%s was used as a variable, but was not declared in the vars dict '
242 '(file %r, line %s)' % (
Edward Lemurba1b1f72019-07-27 00:41:59 +0000243 e.args[0], filename, getattr(node, 'lineno', '<unknown>')))
Paweł Hajdan, Jr6f796792017-06-02 08:40:06 +0200244 elif isinstance(node, ast.Num):
245 return node.n
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200246 elif isinstance(node, ast.Tuple):
247 return tuple(map(_convert, node.elts))
248 elif isinstance(node, ast.List):
249 return list(map(_convert, node.elts))
250 elif isinstance(node, ast.Dict):
Edward Lesmes6f64a052018-03-20 17:35:49 -0400251 return _NodeDict((_convert(k), (_convert(v), v))
Paweł Hajdan, Jr7cf96a42017-05-26 20:28:35 +0200252 for k, v in zip(node.keys, node.values))
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200253 elif isinstance(node, ast.Name):
254 if node.id not in _allowed_names:
255 raise ValueError(
256 'invalid name %r (file %r, line %s)' % (
257 node.id, filename, getattr(node, 'lineno', '<unknown>')))
258 return _allowed_names[node.id]
Raul Tambreb946b232019-03-26 14:48:46 +0000259 elif not sys.version_info[:2] < (3, 4) and isinstance(
260 node, ast.NameConstant): # Since Python 3.4
261 return node.value
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200262 elif isinstance(node, ast.Call):
Edward Lesmes9f531292018-03-20 21:27:15 -0400263 if not isinstance(node.func, ast.Name) or node.func.id != 'Var':
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200264 raise ValueError(
Edward Lesmes9f531292018-03-20 21:27:15 -0400265 'Var is the only allowed function (file %r, line %s)' % (
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200266 filename, getattr(node, 'lineno', '<unknown>')))
Raul Tambreb946b232019-03-26 14:48:46 +0000267 if node.keywords or getattr(node, 'starargs', None) or getattr(
268 node, 'kwargs', None) or len(node.args) != 1:
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200269 raise ValueError(
Edward Lesmes9f531292018-03-20 21:27:15 -0400270 'Var takes exactly one argument (file %r, line %s)' % (
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200271 filename, getattr(node, 'lineno', '<unknown>')))
Edward Lesmes9f531292018-03-20 21:27:15 -0400272 arg = _convert(node.args[0])
Aaron Gableac9b0f32019-04-18 17:38:37 +0000273 if not isinstance(arg, basestring):
Edward Lesmes9f531292018-03-20 21:27:15 -0400274 raise ValueError(
275 'Var\'s argument must be a variable name (file %r, line %s)' % (
276 filename, getattr(node, 'lineno', '<unknown>')))
Edward Lesmes6c24d372018-03-28 12:52:29 -0400277 if vars_dict is None:
Edward Lemure05f18d2018-06-08 17:36:53 +0000278 return '{' + arg + '}'
Edward Lesmes6c24d372018-03-28 12:52:29 -0400279 if arg not in vars_dict:
Edward Lemure05f18d2018-06-08 17:36:53 +0000280 raise KeyError(
Edward Lesmes6c24d372018-03-28 12:52:29 -0400281 '%s was used as a variable, but was not declared in the vars dict '
282 '(file %r, line %s)' % (
283 arg, filename, getattr(node, 'lineno', '<unknown>')))
284 return vars_dict[arg]
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200285 elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
286 return _convert(node.left) + _convert(node.right)
Paweł Hajdan, Jrb7e53332017-05-23 16:57:37 +0200287 elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mod):
288 return _convert(node.left) % _convert(node.right)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200289 else:
290 raise ValueError(
Paweł Hajdan, Jr1ba610b2017-05-24 20:14:44 +0200291 'unexpected AST node: %s %s (file %r, line %s)' % (
292 node, ast.dump(node), filename,
293 getattr(node, 'lineno', '<unknown>')))
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200294 return _convert(node_or_string)
295
296
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000297def Exec(content, filename='<unknown>', vars_override=None, builtin_vars=None):
Edward Lesmes6c24d372018-03-28 12:52:29 -0400298 """Safely execs a set of assignments."""
299 def _validate_statement(node, local_scope):
300 if not isinstance(node, ast.Assign):
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200301 raise ValueError(
Paweł Hajdan, Jr1ba610b2017-05-24 20:14:44 +0200302 'unexpected AST node: %s %s (file %r, line %s)' % (
303 node, ast.dump(node), filename,
304 getattr(node, 'lineno', '<unknown>')))
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200305
Edward Lesmes6c24d372018-03-28 12:52:29 -0400306 if len(node.targets) != 1:
307 raise ValueError(
308 'invalid assignment: use exactly one target (file %r, line %s)' % (
309 filename, getattr(node, 'lineno', '<unknown>')))
310
311 target = node.targets[0]
312 if not isinstance(target, ast.Name):
313 raise ValueError(
314 'invalid assignment: target should be a name (file %r, line %s)' % (
315 filename, getattr(node, 'lineno', '<unknown>')))
316 if target.id in local_scope:
317 raise ValueError(
318 'invalid assignment: overrides var %r (file %r, line %s)' % (
319 target.id, filename, getattr(node, 'lineno', '<unknown>')))
320
321 node_or_string = ast.parse(content, filename=filename, mode='exec')
322 if isinstance(node_or_string, ast.Expression):
323 node_or_string = node_or_string.body
324
325 if not isinstance(node_or_string, ast.Module):
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200326 raise ValueError(
Paweł Hajdan, Jr1ba610b2017-05-24 20:14:44 +0200327 'unexpected AST node: %s %s (file %r, line %s)' % (
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200328 node_or_string,
Paweł Hajdan, Jr1ba610b2017-05-24 20:14:44 +0200329 ast.dump(node_or_string),
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200330 filename,
331 getattr(node_or_string, 'lineno', '<unknown>')))
332
Edward Lesmes6c24d372018-03-28 12:52:29 -0400333 statements = {}
334 for statement in node_or_string.body:
335 _validate_statement(statement, statements)
336 statements[statement.targets[0].id] = statement.value
337
Raul Tambreb946b232019-03-26 14:48:46 +0000338 # The tokenized representation needs to end with a newline token, otherwise
339 # untokenization will trigger an assert later on.
340 # In Python 2.7 on Windows we need to ensure the input ends with a newline
341 # for a newline token to be generated.
342 # In other cases a newline token is always generated during tokenization so
343 # this has no effect.
344 # TODO: Remove this workaround after migrating to Python 3.
345 content += '\n'
Edward Lesmes6c24d372018-03-28 12:52:29 -0400346 tokens = {
Raul Tambreb946b232019-03-26 14:48:46 +0000347 token[2]: list(token) for token in tokenize.generate_tokens(
348 StringIO(content).readline)
Edward Lesmes6c24d372018-03-28 12:52:29 -0400349 }
Raul Tambreb946b232019-03-26 14:48:46 +0000350
Edward Lesmes6c24d372018-03-28 12:52:29 -0400351 local_scope = _NodeDict({}, tokens)
352
353 # Process vars first, so we can expand variables in the rest of the DEPS file.
354 vars_dict = {}
355 if 'vars' in statements:
356 vars_statement = statements['vars']
Edward Lemure05f18d2018-06-08 17:36:53 +0000357 value = _gclient_eval(vars_statement, filename)
Edward Lesmes6c24d372018-03-28 12:52:29 -0400358 local_scope.SetNode('vars', value, vars_statement)
359 # Update the parsed vars with the overrides, but only if they are already
360 # present (overrides do not introduce new variables).
361 vars_dict.update(value)
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000362
363 if builtin_vars:
364 vars_dict.update(builtin_vars)
365
366 if vars_override:
Raul Tambreb946b232019-03-26 14:48:46 +0000367 vars_dict.update({k: v for k, v in vars_override.items() if k in vars_dict})
Edward Lesmes6c24d372018-03-28 12:52:29 -0400368
Raul Tambreb946b232019-03-26 14:48:46 +0000369 for name, node in statements.items():
Edward Lemure05f18d2018-06-08 17:36:53 +0000370 value = _gclient_eval(node, filename, vars_dict)
Edward Lesmes6c24d372018-03-28 12:52:29 -0400371 local_scope.SetNode(name, value, node)
372
John Budorick0f7b2002018-01-19 15:46:17 -0800373 return _GCLIENT_SCHEMA.validate(local_scope)
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200374
375
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000376def ExecLegacy(content, filename='<unknown>', vars_override=None,
377 builtin_vars=None):
Edward Lemur16f4bad2018-05-16 16:53:49 -0400378 """Executes a DEPS file |content| using exec."""
Edward Lesmes6c24d372018-03-28 12:52:29 -0400379 local_scope = {}
380 global_scope = {'Var': lambda var_name: '{%s}' % var_name}
381
382 # If we use 'exec' directly, it complains that 'Parse' contains a nested
383 # function with free variables.
384 # This is because on versions of Python < 2.7.9, "exec(a, b, c)" not the same
385 # as "exec a in b, c" (See https://bugs.python.org/issue21591).
386 eval(compile(content, filename, 'exec'), global_scope, local_scope)
387
Edward Lesmes6c24d372018-03-28 12:52:29 -0400388 vars_dict = {}
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000389 vars_dict.update(local_scope.get('vars', {}))
390 if builtin_vars:
391 vars_dict.update(builtin_vars)
Edward Lesmes6c24d372018-03-28 12:52:29 -0400392 if vars_override:
Raul Tambreb946b232019-03-26 14:48:46 +0000393 vars_dict.update({k: v for k, v in vars_override.items() if k in vars_dict})
Edward Lesmes6c24d372018-03-28 12:52:29 -0400394
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000395 if not vars_dict:
396 return local_scope
397
Edward Lesmes6c24d372018-03-28 12:52:29 -0400398 def _DeepFormat(node):
Aaron Gableac9b0f32019-04-18 17:38:37 +0000399 if isinstance(node, basestring):
Edward Lesmes6c24d372018-03-28 12:52:29 -0400400 return node.format(**vars_dict)
401 elif isinstance(node, dict):
Raul Tambreb946b232019-03-26 14:48:46 +0000402 return {k.format(**vars_dict): _DeepFormat(v) for k, v in node.items()}
Edward Lesmes6c24d372018-03-28 12:52:29 -0400403 elif isinstance(node, list):
404 return [_DeepFormat(elem) for elem in node]
405 elif isinstance(node, tuple):
406 return tuple(_DeepFormat(elem) for elem in node)
407 else:
408 return node
409
410 return _DeepFormat(local_scope)
411
412
Edward Lemur16f4bad2018-05-16 16:53:49 -0400413def _StandardizeDeps(deps_dict, vars_dict):
414 """"Standardizes the deps_dict.
415
416 For each dependency:
417 - Expands the variable in the dependency name.
418 - Ensures the dependency is a dictionary.
419 - Set's the 'dep_type' to be 'git' by default.
420 """
421 new_deps_dict = {}
422 for dep_name, dep_info in deps_dict.items():
423 dep_name = dep_name.format(**vars_dict)
424 if not isinstance(dep_info, collections.Mapping):
425 dep_info = {'url': dep_info}
426 dep_info.setdefault('dep_type', 'git')
427 new_deps_dict[dep_name] = dep_info
428 return new_deps_dict
429
430
431def _MergeDepsOs(deps_dict, os_deps_dict, os_name):
432 """Merges the deps in os_deps_dict into conditional dependencies in deps_dict.
433
434 The dependencies in os_deps_dict are transformed into conditional dependencies
435 using |'checkout_' + os_name|.
436 If the dependency is already present, the URL and revision must coincide.
437 """
438 for dep_name, dep_info in os_deps_dict.items():
439 # Make this condition very visible, so it's not a silent failure.
440 # It's unclear how to support None override in deps_os.
441 if dep_info['url'] is None:
442 logging.error('Ignoring %r:%r in %r deps_os', dep_name, dep_info, os_name)
443 continue
444
445 os_condition = 'checkout_' + (os_name if os_name != 'unix' else 'linux')
446 UpdateCondition(dep_info, 'and', os_condition)
447
448 if dep_name in deps_dict:
449 if deps_dict[dep_name]['url'] != dep_info['url']:
450 raise gclient_utils.Error(
451 'Value from deps_os (%r; %r: %r) conflicts with existing deps '
452 'entry (%r).' % (
453 os_name, dep_name, dep_info, deps_dict[dep_name]))
454
455 UpdateCondition(dep_info, 'or', deps_dict[dep_name].get('condition'))
456
457 deps_dict[dep_name] = dep_info
458
459
460def UpdateCondition(info_dict, op, new_condition):
461 """Updates info_dict's condition with |new_condition|.
462
463 An absent value is treated as implicitly True.
464 """
465 curr_condition = info_dict.get('condition')
466 # Easy case: Both are present.
467 if curr_condition and new_condition:
468 info_dict['condition'] = '(%s) %s (%s)' % (
469 curr_condition, op, new_condition)
470 # If |op| == 'and', and at least one condition is present, then use it.
471 elif op == 'and' and (curr_condition or new_condition):
472 info_dict['condition'] = curr_condition or new_condition
473 # Otherwise, no condition should be set
474 elif curr_condition:
475 del info_dict['condition']
476
477
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000478def Parse(content, validate_syntax, filename, vars_override=None,
479 builtin_vars=None):
Edward Lemur16f4bad2018-05-16 16:53:49 -0400480 """Parses DEPS strings.
481
482 Executes the Python-like string stored in content, resulting in a Python
483 dictionary specifyied by the schema above. Supports syntax validation and
484 variable expansion.
485
486 Args:
487 content: str. DEPS file stored as a string.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400488 validate_syntax: bool. Whether syntax should be validated using the schema
489 defined above.
490 filename: str. The name of the DEPS file, or a string describing the source
491 of the content, e.g. '<string>', '<unknown>'.
492 vars_override: dict, optional. A dictionary with overrides for the variables
493 defined by the DEPS file.
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000494 builtin_vars: dict, optional. A dictionary with variables that are provided
495 by default.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400496
497 Returns:
498 A Python dict with the parsed contents of the DEPS file, as specified by the
499 schema above.
500 """
501 if validate_syntax:
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000502 result = Exec(content, filename, vars_override, builtin_vars)
Edward Lemur16f4bad2018-05-16 16:53:49 -0400503 else:
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000504 result = ExecLegacy(content, filename, vars_override, builtin_vars)
Edward Lemur16f4bad2018-05-16 16:53:49 -0400505
506 vars_dict = result.get('vars', {})
507 if 'deps' in result:
508 result['deps'] = _StandardizeDeps(result['deps'], vars_dict)
509
510 if 'deps_os' in result:
511 deps = result.setdefault('deps', {})
Edward Lemurba1b1f72019-07-27 00:41:59 +0000512 for os_name, os_deps in six.iteritems(result['deps_os']):
Edward Lemur16f4bad2018-05-16 16:53:49 -0400513 os_deps = _StandardizeDeps(os_deps, vars_dict)
514 _MergeDepsOs(deps, os_deps, os_name)
515 del result['deps_os']
516
517 if 'hooks_os' in result:
518 hooks = result.setdefault('hooks', [])
Edward Lemurba1b1f72019-07-27 00:41:59 +0000519 for os_name, os_hooks in six.iteritems(result['hooks_os']):
Edward Lemur16f4bad2018-05-16 16:53:49 -0400520 for hook in os_hooks:
521 UpdateCondition(hook, 'and', 'checkout_' + os_name)
522 hooks.extend(os_hooks)
523 del result['hooks_os']
524
525 return result
526
527
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200528def EvaluateCondition(condition, variables, referenced_variables=None):
529 """Safely evaluates a boolean condition. Returns the result."""
530 if not referenced_variables:
531 referenced_variables = set()
532 _allowed_names = {'None': None, 'True': True, 'False': False}
533 main_node = ast.parse(condition, mode='eval')
534 if isinstance(main_node, ast.Expression):
535 main_node = main_node.body
Ben Pastenea541b282019-05-24 00:25:12 +0000536 def _convert(node, allow_tuple=False):
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200537 if isinstance(node, ast.Str):
538 return node.s
Ben Pastenea541b282019-05-24 00:25:12 +0000539 elif isinstance(node, ast.Tuple) and allow_tuple:
540 return tuple(map(_convert, node.elts))
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200541 elif isinstance(node, ast.Name):
542 if node.id in referenced_variables:
543 raise ValueError(
544 'invalid cyclic reference to %r (inside %r)' % (
545 node.id, condition))
546 elif node.id in _allowed_names:
547 return _allowed_names[node.id]
548 elif node.id in variables:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200549 value = variables[node.id]
550
551 # Allow using "native" types, without wrapping everything in strings.
552 # Note that schema constraints still apply to variables.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000553 if not isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200554 return value
555
556 # Recursively evaluate the variable reference.
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200557 return EvaluateCondition(
558 variables[node.id],
559 variables,
560 referenced_variables.union([node.id]))
561 else:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200562 # Implicitly convert unrecognized names to strings.
563 # If we want to change this, we'll need to explicitly distinguish
564 # between arguments for GN to be passed verbatim, and ones to
565 # be evaluated.
566 return node.id
Edward Lemurba1b1f72019-07-27 00:41:59 +0000567 elif not sys.version_info[:2] < (3, 4) and isinstance(
568 node, ast.NameConstant): # Since Python 3.4
569 return node.value
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200570 elif isinstance(node, ast.BoolOp) and isinstance(node.op, ast.Or):
571 if len(node.values) != 2:
572 raise ValueError(
573 'invalid "or": exactly 2 operands required (inside %r)' % (
574 condition))
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200575 left = _convert(node.values[0])
576 right = _convert(node.values[1])
577 if not isinstance(left, bool):
578 raise ValueError(
579 'invalid "or" operand %r (inside %r)' % (left, condition))
580 if not isinstance(right, bool):
581 raise ValueError(
582 'invalid "or" operand %r (inside %r)' % (right, condition))
583 return left or right
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200584 elif isinstance(node, ast.BoolOp) and isinstance(node.op, ast.And):
585 if len(node.values) != 2:
586 raise ValueError(
587 'invalid "and": exactly 2 operands required (inside %r)' % (
588 condition))
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200589 left = _convert(node.values[0])
590 right = _convert(node.values[1])
591 if not isinstance(left, bool):
592 raise ValueError(
593 'invalid "and" operand %r (inside %r)' % (left, condition))
594 if not isinstance(right, bool):
595 raise ValueError(
596 'invalid "and" operand %r (inside %r)' % (right, condition))
597 return left and right
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200598 elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200599 value = _convert(node.operand)
600 if not isinstance(value, bool):
601 raise ValueError(
602 'invalid "not" operand %r (inside %r)' % (value, condition))
603 return not value
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200604 elif isinstance(node, ast.Compare):
605 if len(node.ops) != 1:
606 raise ValueError(
607 'invalid compare: exactly 1 operator required (inside %r)' % (
608 condition))
609 if len(node.comparators) != 1:
610 raise ValueError(
611 'invalid compare: exactly 1 comparator required (inside %r)' % (
612 condition))
613
614 left = _convert(node.left)
Ben Pastenea541b282019-05-24 00:25:12 +0000615 right = _convert(
616 node.comparators[0], allow_tuple=isinstance(node.ops[0], ast.In))
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200617
618 if isinstance(node.ops[0], ast.Eq):
619 return left == right
Dirk Pranke77b76872017-10-05 18:29:27 -0700620 if isinstance(node.ops[0], ast.NotEq):
621 return left != right
Ben Pastenea541b282019-05-24 00:25:12 +0000622 if isinstance(node.ops[0], ast.In):
623 return left in right
Paweł Hajdan, Jr76c6ea22017-06-02 21:46:57 +0200624
625 raise ValueError(
626 'unexpected operator: %s %s (inside %r)' % (
627 node.ops[0], ast.dump(node), condition))
628 else:
629 raise ValueError(
630 'unexpected AST node: %s %s (inside %r)' % (
631 node, ast.dump(node), condition))
632 return _convert(main_node)
Edward Lesmes6f64a052018-03-20 17:35:49 -0400633
634
635def RenderDEPSFile(gclient_dict):
636 contents = sorted(gclient_dict.tokens.values(), key=lambda token: token[2])
Raul Tambreb946b232019-03-26 14:48:46 +0000637 # The last token is a newline, which we ensure in Exec() for compatibility.
638 # However tests pass in inputs not ending with a newline and expect the same
639 # back, so for backwards compatibility need to remove that newline character.
640 # TODO: Fix tests to expect the newline
641 return tokenize.untokenize(contents)[:-1]
Edward Lesmes6f64a052018-03-20 17:35:49 -0400642
643
644def _UpdateAstString(tokens, node, value):
645 position = node.lineno, node.col_offset
Edward Lemur5cc2afd2018-08-28 00:54:45 +0000646 quote_char = ''
647 if isinstance(node, ast.Str):
648 quote_char = tokens[position][1][0]
Edward Lesmes62af4e42018-03-30 18:15:44 -0400649 tokens[position][1] = quote_char + value + quote_char
Edward Lesmes6f64a052018-03-20 17:35:49 -0400650 node.s = value
651
652
Edward Lesmes3d993812018-04-02 12:52:49 -0400653def _ShiftLinesInTokens(tokens, delta, start):
654 new_tokens = {}
655 for token in tokens.values():
656 if token[2][0] >= start:
657 token[2] = token[2][0] + delta, token[2][1]
658 token[3] = token[3][0] + delta, token[3][1]
659 new_tokens[token[2]] = token
660 return new_tokens
661
662
663def AddVar(gclient_dict, var_name, value):
664 if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
665 raise ValueError(
666 "Can't use SetVar for the given gclient dict. It contains no "
667 "formatting information.")
668
669 if 'vars' not in gclient_dict:
670 raise KeyError("vars dict is not defined.")
671
672 if var_name in gclient_dict['vars']:
673 raise ValueError(
674 "%s has already been declared in the vars dict. Consider using SetVar "
675 "instead." % var_name)
676
677 if not gclient_dict['vars']:
678 raise ValueError('vars dict is empty. This is not yet supported.')
679
Edward Lesmes8d626572018-04-05 17:53:10 -0400680 # We will attempt to add the var right after 'vars = {'.
681 node = gclient_dict.GetNode('vars')
Edward Lesmes3d993812018-04-02 12:52:49 -0400682 if node is None:
683 raise ValueError(
684 "The vars dict has no formatting information." % var_name)
Edward Lesmes8d626572018-04-05 17:53:10 -0400685 line = node.lineno + 1
686
687 # We will try to match the new var's indentation to the next variable.
688 col = node.keys[0].col_offset
Edward Lesmes3d993812018-04-02 12:52:49 -0400689
690 # We use a minimal Python dictionary, so that ast can parse it.
691 var_content = '{\n%s"%s": "%s",\n}' % (' ' * col, var_name, value)
692 var_ast = ast.parse(var_content).body[0].value
693
694 # Set the ast nodes for the key and value.
695 vars_node = gclient_dict.GetNode('vars')
696
697 var_name_node = var_ast.keys[0]
698 var_name_node.lineno += line - 2
699 vars_node.keys.insert(0, var_name_node)
700
701 value_node = var_ast.values[0]
702 value_node.lineno += line - 2
703 vars_node.values.insert(0, value_node)
704
705 # Update the tokens.
Raul Tambreb946b232019-03-26 14:48:46 +0000706 var_tokens = list(tokenize.generate_tokens(StringIO(var_content).readline))
Edward Lesmes3d993812018-04-02 12:52:49 -0400707 var_tokens = {
708 token[2]: list(token)
709 # Ignore the tokens corresponding to braces and new lines.
710 for token in var_tokens[2:-2]
711 }
712
713 gclient_dict.tokens = _ShiftLinesInTokens(gclient_dict.tokens, 1, line)
714 gclient_dict.tokens.update(_ShiftLinesInTokens(var_tokens, line - 2, 0))
715
716
Edward Lesmes6f64a052018-03-20 17:35:49 -0400717def SetVar(gclient_dict, var_name, value):
718 if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
719 raise ValueError(
720 "Can't use SetVar for the given gclient dict. It contains no "
721 "formatting information.")
722 tokens = gclient_dict.tokens
723
Edward Lesmes3d993812018-04-02 12:52:49 -0400724 if 'vars' not in gclient_dict:
725 raise KeyError("vars dict is not defined.")
726
727 if var_name not in gclient_dict['vars']:
Edward Lesmes6f64a052018-03-20 17:35:49 -0400728 raise ValueError(
Edward Lesmes3d993812018-04-02 12:52:49 -0400729 "%s has not been declared in the vars dict. Consider using AddVar "
730 "instead." % var_name)
Edward Lesmes6f64a052018-03-20 17:35:49 -0400731
732 node = gclient_dict['vars'].GetNode(var_name)
733 if node is None:
734 raise ValueError(
735 "The vars entry for %s has no formatting information." % var_name)
736
737 _UpdateAstString(tokens, node, value)
Edward Lesmes6c24d372018-03-28 12:52:29 -0400738 gclient_dict['vars'].SetNode(var_name, value, node)
Edward Lesmes6f64a052018-03-20 17:35:49 -0400739
740
Edward Lemura1e4d482018-12-17 19:01:03 +0000741def _GetVarName(node):
742 if isinstance(node, ast.Call):
743 return node.args[0].s
744 elif node.s.endswith('}'):
745 last_brace = node.s.rfind('{')
746 return node.s[last_brace+1:-1]
747 return None
748
749
Edward Lesmes6f64a052018-03-20 17:35:49 -0400750def SetCIPD(gclient_dict, dep_name, package_name, new_version):
751 if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
752 raise ValueError(
753 "Can't use SetCIPD for the given gclient dict. It contains no "
754 "formatting information.")
755 tokens = gclient_dict.tokens
756
757 if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
Edward Lesmes3d993812018-04-02 12:52:49 -0400758 raise KeyError(
Edward Lesmes6f64a052018-03-20 17:35:49 -0400759 "Could not find any dependency called %s." % dep_name)
760
761 # Find the package with the given name
762 packages = [
763 package
764 for package in gclient_dict['deps'][dep_name]['packages']
765 if package['package'] == package_name
766 ]
767 if len(packages) != 1:
768 raise ValueError(
769 "There must be exactly one package with the given name (%s), "
770 "%s were found." % (package_name, len(packages)))
771
772 # TODO(ehmaldonado): Support Var in package's version.
773 node = packages[0].GetNode('version')
774 if node is None:
775 raise ValueError(
776 "The deps entry for %s:%s has no formatting information." %
777 (dep_name, package_name))
778
Edward Lemura1e4d482018-12-17 19:01:03 +0000779 if not isinstance(node, ast.Call) and not isinstance(node, ast.Str):
780 raise ValueError(
781 "Unsupported dependency revision format. Please file a bug to the "
Edward Lemurfb8c1a22018-12-17 20:44:18 +0000782 "Infra>SDK component in crbug.com")
Edward Lemura1e4d482018-12-17 19:01:03 +0000783
784 var_name = _GetVarName(node)
785 if var_name is not None:
786 SetVar(gclient_dict, var_name, new_version)
787 else:
788 _UpdateAstString(tokens, node, new_version)
789 packages[0].SetNode('version', new_version, node)
Edward Lesmes6f64a052018-03-20 17:35:49 -0400790
791
Edward Lesmes9f531292018-03-20 21:27:15 -0400792def SetRevision(gclient_dict, dep_name, new_revision):
Edward Lesmes62af4e42018-03-30 18:15:44 -0400793 def _UpdateRevision(dep_dict, dep_key, new_revision):
794 dep_node = dep_dict.GetNode(dep_key)
795 if dep_node is None:
796 raise ValueError(
797 "The deps entry for %s has no formatting information." % dep_name)
798
799 node = dep_node
800 if isinstance(node, ast.BinOp):
801 node = node.right
802
803 if not isinstance(node, ast.Call) and not isinstance(node, ast.Str):
804 raise ValueError(
Edward Lemura1e4d482018-12-17 19:01:03 +0000805 "Unsupported dependency revision format. Please file a bug to the "
Edward Lemurfb8c1a22018-12-17 20:44:18 +0000806 "Infra>SDK component in crbug.com")
Edward Lesmes62af4e42018-03-30 18:15:44 -0400807
808 var_name = _GetVarName(node)
809 if var_name is not None:
810 SetVar(gclient_dict, var_name, new_revision)
811 else:
812 if '@' in node.s:
Edward Lesmes1118a212018-04-05 18:37:07 -0400813 # '@' is part of the last string, which we want to modify. Discard
814 # whatever was after the '@' and put the new revision in its place.
Edward Lesmes62af4e42018-03-30 18:15:44 -0400815 new_revision = node.s.split('@')[0] + '@' + new_revision
Edward Lesmes1118a212018-04-05 18:37:07 -0400816 elif '@' not in dep_dict[dep_key]:
817 # '@' is not part of the URL at all. This mean the dependency is
818 # unpinned and we should pin it.
819 new_revision = node.s + '@' + new_revision
Edward Lesmes62af4e42018-03-30 18:15:44 -0400820 _UpdateAstString(tokens, node, new_revision)
821 dep_dict.SetNode(dep_key, new_revision, node)
822
Edward Lesmes6f64a052018-03-20 17:35:49 -0400823 if not isinstance(gclient_dict, _NodeDict) or gclient_dict.tokens is None:
824 raise ValueError(
825 "Can't use SetRevision for the given gclient dict. It contains no "
826 "formatting information.")
827 tokens = gclient_dict.tokens
828
829 if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
Edward Lesmes3d993812018-04-02 12:52:49 -0400830 raise KeyError(
Edward Lesmes6f64a052018-03-20 17:35:49 -0400831 "Could not find any dependency called %s." % dep_name)
832
Edward Lesmes6f64a052018-03-20 17:35:49 -0400833 if isinstance(gclient_dict['deps'][dep_name], _NodeDict):
Edward Lesmes62af4e42018-03-30 18:15:44 -0400834 _UpdateRevision(gclient_dict['deps'][dep_name], 'url', new_revision)
Edward Lesmes6f64a052018-03-20 17:35:49 -0400835 else:
Edward Lesmes62af4e42018-03-30 18:15:44 -0400836 _UpdateRevision(gclient_dict['deps'], dep_name, new_revision)
Edward Lesmes411041f2018-04-05 20:12:55 -0400837
838
839def GetVar(gclient_dict, var_name):
840 if 'vars' not in gclient_dict or var_name not in gclient_dict['vars']:
841 raise KeyError(
842 "Could not find any variable called %s." % var_name)
843
844 return gclient_dict['vars'][var_name]
845
846
847def GetCIPD(gclient_dict, dep_name, package_name):
848 if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
849 raise KeyError(
850 "Could not find any dependency called %s." % dep_name)
851
852 # Find the package with the given name
853 packages = [
854 package
855 for package in gclient_dict['deps'][dep_name]['packages']
856 if package['package'] == package_name
857 ]
858 if len(packages) != 1:
859 raise ValueError(
860 "There must be exactly one package with the given name (%s), "
861 "%s were found." % (package_name, len(packages)))
862
Edward Lemura92b9612018-07-03 02:34:32 +0000863 return packages[0]['version']
Edward Lesmes411041f2018-04-05 20:12:55 -0400864
865
866def GetRevision(gclient_dict, dep_name):
867 if 'deps' not in gclient_dict or dep_name not in gclient_dict['deps']:
868 raise KeyError(
869 "Could not find any dependency called %s." % dep_name)
870
871 dep = gclient_dict['deps'][dep_name]
872 if dep is None:
873 return None
874 elif isinstance(dep, basestring):
875 _, _, revision = dep.partition('@')
876 return revision or None
877 elif isinstance(dep, collections.Mapping) and 'url' in dep:
878 _, _, revision = dep['url'].partition('@')
879 return revision or None
880 else:
881 raise ValueError(
882 '%s is not a valid git dependency.' % dep_name)