Commit 7e50261c authored by Paweł Hajdan, Jr's avatar Paweł Hajdan, Jr Committed by Commit Bot

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: 's avatarAndrii Shyshkalov <tandrii@chromium.org>
parent ab49a2bb
......@@ -616,7 +616,9 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
(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', {})
if 'recursion' in local_scope:
......@@ -718,8 +720,7 @@ class Dependency(gclient_utils.WorkItem, DependencySettings):
condition = dep_value.get('condition')
if condition:
# TODO(phajdan.jr): should we also take custom vars into account?
condition_value = gclient_eval.EvaluateCondition(
condition, local_scope.get('vars', {}))
condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
should_process = should_process and condition_value
deps_to_add.append(Dependency(
self, name, url, None, None, self.custom_vars, None,
......
......@@ -5,10 +5,12 @@
"""Generic utils."""
import codecs
import collections
import contextlib
import cStringIO
import datetime
import logging
import operator
import os
import pipes
import platform
......@@ -1236,3 +1238,67 @@ def FindExecutable(executable):
if os.path.isfile(alt_target) and os.access(alt_target, os.X_OK):
return alt_target
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(),)
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