Disable tool initialization in tests which don't need it
[scons.git] / SCons / PathList.py
blobfcda3c59af7a7c79b161f2a8cab7e4b40a2fd563
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 :class:`~SCons.Node.FS.EntryProxy` object has a ``get()`` method that
51 returns the underlying Node that it wraps, which is a bit of
52 architectural dependence that we might need to break or modify in the
53 future in response to 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 Initializes a :class:`PathList` object, canonicalizing the input and
70 pre-processing it for quicker substitution later.
72 The stored representation of the :class:`PathList` is a list of tuples
73 containing (type, value), where the "type" is one of the ``TYPE_*``
74 variables defined above. We distinguish between:
76 * Strings that contain no ``$`` and therefore need no
77 delayed-evaluation string substitution (we expect that there
78 will be many of these and that we therefore get a pretty
79 big win from avoiding string substitution)
81 * Strings that contain ``$`` and therefore need substitution
82 (the hard case is things like ``${TARGET.dir}/include``,
83 which require re-evaluation for every target + source)
85 * Other objects (which may be something like an
86 :class:`~SCons.Node.FS.EntryProxy`
87 that needs a method called to return a Node)
89 Pre-identifying the type of each element in the :class:`PathList`
90 up-front and storing the type in the list of tuples is intended to
91 reduce the amount of calculation when we actually do the substitution
92 over and over for each target.
93 """
95 def __init__(self, pathlist, split=True) -> None:
96 if SCons.Util.is_String(pathlist):
97 if split:
98 pathlist = pathlist.split(os.pathsep)
99 else: # no splitting, but still need a list
100 pathlist = [pathlist]
101 elif not SCons.Util.is_Sequence(pathlist):
102 pathlist = [pathlist]
104 pl = []
105 for p in pathlist:
106 try:
107 found = '$' in p
108 except (AttributeError, TypeError):
109 type = TYPE_OBJECT
110 else:
111 if not found:
112 type = TYPE_STRING_NO_SUBST
113 else:
114 type = TYPE_STRING_SUBST
115 pl.append((type, p))
117 self.pathlist = tuple(pl)
119 def __len__(self) -> int: return len(self.pathlist)
121 def __getitem__(self, i): return self.pathlist[i]
123 def subst_path(self, env, target, source):
125 Performs construction variable substitution on a pre-digested
126 PathList for a specific target and source.
128 result = []
129 for type, value in self.pathlist:
130 if type == TYPE_STRING_SUBST:
131 value = env.subst(value, target=target, source=source,
132 conv=node_conv)
133 if SCons.Util.is_Sequence(value):
134 result.extend(SCons.Util.flatten(value))
135 elif value:
136 result.append(value)
137 elif type == TYPE_OBJECT:
138 value = node_conv(value)
139 if value:
140 result.append(value)
141 elif value:
142 result.append(value)
143 return tuple(result)
146 class PathListCache:
147 """A class to handle caching of PathList lookups.
149 This class gets instantiated once and then deleted from the namespace,
150 so it's used as a Singleton (although we don't enforce that in the
151 usual Pythonic ways). We could have just made the cache a dictionary
152 in the module namespace, but putting it in this class allows us to
153 use the same Memoizer pattern that we use elsewhere to count cache
154 hits and misses, which is very valuable.
156 Lookup keys in the cache are computed by the :meth:`_PathList_key` method.
157 Cache lookup should be quick, so we don't spend cycles canonicalizing
158 all forms of the same lookup key. For example, ``x:y`` and ``['x', 'y']``
159 logically represent the same list, but we don't bother to
160 split string representations and treat those two equivalently.
161 (Note, however, that we do, treat lists and tuples the same.)
163 The main type of duplication we're trying to catch will come from
164 looking up the same path list from two different clones of the
165 same construction environment. That is, given::
167 env2 = env1.Clone()
169 both ``env1`` and ``env2`` will have the same ``CPPPATH`` value, and we can
170 cheaply avoid re-parsing both values of ``CPPPATH`` by using the
171 common value from this cache.
173 def __init__(self) -> None:
174 self._memo = {}
176 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):
194 """Entry point for getting PathLists.
196 Returns the cached :class:`_PathList` object for the specified
197 pathlist, creating and caching a new object as necessary.
199 pathlist = self._PathList_key(pathlist)
200 try:
201 memo_dict = self._memo['PathList']
202 except KeyError:
203 memo_dict = {}
204 self._memo['PathList'] = memo_dict
205 else:
206 try:
207 return memo_dict[pathlist]
208 except KeyError:
209 pass
211 result = _PathList(pathlist, split)
213 memo_dict[pathlist] = result
215 return result
217 PathList = PathListCache().PathList
219 # TODO: removing the class object here means Sphinx doesn't pick up its
220 # docstrings: they're fine for reading here, but are not in API Docs.
221 del PathListCache
223 # Local Variables:
224 # tab-width:4
225 # indent-tabs-mode:nil
226 # End:
227 # vim: set expandtab tabstop=4 shiftwidth=4: