1#!/usr/bin/python
2
3# Copyright 2014 Google Inc.
4#
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
8import collections
9import types
10
11# The goal of this class is to store a set of unique items in the order in
12# which they are inserted. This is important for the final makefile, where
13# we want to make sure the image decoders are in a particular order. See
14# images.gyp for more information.
15class OrderedSet(object):
16  """Ordered set of unique items that supports addition and removal.
17
18  Retains the order in which items are inserted.
19  """
20
21  def __init__(self):
22    self.__ordered_set = []
23
24  def add(self, item):
25    """Add item, if it is not already in the set.
26
27    item is appended to the end if it is not already in the set.
28
29    Args:
30      item: The item to add.
31    """
32    if item not in self.__ordered_set:
33      self.__ordered_set.append(item)
34
35  def __contains__(self, item):
36    """Whether the set contains item.
37
38    Args:
39      item: The item to search for in the set.
40
41    Returns:
42      bool: Whether the item is in the set.
43    """
44    return item in self.__ordered_set
45
46  def __iter__(self):
47    """Iterator for the set.
48    """
49    return self.__ordered_set.__iter__()
50
51  def remove(self, item):
52    """
53    Remove item from the set.
54
55    Args:
56      item: Item to be removed.
57
58    Raises:
59      ValueError if item is not in the set.
60    """
61    self.__ordered_set.remove(item)
62
63  def __len__(self):
64    """Number of items in the set.
65    """
66    return len(self.__ordered_set)
67
68  def __getitem__(self, index):
69    """Return item at index.
70    """
71    return self.__ordered_set[index]
72
73  def reset(self):
74    """Reset to empty.
75    """
76    self.__ordered_set = []
77
78  def set(self, other):
79    """Replace this ordered set with another.
80
81    Args:
82      other: OrderedSet to replace this one. After this call, this OrderedSet
83        will contain exactly the same elements as other.
84    """
85    self.__ordered_set = list(other.__ordered_set)
86
87VAR_NAMES = ['LOCAL_CFLAGS',
88             'LOCAL_CPPFLAGS',
89             'LOCAL_SRC_FILES',
90             'LOCAL_SHARED_LIBRARIES',
91             'LOCAL_STATIC_LIBRARIES',
92             'LOCAL_C_INCLUDES',
93             'LOCAL_EXPORT_C_INCLUDE_DIRS',
94             'DEFINES',
95             'KNOWN_TARGETS',
96             # These are not parsed by gyp, but set manually.
97             'LOCAL_MODULE_TAGS',
98             'LOCAL_MODULE']
99
100class VarsDict(collections.namedtuple('VarsDict', VAR_NAMES)):
101  """Custom class for storing the arguments to Android.mk variables.
102
103  Can also be treated as a dictionary with fixed keys.
104  """
105
106  __slots__ = ()
107
108  def __new__(cls):
109    lists = []
110    # TODO (scroggo): Is there a better way add N items?
111    for __unused__ in range(len(VAR_NAMES)):
112      lists.append(OrderedSet())
113    return tuple.__new__(cls, lists)
114
115  def keys(self):
116    """Return the field names as strings.
117    """
118    return self._fields
119
120  def __getitem__(self, index):
121    """Return an item, indexed by a number or a string.
122    """
123    if type(index) == types.IntType:
124      # Treat the index as an array index into a tuple.
125      return tuple.__getitem__(self, index)
126    if type(index) == types.StringType:
127      # Treat the index as a key into a dictionary.
128      return eval('self.%s' % index)
129    return None
130
131
132def intersect(var_dict_list):
133  """Compute intersection of VarsDicts.
134
135  Find the intersection of a list of VarsDicts and trim each input to its
136  unique entries.
137
138  Args:
139    var_dict_list: list of VarsDicts. WARNING: each VarsDict will be
140      modified in place, to remove the common elements!
141  Returns:
142    VarsDict containing list entries common to all VarsDicts in
143      var_dict_list
144  """
145  intersection = VarsDict()
146  # First VarsDict
147  var_dict_a = var_dict_list[0]
148  # The rest.
149  other_var_dicts = var_dict_list[1:]
150
151  for key in var_dict_a.keys():
152    # Copy A's list, so we can continue iterating after modifying the original.
153    a_list = list(var_dict_a[key])
154    for item in a_list:
155      # If item is in all lists, add to intersection, and remove from all.
156      in_all_lists = True
157      for var_dict in other_var_dicts:
158        if not item in var_dict[key]:
159          in_all_lists = False
160          break
161      if in_all_lists:
162        intersection[key].add(item)
163        for var_dict in var_dict_list:
164          var_dict[key].remove(item)
165  return intersection
166
167