Remove redundant code
[scons.git] / SCons / PathList.py
blob33ac7e58bec96be4d87c750f6aef9a1e239f057e
1 # MIT License
3 # Copyright The SCons Foundation
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:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
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.
24 """Handle lists of directory paths.
26 These are the path lists that get set as CPPPATH, LIBPATH,
27 etc.) with as much caching of data and efficiency as we can, while
28 still keeping the evaluation delayed so that we Do the Right Thing
29 (almost) regardless of how the variable is specified.
30 """
32 import os
34 import SCons.Memoize
35 import SCons.Node
36 import SCons.Util
39 # Variables to specify the different types of entries in a PathList object:
42 TYPE_STRING_NO_SUBST = 0 # string with no '$'
43 TYPE_STRING_SUBST = 1 # string containing '$'
44 TYPE_OBJECT = 2 # other object
46 def 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
66 class _PathList:
67 """An actual PathList object."""
69 def __init__(self, pathlist, split=True) -> None:
70 """
71 Initializes a PathList object, canonicalizing the input and
72 pre-processing it for quicker substitution later.
74 The stored representation of the PathList is a list of tuples
75 containing (type, value), where the "type" is one of the TYPE_*
76 variables defined above. We distinguish between:
78 strings that contain no '$' and therefore need no
79 delayed-evaluation string substitution (we expect that there
80 will be many of these and that we therefore get a pretty
81 big win from avoiding string substitution)
83 strings that contain '$' and therefore need substitution
84 (the hard case is things like '${TARGET.dir}/include',
85 which require re-evaluation for every target + source)
87 other objects (which may be something like an EntryProxy
88 that needs a method called to return a Node)
90 Pre-identifying the type of each element in the PathList up-front
91 and storing the type in the list of tuples is intended to reduce
92 the amount of calculation when we actually do the substitution
93 over and over for each target.
94 """
95 if SCons.Util.is_String(pathlist):
96 if split:
97 pathlist = pathlist.split(os.pathsep)
98 else: # no splitting, but still need a list
99 pathlist = [pathlist]
100 elif not SCons.Util.is_Sequence(pathlist):
101 pathlist = [pathlist]
103 pl = []
104 for p in pathlist:
105 try:
106 found = '$' in p
107 except (AttributeError, TypeError):
108 type = TYPE_OBJECT
109 else:
110 if not found:
111 type = TYPE_STRING_NO_SUBST
112 else:
113 type = TYPE_STRING_SUBST
114 pl.append((type, p))
116 self.pathlist = tuple(pl)
118 def __len__(self) -> int: return len(self.pathlist)
120 def __getitem__(self, i): return self.pathlist[i]
122 def subst_path(self, env, target, source):
124 Performs construction variable substitution on a pre-digested
125 PathList for a specific target and source.
127 result = []
128 for type, value in self.pathlist:
129 if type == TYPE_STRING_SUBST:
130 value = env.subst(value, target=target, source=source,
131 conv=node_conv)
132 if SCons.Util.is_Sequence(value):
133 result.extend(SCons.Util.flatten(value))
134 elif value:
135 result.append(value)
136 elif type == TYPE_OBJECT:
137 value = node_conv(value)
138 if value:
139 result.append(value)
140 elif value:
141 result.append(value)
142 return tuple(result)
145 class PathListCache:
146 """A class to handle caching of PathList lookups.
148 This class gets instantiated once and then deleted from the namespace,
149 so it's used as a Singleton (although we don't enforce that in the
150 usual Pythonic ways). We could have just made the cache a dictionary
151 in the module namespace, but putting it in this class allows us to
152 use the same Memoizer pattern that we use elsewhere to count cache
153 hits and misses, which is very valuable.
155 Lookup keys in the cache are computed by the _PathList_key() method.
156 Cache lookup should be quick, so we don't spend cycles canonicalizing
157 all forms of the same lookup key. For example, 'x:y' and ['x',
158 'y'] logically represent the same list, but we don't bother to
159 split string representations and treat those two equivalently.
160 (Note, however, that we do, treat lists and tuples the same.)
162 The main type of duplication we're trying to catch will come from
163 looking up the same path list from two different clones of the
164 same construction environment. That is, given
166 env2 = env1.Clone()
168 both env1 and env2 will have the same CPPPATH value, and we can
169 cheaply avoid re-parsing both values of CPPPATH by using the
170 common value from this cache.
172 def __init__(self) -> None:
173 self._memo = {}
175 def _PathList_key(self, pathlist):
177 Returns the key for memoization of PathLists.
179 Note that we want this to be pretty quick, so we don't completely
180 canonicalize all forms of the same list. For example,
181 'dir1:$ROOT/dir2' and ['$ROOT/dir1', 'dir'] may logically
182 represent the same list if you're executing from $ROOT, but
183 we're not going to bother splitting strings into path elements,
184 or massaging strings into Nodes, to identify that equivalence.
185 We just want to eliminate obvious redundancy from the normal
186 case of re-using exactly the same cloned value for a path.
188 if SCons.Util.is_Sequence(pathlist):
189 pathlist = tuple(SCons.Util.flatten(pathlist))
190 return pathlist
192 @SCons.Memoize.CountDictCall(_PathList_key)
193 def PathList(self, pathlist, split=True):
195 Returns the cached _PathList object for the specified pathlist,
196 creating and caching a new object as necessary.
198 pathlist = self._PathList_key(pathlist)
199 try:
200 memo_dict = self._memo['PathList']
201 except KeyError:
202 memo_dict = {}
203 self._memo['PathList'] = memo_dict
204 else:
205 try:
206 return memo_dict[pathlist]
207 except KeyError:
208 pass
210 result = _PathList(pathlist, split)
212 memo_dict[pathlist] = result
214 return result
216 PathList = PathListCache().PathList
219 del PathListCache
221 # Local Variables:
222 # tab-width:4
223 # indent-tabs-mode:nil
224 # End:
225 # vim: set expandtab tabstop=4 shiftwidth=4: