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