1# MIT License 2# 3# Copyright The SCons Foundation 4# 5# Permission is hereby granted, free of charge, to any person obtaining 6# a copy of this software and associated documentation files (the 7# "Software"), to deal in the Software without restriction, including 8# without limitation the rights to use, copy, modify, merge, publish, 9# distribute, sublicense, and/or sell copies of the Software, and to 10# permit persons to whom the Software is furnished to do so, subject to 11# the following conditions: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 17# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 18# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 24"""Handle lists of directory paths. 25 26These are the path lists that get set as CPPPATH, LIBPATH, 27etc.) with as much caching of data and efficiency as we can, while 28still keeping the evaluation delayed so that we Do the Right Thing 29(almost) regardless of how the variable is specified. 30""" 31 32import os 33 34import SCons.Memoize 35import SCons.Node 36import SCons.Util 37 38# 39# Variables to specify the different types of entries in a PathList object: 40# 41 42TYPE_STRING_NO_SUBST = 0 # string with no '$' 43TYPE_STRING_SUBST = 1 # string containing '$' 44TYPE_OBJECT = 2 # other object 45 46def node_conv(obj): 47 """ 48 This is the "string conversion" routine that we have our substitutions 49 use to return Nodes, not strings. This relies on the fact that an 50 EntryProxy object has a get() method that returns the underlying 51 Node that it wraps, which is a bit of architectural dependence 52 that we might need to break or modify in the future in response to 53 additional requirements. 54 """ 55 try: 56 get = obj.get 57 except AttributeError: 58 if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ): 59 result = obj 60 else: 61 result = str(obj) 62 else: 63 result = get() 64 return result 65 66class _PathList: 67 """ 68 An actual PathList object. 69 """ 70 def __init__(self, pathlist): 71 """ 72 Initializes a PathList object, canonicalizing the input and 73 pre-processing it for quicker substitution later. 74 75 The stored representation of the PathList is a list of tuples 76 containing (type, value), where the "type" is one of the TYPE_* 77 variables defined above. We distinguish between: 78 79 strings that contain no '$' and therefore need no 80 delayed-evaluation string substitution (we expect that there 81 will be many of these and that we therefore get a pretty 82 big win from avoiding string substitution) 83 84 strings that contain '$' and therefore need substitution 85 (the hard case is things like '${TARGET.dir}/include', 86 which require re-evaluation for every target + source) 87 88 other objects (which may be something like an EntryProxy 89 that needs a method called to return a Node) 90 91 Pre-identifying the type of each element in the PathList up-front 92 and storing the type in the list of tuples is intended to reduce 93 the amount of calculation when we actually do the substitution 94 over and over for each target. 95 """ 96 if SCons.Util.is_String(pathlist): 97 pathlist = pathlist.split(os.pathsep) 98 elif not SCons.Util.is_Sequence(pathlist): 99 pathlist = [pathlist] 100 101 pl = [] 102 for p in pathlist: 103 try: 104 found = '$' in p 105 except (AttributeError, TypeError): 106 type = TYPE_OBJECT 107 else: 108 if not found: 109 type = TYPE_STRING_NO_SUBST 110 else: 111 type = TYPE_STRING_SUBST 112 pl.append((type, p)) 113 114 self.pathlist = tuple(pl) 115 116 def __len__(self): return len(self.pathlist) 117 118 def __getitem__(self, i): return self.pathlist[i] 119 120 def subst_path(self, env, target, source): 121 """ 122 Performs construction variable substitution on a pre-digested 123 PathList for a specific target and source. 124 """ 125 result = [] 126 for type, value in self.pathlist: 127 if type == TYPE_STRING_SUBST: 128 value = env.subst(value, target=target, source=source, 129 conv=node_conv) 130 if SCons.Util.is_Sequence(value): 131 result.extend(SCons.Util.flatten(value)) 132 elif value: 133 result.append(value) 134 elif type == TYPE_OBJECT: 135 value = node_conv(value) 136 if value: 137 result.append(value) 138 elif value: 139 result.append(value) 140 return tuple(result) 141 142 143class PathListCache: 144 """ 145 A class to handle caching of PathList lookups. 146 147 This class gets instantiated once and then deleted from the namespace, 148 so it's used as a Singleton (although we don't enforce that in the 149 usual Pythonic ways). We could have just made the cache a dictionary 150 in the module namespace, but putting it in this class allows us to 151 use the same Memoizer pattern that we use elsewhere to count cache 152 hits and misses, which is very valuable. 153 154 Lookup keys in the cache are computed by the _PathList_key() method. 155 Cache lookup should be quick, so we don't spend cycles canonicalizing 156 all forms of the same lookup key. For example, 'x:y' and ['x', 157 'y'] logically represent the same list, but we don't bother to 158 split string representations and treat those two equivalently. 159 (Note, however, that we do, treat lists and tuples the same.) 160 161 The main type of duplication we're trying to catch will come from 162 looking up the same path list from two different clones of the 163 same construction environment. That is, given 164 165 env2 = env1.Clone() 166 167 both env1 and env2 will have the same CPPPATH value, and we can 168 cheaply avoid re-parsing both values of CPPPATH by using the 169 common value from this cache. 170 """ 171 def __init__(self): 172 self._memo = {} 173 174 def _PathList_key(self, pathlist): 175 """ 176 Returns the key for memoization of PathLists. 177 178 Note that we want this to be pretty quick, so we don't completely 179 canonicalize all forms of the same list. For example, 180 'dir1:$ROOT/dir2' and ['$ROOT/dir1', 'dir'] may logically 181 represent the same list if you're executing from $ROOT, but 182 we're not going to bother splitting strings into path elements, 183 or massaging strings into Nodes, to identify that equivalence. 184 We just want to eliminate obvious redundancy from the normal 185 case of re-using exactly the same cloned value for a path. 186 """ 187 if SCons.Util.is_Sequence(pathlist): 188 pathlist = tuple(SCons.Util.flatten(pathlist)) 189 return pathlist 190 191 @SCons.Memoize.CountDictCall(_PathList_key) 192 def PathList(self, pathlist): 193 """ 194 Returns the cached _PathList object for the specified pathlist, 195 creating and caching a new object as necessary. 196 """ 197 pathlist = self._PathList_key(pathlist) 198 try: 199 memo_dict = self._memo['PathList'] 200 except KeyError: 201 memo_dict = {} 202 self._memo['PathList'] = memo_dict 203 else: 204 try: 205 return memo_dict[pathlist] 206 except KeyError: 207 pass 208 209 result = _PathList(pathlist) 210 211 memo_dict[pathlist] = result 212 213 return result 214 215PathList = PathListCache().PathList 216 217 218del PathListCache 219 220# Local Variables: 221# tab-width:4 222# indent-tabs-mode:nil 223# End: 224# vim: set expandtab tabstop=4 shiftwidth=4: 225