"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "SCons/Util.py" between
scons-4.2.0.tar.gz and SCons-4.3.0.tar.gz

About: SCons is a software construction tool (a Python script and a set of modules as a superior alternative to the classic "Make" build tool).

Util.py  (scons-4.2.0):Util.py  (SCons-4.3.0)
skipping to change at line 120 skipping to change at line 120
calculating command signatures. calculating command signatures.
""" """
drive, rest = os.path.splitdrive(path) drive, rest = os.path.splitdrive(path)
if drive: if drive:
path = drive.upper() + rest path = drive.upper() + rest
return path return path
class NodeList(UserList): class NodeList(UserList):
"""A list of Nodes with special attribute retrieval. """A list of Nodes with special attribute retrieval.
This class is almost exactly like a regular list of Nodes Unlike an ordinary list, access to a member's attribute returns a
(actually it can hold any object), with one important difference. `NodeList` containing the same attribute for each member. Although
If you try to get an attribute from this list, it will return that this can hold any object, it is intended for use when processing
attribute from every item in the list. For example: Nodes, where fetching an attribute of each member is very commone,
for example getting the content signature of each node. The term
"attribute" here includes the string representation.
Example:
>>> someList = NodeList([' foo ', ' bar ']) >>> someList = NodeList([' foo ', ' bar '])
>>> someList.strip() >>> someList.strip()
['foo', 'bar'] ['foo', 'bar']
""" """
def __bool__(self): def __bool__(self):
return bool(self.data) return bool(self.data)
def __str__(self): def __str__(self):
return ' '.join(map(str, self.data)) return ' '.join(map(str, self.data))
def __iter__(self): def __iter__(self):
return iter(self.data) return iter(self.data)
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs) -> 'NodeList':
result = [x(*args, **kwargs) for x in self.data] result = [x(*args, **kwargs) for x in self.data]
return self.__class__(result) return self.__class__(result)
def __getattr__(self, name): def __getattr__(self, name) -> 'NodeList':
"""Returns a NodeList of `name` from each member."""
result = [getattr(x, name) for x in self.data] result = [getattr(x, name) for x in self.data]
return self.__class__(result) return self.__class__(result)
def __getitem__(self, index): def __getitem__(self, index):
""" """Returns one item, forces a `NodeList` if `index` is a slice."""
This comes for free on py2, # TODO: annotate return how? Union[] - don't know type of single item
but py3 slices of NodeList are returning a list
breaking slicing nodelist and refering to
properties and methods on contained object
"""
# return self.__class__(self.data[index])
if isinstance(index, slice): if isinstance(index, slice):
# Expand the slice object using range() return self.__class__(self.data[index])
# limited by number of items in self.data
indices = index.indices(len(self.data))
return self.__class__([self[x] for x in range(*indices)])
# Return one item of the tart
return self.data[index] return self.data[index]
_get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$') _get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$')
def get_environment_var(varstr) -> Optional[str]: def get_environment_var(varstr) -> Optional[str]:
"""Return undecorated construction variable string. """Return undecorated construction variable string.
Determine if `varstr` looks like a reference Determine if `varstr` looks like a reference
to a single environment variable, like `"$FOO"` or `"${FOO}"`. to a single environment variable, like `"$FOO"` or `"${FOO}"`.
If so, return that variable with no decorations, like `"FOO"`. If so, return that variable with no decorations, like `"FOO"`.
skipping to change at line 1192 skipping to change at line 1186
22 ['-', '-', 's', 'o', 'm', 'e', ' ', '-', '-', 'o', 'p', 't', 's', ' ', 'a ', 'n', 'd', ' ', 'a', 'r', 'g', 's'] 22 ['-', '-', 's', 'o', 'm', 'e', ' ', '-', '-', 'o', 'p', 't', 's', ' ', 'a ', 'n', 'd', ' ', 'a', 'r', 'g', 's']
>>> c = CLVar("--some --opts and args") >>> c = CLVar("--some --opts and args")
>>> print(len(c), repr(c)) >>> print(len(c), repr(c))
4 ['--some', '--opts', 'and', 'args'] 4 ['--some', '--opts', 'and', 'args']
>>> c += " strips spaces " >>> c += " strips spaces "
>>> print(len(c), repr(c)) >>> print(len(c), repr(c))
6 ['--some', '--opts', 'and', 'args', 'strips', 'spaces'] 6 ['--some', '--opts', 'and', 'args', 'strips', 'spaces']
""" """
def __init__(self, initlist=None): def __init__(self, initlist=None):
super().__init__(Split(initlist)) super().__init__(Split(initlist if initlist is not None else []))
def __add__(self, other): def __add__(self, other):
return super().__add__(CLVar(other)) return super().__add__(CLVar(other))
def __radd__(self, other): def __radd__(self, other):
return super().__radd__(CLVar(other)) return super().__radd__(CLVar(other))
def __iadd__(self, other): def __iadd__(self, other):
return super().__iadd__(CLVar(other)) return super().__iadd__(CLVar(other))
skipping to change at line 1642 skipping to change at line 1636
obj.added_methods.append(method) obj.added_methods.append(method)
else: else:
method = MethodType(function, obj) method = MethodType(function, obj)
else: else:
# obj is a class # obj is a class
method = function method = function
setattr(obj, name, method) setattr(obj, name, method)
# Default hash function and format. SCons-internal. # Default hash function and format. SCons-internal.
ALLOWED_HASH_FORMATS = ['md5', 'sha1', 'sha256'] DEFAULT_HASH_FORMATS = ['md5', 'sha1', 'sha256']
ALLOWED_HASH_FORMATS = []
_HASH_FUNCTION = None _HASH_FUNCTION = None
_HASH_FORMAT = None _HASH_FORMAT = None
def _attempt_init_of_python_3_9_hash_object(hash_function_object, sys_used=sys):
"""Python 3.9 and onwards lets us initialize the hash function object with t
he
key "usedforsecurity"=false. This lets us continue to use algorithms that ha
ve
been deprecated either by FIPS or by Python itself, as the MD5 algorithm SCo
ns
prefers is not being used for security purposes as much as a short, 32 char
hash that is resistant to accidental collisions.
In prior versions of python, hashlib returns a native function wrapper, whic
h
errors out when it's queried for the optional parameter, so this function
wraps that call.
It can still throw a ValueError if the initialization fails due to FIPS
compliance issues, but that is assumed to be the responsibility of the calle
r.
"""
if hash_function_object is None:
return None
# https://stackoverflow.com/a/11887885 details how to check versions with th
e "packaging" library.
# however, for our purposes checking the version is greater than or equal to
3.9 is good enough, as
# the API is guaranteed to have support for the 'usedforsecurity' flag in 3.
9. See
# https://docs.python.org/3/library/hashlib.html#:~:text=usedforsecurity for
the version support notes.
if (sys_used.version_info.major > 3) or (sys_used.version_info.major == 3 an
d sys_used.version_info.minor >= 9):
return hash_function_object(usedforsecurity=False)
# note that this can throw a ValueError in FIPS-enabled versions of Linux pr
ior to 3.9
# the OpenSSL hashlib will throw on first init here, but that is assumed to
be responsibility of
# the caller to diagnose the ValueError & potentially display the error to s
creen.
return hash_function_object()
def _set_allowed_viable_default_hashes(hashlib_used, sys_used=sys):
"""Checks if SCons has ability to call the default algorithms normally suppo
rted.
This util class is sometimes called prior to setting the user-selected hash
algorithm,
meaning that on FIPS-compliant systems the library would default-initialize
MD5
and throw an exception in set_hash_format. A common case is using the SConf
options,
which can run prior to main, and thus ignore the options.hash_format variabl
e.
This function checks the DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORM
ATS
to only the ones that can be called. In Python >= 3.9 this will always defau
lt to
MD5 as in Python 3.9 there is an optional attribute "usedforsecurity" set fo
r the method.
Throws if no allowed hash formats are detected.
"""
global ALLOWED_HASH_FORMATS
_last_error = None
# note: if you call this method repeatedly, example using timeout, this is n
eeded.
# otherwise it keeps appending valid formats to the string
ALLOWED_HASH_FORMATS = []
for test_algorithm in DEFAULT_HASH_FORMATS:
_test_hash = getattr(hashlib_used, test_algorithm, None)
# we know hashlib claims to support it... check to see if we can call it
.
if _test_hash is not None:
# the hashing library will throw an exception on initialization in F
IPS mode,
# meaning if we call the default algorithm returned with no paramete
rs, it'll
# throw if it's a bad algorithm, otherwise it will append it to the
known
# good formats.
try:
_attempt_init_of_python_3_9_hash_object(_test_hash, sys_used)
ALLOWED_HASH_FORMATS.append(test_algorithm)
except ValueError as e:
_last_error = e
continue
if len(ALLOWED_HASH_FORMATS) == 0:
from SCons.Errors import SConsEnvironmentError # pylint: disable=import
-outside-toplevel
# chain the exception thrown with the most recent error from hashlib.
raise SConsEnvironmentError(
'No usable hash algorithms found.'
'Most recent error from hashlib attached in trace.'
) from _last_error
return
_set_allowed_viable_default_hashes(hashlib)
def get_hash_format(): def get_hash_format():
"""Retrieves the hash format or ``None`` if not overridden. """Retrieves the hash format or ``None`` if not overridden.
A return value of ``None`` A return value of ``None``
does not guarantee that MD5 is being used; instead, it means that the does not guarantee that MD5 is being used; instead, it means that the
default precedence order documented in :func:`SCons.Util.set_hash_format` default precedence order documented in :func:`SCons.Util.set_hash_format`
is respected. is respected.
""" """
return _HASH_FORMAT return _HASH_FORMAT
def set_hash_format(hash_format): def _attempt_get_hash_function(hash_name, hashlib_used=hashlib, sys_used=sys):
"""Wrapper used to try to initialize a hash function given.
If successful, returns the name of the hash function back to the user.
Otherwise returns None.
"""
try:
_fetch_hash = getattr(hashlib_used, hash_name, None)
if _fetch_hash is None:
return None
_attempt_init_of_python_3_9_hash_object(_fetch_hash, sys_used)
return hash_name
except ValueError:
# if attempt_init_of_python_3_9 throws, this is typically due to FIPS be
ing enabled
# however, if we get to this point, the viable hash function check has e
ither been
# bypassed or otherwise failed to properly restrict the user to only the
supported
# functions. As such throw the UserError as an internal assertion-like e
rror.
return None
def set_hash_format(hash_format, hashlib_used=hashlib, sys_used=sys):
"""Sets the default hash format used by SCons. """Sets the default hash format used by SCons.
If `hash_format` is ``None`` or If `hash_format` is ``None`` or
an empty string, the default is determined by this function. an empty string, the default is determined by this function.
Currently the default behavior is to use the first available format of Currently the default behavior is to use the first available format of
the following options: MD5, SHA1, SHA256. the following options: MD5, SHA1, SHA256.
""" """
global _HASH_FORMAT, _HASH_FUNCTION global _HASH_FORMAT, _HASH_FUNCTION
_HASH_FORMAT = hash_format _HASH_FORMAT = hash_format
if hash_format: if hash_format:
hash_format_lower = hash_format.lower() hash_format_lower = hash_format.lower()
if hash_format_lower not in ALLOWED_HASH_FORMATS: if hash_format_lower not in ALLOWED_HASH_FORMATS:
from SCons.Errors import UserError # pylint: disable=import-outside -toplevel from SCons.Errors import UserError # pylint: disable=import-outside -toplevel
raise UserError('Hash format "%s" is not supported by SCons. Only ' # user can select something not supported by their OS but normally s
upported by
# SCons, example, selecting MD5 in an OS with FIPS-mode turned on. T
herefore we first
# check if SCons supports it, and then if their local OS supports it
.
if hash_format_lower in DEFAULT_HASH_FORMATS:
raise UserError('While hash format "%s" is supported by SCons, t
he '
'local system indicates only the following hash '
'formats are supported by the hashlib library: %s' %
(hash_format_lower,
', '.join(ALLOWED_HASH_FORMATS))
)
else:
# the hash format isn't supported by SCons in any case. Warn the
user, and
# if we detect that SCons supports more algorithms than their lo
cal system
# supports, warn the user about that too.
if ALLOWED_HASH_FORMATS == DEFAULT_HASH_FORMATS:
raise UserError('Hash format "%s" is not supported by SCons.
Only '
'the following hash formats are supported: %s' % 'the following hash formats are supported: %s' %
(hash_format_lower, (hash_format_lower,
', '.join(ALLOWED_HASH_FORMATS))) ', '.join(ALLOWED_HASH_FORMATS))
)
else:
raise UserError('Hash format "%s" is not supported by SCons.
'
'SCons supports more hash formats than your local sy
stem '
'is reporting; SCons supports: %s. Your local system
only '
'supports: %s' %
(hash_format_lower,
', '.join(DEFAULT_HASH_FORMATS),
', '.join(ALLOWED_HASH_FORMATS))
)
# this is not expected to fail. If this fails it means the set_allowed_v
iable_default_hashes
# function did not throw, or when it threw, the exception was caught and
ignored, or
# the global ALLOWED_HASH_FORMATS was changed by an external user.
_HASH_FUNCTION = _attempt_get_hash_function(hash_format_lower, hashlib_u
sed, sys_used)
_HASH_FUNCTION = getattr(hashlib, hash_format_lower, None)
if _HASH_FUNCTION is None: if _HASH_FUNCTION is None:
from SCons.Errors import UserError # pylint: disable=import-outside -toplevel from SCons.Errors import UserError # pylint: disable=import-outside -toplevel
raise UserError( raise UserError(
'Hash format "%s" is not available in your Python interpreter.' 'Hash format "%s" is not available in your Python interpreter. '
'Expected to be supported algorithm by set_allowed_viable_defaul
t_hashes, '
'Assertion error in SCons.'
% hash_format_lower % hash_format_lower
) )
else: else:
# Set the default hash format based on what is available, defaulting # Set the default hash format based on what is available, defaulting
# to md5 for backwards compatibility. # to the first supported hash algorithm (usually md5) for backwards comp
atibility.
# in FIPS-compliant systems this usually defaults to SHA1, unless that t
oo has been
# disabled.
for choice in ALLOWED_HASH_FORMATS: for choice in ALLOWED_HASH_FORMATS:
_HASH_FUNCTION = getattr(hashlib, choice, None) _HASH_FUNCTION = _attempt_get_hash_function(choice, hashlib_used, sy
s_used)
if _HASH_FUNCTION is not None: if _HASH_FUNCTION is not None:
break break
else: else:
# This is not expected to happen in practice. # This is not expected to happen in practice.
from SCons.Errors import UserError # pylint: disable=import-outside -toplevel from SCons.Errors import UserError # pylint: disable=import-outside -toplevel
raise UserError( raise UserError(
'Your Python interpreter does not have MD5, SHA1, or SHA256. ' 'Your Python interpreter does not have MD5, SHA1, or SHA256. '
'SCons requires at least one.') 'SCons requires at least one. Expected to support one or more '
'during set_allowed_viable_default_hashes.'
)
# Ensure that this is initialized in case either: # Ensure that this is initialized in case either:
# 1. This code is running in a unit test. # 1. This code is running in a unit test.
# 2. This code is running in a consumer that does hash operations while # 2. This code is running in a consumer that does hash operations while
# SConscript files are being loaded. # SConscript files are being loaded.
set_hash_format(None) set_hash_format(None)
def _get_hash_object(hash_format): def get_current_hash_algorithm_used():
"""Returns the current hash algorithm name used.
Where the python version >= 3.9, this is expected to return md5.
If python's version is <= 3.8, this returns md5 on non-FIPS-mode platforms,
and
sha1 or sha256 on FIPS-mode Linux platforms.
This function is primarily useful for testing, where one expects a value to
be
one of N distinct hashes, and therefore the test needs to know which hash to
select.
"""
return _HASH_FUNCTION
def _get_hash_object(hash_format, hashlib_used=hashlib, sys_used=sys):
"""Allocates a hash object using the requested hash format. """Allocates a hash object using the requested hash format.
Args: Args:
hash_format: Hash format to use. hash_format: Hash format to use.
Returns: Returns:
hashlib object. hashlib object.
""" """
if hash_format is None: if hash_format is None:
if _HASH_FUNCTION is None: if _HASH_FUNCTION is None:
from SCons.Errors import UserError # pylint: disable=import-outside -toplevel from SCons.Errors import UserError # pylint: disable=import-outside -toplevel
raise UserError('There is no default hash function. Did you call ' raise UserError('There is no default hash function. Did you call '
'a hashing function before SCons was initialized?') 'a hashing function before SCons was initialized?')
return _HASH_FUNCTION() return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HA SH_FUNCTION, None), sys_used)
if not hasattr(hashlib, hash_format): if not hasattr(hashlib, hash_format):
from SCons.Errors import UserError # pylint: disable=import-outside-top level from SCons.Errors import UserError # pylint: disable=import-outside-top level
raise UserError( raise UserError(
'Hash format "%s" is not available in your Python interpreter.' % 'Hash format "%s" is not available in your Python interpreter.' %
hash_format) hash_format)
return getattr(hashlib, hash_format)() return _attempt_init_of_python_3_9_hash_object(getattr(hashlib, hash_format) , sys_used)
def hash_signature(s, hash_format=None): def hash_signature(s, hash_format=None):
""" """
Generate hash signature of a string Generate hash signature of a string
Args: Args:
s: either string or bytes. Normally should be bytes s: either string or bytes. Normally should be bytes
hash_format: Specify to override default hash format hash_format: Specify to override default hash format
Returns: Returns:
 End of changes. 19 change blocks. 
33 lines changed or deleted 222 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)