Commit 8f8a50d0 authored by Edward Lemur's avatar Edward Lemur Committed by Commit Bot

gclient: Make built-in vars available for expansion.

Make it possible to refer to built-in variables without having to declare
then in DEPS files.

Bug: None
Change-Id: I5403963052463befc074f29750de56cce13927ce
Reviewed-on: https://chromium-review.googlesource.com/c/1312234Reviewed-by: 's avatarDirk Pranke <dpranke@chromium.org>
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
parent 5ee6b6e6
...@@ -681,7 +681,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): ...@@ -681,7 +681,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
try: try:
local_scope = gclient_eval.Parse( local_scope = gclient_eval.Parse(
deps_content, self._get_option('validate_syntax', False), deps_content, self._get_option('validate_syntax', False),
filepath, self.get_vars()) filepath, self.get_vars(), self.get_builtin_vars())
except SyntaxError as e: except SyntaxError as e:
gclient_utils.SyntaxErrorToError(filepath, e) gclient_utils.SyntaxErrorToError(filepath, e)
...@@ -1192,11 +1192,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): ...@@ -1192,11 +1192,8 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
d = d.parent d = d.parent
return tuple(out) return tuple(out)
def get_vars(self): def get_builtin_vars(self):
"""Returns a dictionary of effective variable values return {
(DEPS file contents with applied custom_vars overrides)."""
# Provide some built-in variables.
result = {
'checkout_android': 'android' in self.target_os, 'checkout_android': 'android' in self.target_os,
'checkout_chromeos': 'chromeos' in self.target_os, 'checkout_chromeos': 'chromeos' in self.target_os,
'checkout_fuchsia': 'fuchsia' in self.target_os, 'checkout_fuchsia': 'fuchsia' in self.target_os,
...@@ -1216,15 +1213,22 @@ class Dependency(gclient_utils.WorkItem, DependencySettings): ...@@ -1216,15 +1213,22 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
'checkout_x64': 'x64' in self.target_cpu, 'checkout_x64': 'x64' in self.target_cpu,
'host_cpu': detect_host_arch.HostArch(), 'host_cpu': detect_host_arch.HostArch(),
} }
# Variable precedence:
# - built-in def get_vars(self):
"""Returns a dictionary of effective variable values
(DEPS file contents with applied custom_vars overrides)."""
# Variable precedence (last has highest):
# - DEPS vars # - DEPS vars
# - parents, from first to last # - parents, from first to last
# - built-in
# - custom_vars overrides # - custom_vars overrides
result = {}
result.update(self._vars) result.update(self._vars)
if self.parent: if self.parent:
parent_vars = self.parent.get_vars() parent_vars = self.parent.get_vars()
result.update(parent_vars) result.update(parent_vars)
# Provide some built-in variables.
result.update(self.get_builtin_vars())
result.update(self.custom_vars or {}) result.update(self.custom_vars or {})
return result return result
......
...@@ -277,7 +277,7 @@ def _gclient_eval(node_or_string, filename='<unknown>', vars_dict=None): ...@@ -277,7 +277,7 @@ def _gclient_eval(node_or_string, filename='<unknown>', vars_dict=None):
return _convert(node_or_string) return _convert(node_or_string)
def Exec(content, filename='<unknown>', vars_override=None): def Exec(content, filename='<unknown>', vars_override=None, builtin_vars=None):
"""Safely execs a set of assignments.""" """Safely execs a set of assignments."""
def _validate_statement(node, local_scope): def _validate_statement(node, local_scope):
if not isinstance(node, ast.Assign): if not isinstance(node, ast.Assign):
...@@ -334,11 +334,15 @@ def Exec(content, filename='<unknown>', vars_override=None): ...@@ -334,11 +334,15 @@ def Exec(content, filename='<unknown>', vars_override=None):
# Update the parsed vars with the overrides, but only if they are already # Update the parsed vars with the overrides, but only if they are already
# present (overrides do not introduce new variables). # present (overrides do not introduce new variables).
vars_dict.update(value) vars_dict.update(value)
if vars_override:
vars_dict.update({ if builtin_vars:
k: v vars_dict.update(builtin_vars)
for k, v in vars_override.iteritems()
if k in vars_dict}) if vars_override:
vars_dict.update({
k: v
for k, v in vars_override.iteritems()
if k in vars_dict})
for name, node in statements.iteritems(): for name, node in statements.iteritems():
value = _gclient_eval(node, filename, vars_dict) value = _gclient_eval(node, filename, vars_dict)
...@@ -347,7 +351,8 @@ def Exec(content, filename='<unknown>', vars_override=None): ...@@ -347,7 +351,8 @@ def Exec(content, filename='<unknown>', vars_override=None):
return _GCLIENT_SCHEMA.validate(local_scope) return _GCLIENT_SCHEMA.validate(local_scope)
def ExecLegacy(content, filename='<unknown>', vars_override=None): def ExecLegacy(content, filename='<unknown>', vars_override=None,
builtin_vars=None):
"""Executes a DEPS file |content| using exec.""" """Executes a DEPS file |content| using exec."""
local_scope = {} local_scope = {}
global_scope = {'Var': lambda var_name: '{%s}' % var_name} global_scope = {'Var': lambda var_name: '{%s}' % var_name}
...@@ -358,11 +363,10 @@ def ExecLegacy(content, filename='<unknown>', vars_override=None): ...@@ -358,11 +363,10 @@ def ExecLegacy(content, filename='<unknown>', vars_override=None):
# as "exec a in b, c" (See https://bugs.python.org/issue21591). # as "exec a in b, c" (See https://bugs.python.org/issue21591).
eval(compile(content, filename, 'exec'), global_scope, local_scope) eval(compile(content, filename, 'exec'), global_scope, local_scope)
if 'vars' not in local_scope:
return local_scope
vars_dict = {} vars_dict = {}
vars_dict.update(local_scope['vars']) vars_dict.update(local_scope.get('vars', {}))
if builtin_vars:
vars_dict.update(builtin_vars)
if vars_override: if vars_override:
vars_dict.update({ vars_dict.update({
k: v k: v
...@@ -370,6 +374,9 @@ def ExecLegacy(content, filename='<unknown>', vars_override=None): ...@@ -370,6 +374,9 @@ def ExecLegacy(content, filename='<unknown>', vars_override=None):
if k in vars_dict if k in vars_dict
}) })
if not vars_dict:
return local_scope
def _DeepFormat(node): def _DeepFormat(node):
if isinstance(node, basestring): if isinstance(node, basestring):
return node.format(**vars_dict) return node.format(**vars_dict)
...@@ -453,7 +460,8 @@ def UpdateCondition(info_dict, op, new_condition): ...@@ -453,7 +460,8 @@ def UpdateCondition(info_dict, op, new_condition):
del info_dict['condition'] del info_dict['condition']
def Parse(content, validate_syntax, filename, vars_override=None): def Parse(content, validate_syntax, filename, vars_override=None,
builtin_vars=None):
"""Parses DEPS strings. """Parses DEPS strings.
Executes the Python-like string stored in content, resulting in a Python Executes the Python-like string stored in content, resulting in a Python
...@@ -468,15 +476,17 @@ def Parse(content, validate_syntax, filename, vars_override=None): ...@@ -468,15 +476,17 @@ def Parse(content, validate_syntax, filename, vars_override=None):
of the content, e.g. '<string>', '<unknown>'. of the content, e.g. '<string>', '<unknown>'.
vars_override: dict, optional. A dictionary with overrides for the variables vars_override: dict, optional. A dictionary with overrides for the variables
defined by the DEPS file. defined by the DEPS file.
builtin_vars: dict, optional. A dictionary with variables that are provided
by default.
Returns: Returns:
A Python dict with the parsed contents of the DEPS file, as specified by the A Python dict with the parsed contents of the DEPS file, as specified by the
schema above. schema above.
""" """
if validate_syntax: if validate_syntax:
result = Exec(content, filename, vars_override) result = Exec(content, filename, vars_override, builtin_vars)
else: else:
result = ExecLegacy(content, filename, vars_override) result = ExecLegacy(content, filename, vars_override, builtin_vars)
vars_dict = result.get('vars', {}) vars_dict = result.get('vars', {})
if 'deps' in result: if 'deps' in result:
......
...@@ -701,6 +701,55 @@ class ParseTest(unittest.TestCase): ...@@ -701,6 +701,55 @@ class ParseTest(unittest.TestCase):
'condition': 'baz'}}, 'condition': 'baz'}},
}, local_scope) }, local_scope)
def test_has_builtin_vars(self):
builtin_vars = {'builtin_var': 'foo'}
deps_file = '\n'.join([
'deps = {',
' "a_dep": "a{builtin_var}b",',
'}',
])
for validate_syntax in False, True:
local_scope = gclient_eval.Parse(
deps_file, validate_syntax, '<unknown>', None, builtin_vars)
self.assertEqual({
'deps': {'a_dep': {'url': 'afoob',
'dep_type': 'git'}},
}, local_scope)
def test_declaring_builtin_var_has_no_effect(self):
builtin_vars = {'builtin_var': 'foo'}
deps_file = '\n'.join([
'vars = {',
' "builtin_var": "bar",',
'}',
'deps = {',
' "a_dep": "a{builtin_var}b",',
'}',
])
for validate_syntax in False, True:
local_scope = gclient_eval.Parse(
deps_file, validate_syntax, '<unknown>', None, builtin_vars)
self.assertEqual({
'vars': {'builtin_var': 'bar'},
'deps': {'a_dep': {'url': 'afoob',
'dep_type': 'git'}},
}, local_scope)
def test_override_builtin_var(self):
builtin_vars = {'builtin_var': 'foo'}
vars_override = {'builtin_var': 'override'}
deps_file = '\n'.join([
'deps = {',
' "a_dep": "a{builtin_var}b",',
'}',
])
for validate_syntax in False, True:
local_scope = gclient_eval.Parse(
deps_file, validate_syntax, '<unknown>', vars_override, builtin_vars)
self.assertEqual({
'deps': {'a_dep': {'url': 'aoverrideb',
'dep_type': 'git'}},
}, local_scope, str(local_scope))
def test_expands_vars(self): def test_expands_vars(self):
for validate_syntax in True, False: for validate_syntax in True, False:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment