gclient: freeze vars to prevent accidental modification

This will also be useful for other values (deps_os, hooks_os)
we may want to store and keep constant.

Freeze code copied from luci/recipes-py @ 944125e6d1e8c831d09517bde658a38d8f81db37

Bug: 570091
Change-Id: I3365cf2b267c478316870bbb3fd41e9955aa4ddf
Reviewed-on: https://chromium-review.googlesource.com/531166
Commit-Queue: Paweł Hajdan Jr. <phajdan.jr@chromium.org>
Reviewed-by: Andrii Shyshkalov <tandrii@chromium.org>
changes/66/531166/3
Paweł Hajdan, Jr 9 years ago committed by Commit Bot
parent ab49a2bbd9
commit 7e50261c2c

@ -616,7 +616,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
'ParseDepsFile(%s): Strict mode disallows %r -> %r' % 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
(self.name, key, val)) (self.name, key, val))
self._vars = local_scope.get('vars', {}) # Since we heavily post-process things, freeze ones which should
# reflect original state of DEPS.
self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
deps = local_scope.get('deps', {}) deps = local_scope.get('deps', {})
if 'recursion' in local_scope: if 'recursion' in local_scope:
@ -718,8 +720,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
condition = dep_value.get('condition') condition = dep_value.get('condition')
if condition: if condition:
# TODO(phajdan.jr): should we also take custom vars into account? # TODO(phajdan.jr): should we also take custom vars into account?
condition_value = gclient_eval.EvaluateCondition( condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
condition, local_scope.get('vars', {}))
should_process = should_process and condition_value should_process = should_process and condition_value
deps_to_add.append(Dependency( deps_to_add.append(Dependency(
self, name, url, None, None, self.custom_vars, None, self, name, url, None, None, self.custom_vars, None,

@ -5,10 +5,12 @@
"""Generic utils.""" """Generic utils."""
import codecs import codecs
import collections
import contextlib import contextlib
import cStringIO import cStringIO
import datetime import datetime
import logging import logging
import operator
import os import os
import pipes import pipes
import platform import platform
@ -1236,3 +1238,67 @@ def FindExecutable(executable):
if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK): if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
return alt_target return alt_target
return None return None
def freeze(obj):
"""Takes a generic object ``obj``, and returns an immutable version of it.
Supported types:
* dict / OrderedDict -> FrozenDict
* list -> tuple
* set -> frozenset
* any object with a working __hash__ implementation (assumes that hashable
means immutable)
Will raise TypeError if you pass an object which is not hashable.
"""
if isinstance(obj, dict):
return FrozenDict((freeze(k), freeze(v)) for k, v in obj.iteritems())
elif isinstance(obj, (list, tuple)):
return tuple(freeze(i) for i in obj)
elif isinstance(obj, set):
return frozenset(freeze(i) for i in obj)
else:
hash(obj)
return obj
class FrozenDict(collections.Mapping):
"""An immutable OrderedDict.
Modified From: http://stackoverflow.com/a/2704866
"""
def __init__(self, *args, **kwargs):
self._d = collections.OrderedDict(*args, **kwargs)
# Calculate the hash immediately so that we know all the items are
# hashable too.
self._hash = reduce(operator.xor,
(hash(i) for i in enumerate(self._d.iteritems())), 0)
def __eq__(self, other):
if not isinstance(other, collections.Mapping):
return NotImplemented
if self is other:
return True
if len(self) != len(other):
return False
for k, v in self.iteritems():
if k not in other or other[k] != v:
return False
return True
def __iter__(self):
return iter(self._d)
def __len__(self):
return len(self._d)
def __getitem__(self, key):
return self._d[key]
def __hash__(self):
return self._hash
def __repr__(self):
return 'FrozenDict(%r)' % (self._d.items(),)

Loading…
Cancel
Save