227 lines
8.4 KiB
Python
Vendored
227 lines
8.4 KiB
Python
Vendored
# MIT License
|
|
#
|
|
# Copyright The SCons Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining
|
|
# a copy of this software and associated documentation files (the
|
|
# "Software"), to deal in the Software without restriction, including
|
|
# without limitation the rights to use, copy, modify, merge, publish,
|
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
# permit persons to whom the Software is furnished to do so, subject to
|
|
# the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included
|
|
# in all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
"""Handle lists of directory paths.
|
|
|
|
These are the path lists that get set as ``CPPPATH``, ``LIBPATH``,
|
|
etc.) with as much caching of data and efficiency as we can, while
|
|
still keeping the evaluation delayed so that we Do the Right Thing
|
|
(almost) regardless of how the variable is specified.
|
|
"""
|
|
|
|
import os
|
|
|
|
import SCons.Memoize
|
|
import SCons.Node
|
|
import SCons.Util
|
|
|
|
#
|
|
# Variables to specify the different types of entries in a PathList object:
|
|
#
|
|
|
|
TYPE_STRING_NO_SUBST = 0 # string with no '$'
|
|
TYPE_STRING_SUBST = 1 # string containing '$'
|
|
TYPE_OBJECT = 2 # other object
|
|
|
|
def node_conv(obj):
|
|
"""
|
|
This is the "string conversion" routine that we have our substitutions
|
|
use to return Nodes, not strings. This relies on the fact that an
|
|
:class:`~SCons.Node.FS.EntryProxy` object has a ``get()`` method that
|
|
returns the underlying Node that it wraps, which is a bit of
|
|
architectural dependence that we might need to break or modify in the
|
|
future in response to additional requirements.
|
|
"""
|
|
try:
|
|
get = obj.get
|
|
except AttributeError:
|
|
if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ):
|
|
result = obj
|
|
else:
|
|
result = str(obj)
|
|
else:
|
|
result = get()
|
|
return result
|
|
|
|
class _PathList:
|
|
"""An actual PathList object.
|
|
|
|
Initializes a :class:`PathList` object, canonicalizing the input and
|
|
pre-processing it for quicker substitution later.
|
|
|
|
The stored representation of the :class:`PathList` is a list of tuples
|
|
containing (type, value), where the "type" is one of the ``TYPE_*``
|
|
variables defined above. We distinguish between:
|
|
|
|
* Strings that contain no ``$`` and therefore need no
|
|
delayed-evaluation string substitution (we expect that there
|
|
will be many of these and that we therefore get a pretty
|
|
big win from avoiding string substitution)
|
|
|
|
* Strings that contain ``$`` and therefore need substitution
|
|
(the hard case is things like ``${TARGET.dir}/include``,
|
|
which require re-evaluation for every target + source)
|
|
|
|
* Other objects (which may be something like an
|
|
:class:`~SCons.Node.FS.EntryProxy`
|
|
that needs a method called to return a Node)
|
|
|
|
Pre-identifying the type of each element in the :class:`PathList`
|
|
up-front and storing the type in the list of tuples is intended to
|
|
reduce the amount of calculation when we actually do the substitution
|
|
over and over for each target.
|
|
"""
|
|
|
|
def __init__(self, pathlist, split=True) -> None:
|
|
if SCons.Util.is_String(pathlist):
|
|
if split:
|
|
pathlist = pathlist.split(os.pathsep)
|
|
else: # no splitting, but still need a list
|
|
pathlist = [pathlist]
|
|
elif not SCons.Util.is_Sequence(pathlist):
|
|
pathlist = [pathlist]
|
|
|
|
pl = []
|
|
for p in pathlist:
|
|
try:
|
|
found = '$' in p
|
|
except (AttributeError, TypeError):
|
|
type = TYPE_OBJECT
|
|
else:
|
|
if not found:
|
|
type = TYPE_STRING_NO_SUBST
|
|
else:
|
|
type = TYPE_STRING_SUBST
|
|
pl.append((type, p))
|
|
|
|
self.pathlist = tuple(pl)
|
|
|
|
def __len__(self) -> int: return len(self.pathlist)
|
|
|
|
def __getitem__(self, i): return self.pathlist[i]
|
|
|
|
def subst_path(self, env, target, source):
|
|
"""
|
|
Performs construction variable substitution on a pre-digested
|
|
PathList for a specific target and source.
|
|
"""
|
|
result = []
|
|
for type, value in self.pathlist:
|
|
if type == TYPE_STRING_SUBST:
|
|
value = env.subst(value, target=target, source=source,
|
|
conv=node_conv)
|
|
if SCons.Util.is_Sequence(value):
|
|
result.extend(SCons.Util.flatten(value))
|
|
elif value:
|
|
result.append(value)
|
|
elif type == TYPE_OBJECT:
|
|
value = node_conv(value)
|
|
if value:
|
|
result.append(value)
|
|
elif value:
|
|
result.append(value)
|
|
return tuple(result)
|
|
|
|
|
|
class PathListCache:
|
|
"""A class to handle caching of PathList lookups.
|
|
|
|
This class gets instantiated once and then deleted from the namespace,
|
|
so it's used as a Singleton (although we don't enforce that in the
|
|
usual Pythonic ways). We could have just made the cache a dictionary
|
|
in the module namespace, but putting it in this class allows us to
|
|
use the same Memoizer pattern that we use elsewhere to count cache
|
|
hits and misses, which is very valuable.
|
|
|
|
Lookup keys in the cache are computed by the :meth:`_PathList_key` method.
|
|
Cache lookup should be quick, so we don't spend cycles canonicalizing
|
|
all forms of the same lookup key. For example, ``x:y`` and ``['x', 'y']``
|
|
logically represent the same list, but we don't bother to
|
|
split string representations and treat those two equivalently.
|
|
(Note, however, that we do, treat lists and tuples the same.)
|
|
|
|
The main type of duplication we're trying to catch will come from
|
|
looking up the same path list from two different clones of the
|
|
same construction environment. That is, given::
|
|
|
|
env2 = env1.Clone()
|
|
|
|
both ``env1`` and ``env2`` will have the same ``CPPPATH`` value, and we can
|
|
cheaply avoid re-parsing both values of ``CPPPATH`` by using the
|
|
common value from this cache.
|
|
"""
|
|
def __init__(self) -> None:
|
|
self._memo = {}
|
|
|
|
def _PathList_key(self, pathlist):
|
|
"""Returns the key for memoization of PathLists.
|
|
|
|
Note that we want this to be pretty quick, so we don't completely
|
|
canonicalize all forms of the same list. For example,
|
|
``dir1:$ROOT/dir2`` and ``['$ROOT/dir1', 'dir']`` may logically
|
|
represent the same list if you're executing from ``$ROOT``, but
|
|
we're not going to bother splitting strings into path elements,
|
|
or massaging strings into Nodes, to identify that equivalence.
|
|
We just want to eliminate obvious redundancy from the normal
|
|
case of re-using exactly the same cloned value for a path.
|
|
"""
|
|
if SCons.Util.is_Sequence(pathlist):
|
|
pathlist = tuple(SCons.Util.flatten(pathlist))
|
|
return pathlist
|
|
|
|
@SCons.Memoize.CountDictCall(_PathList_key)
|
|
def PathList(self, pathlist, split=True):
|
|
"""Entry point for getting PathLists.
|
|
|
|
Returns the cached :class:`_PathList` object for the specified
|
|
pathlist, creating and caching a new object as necessary.
|
|
"""
|
|
pathlist = self._PathList_key(pathlist)
|
|
try:
|
|
memo_dict = self._memo['PathList']
|
|
except KeyError:
|
|
memo_dict = {}
|
|
self._memo['PathList'] = memo_dict
|
|
else:
|
|
try:
|
|
return memo_dict[pathlist]
|
|
except KeyError:
|
|
pass
|
|
|
|
result = _PathList(pathlist, split)
|
|
|
|
memo_dict[pathlist] = result
|
|
|
|
return result
|
|
|
|
PathList = PathListCache().PathList
|
|
|
|
# TODO: removing the class object here means Sphinx doesn't pick up its
|
|
# docstrings: they're fine for reading here, but are not in API Docs.
|
|
del PathListCache
|
|
|
|
# Local Variables:
|
|
# tab-width:4
|
|
# indent-tabs-mode:nil
|
|
# End:
|
|
# vim: set expandtab tabstop=4 shiftwidth=4:
|