summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilliam Deegan <bill@baddogconsulting.com>2021-11-16 18:46:46 (GMT)
committerGitHub <noreply@github.com>2021-11-16 18:46:46 (GMT)
commit02f4075bf491267bbc872587d9845086560fc6b1 (patch)
treea08b4a7f3e6397daf20615f13e5c84d2ebd0726e
parentcd98aff49191140710cec03696efbb62281bf416 (diff)
parent3b9bcab73ec204e5228b7ad8990e5db06ffad155 (diff)
downloadSCons-02f4075bf491267bbc872587d9845086560fc6b1.zip
SCons-02f4075bf491267bbc872587d9845086560fc6b1.tar.gz
SCons-02f4075bf491267bbc872587d9845086560fc6b1.tar.bz2
Merge pull request #4047 from jcassagnol-public/compile-with-fips-enabled
Fix tests to work with FIPS enabled
-rw-r--r--.gitignore2
-rwxr-xr-xCHANGES.txt8
-rw-r--r--SCons/Environment.py2
-rw-r--r--SCons/EnvironmentTests.py3
-rw-r--r--SCons/Node/NodeTests.py4
-rw-r--r--SCons/SConsign.py21
-rw-r--r--SCons/SConsignTests.py6
-rw-r--r--SCons/Scanner/Dir.py19
-rw-r--r--SCons/Scanner/DirTests.py39
-rw-r--r--SCons/Util.py169
-rw-r--r--SCons/UtilTests.py189
-rw-r--r--doc/man/scons.xml12
-rw-r--r--test/Configure/ConfigureDryRunError.py13
-rw-r--r--test/Configure/VariantDir-SConscript.py2
-rw-r--r--test/Configure/implicit-cache.py28
-rw-r--r--test/Configure/option--config.py6
-rw-r--r--test/Removed/SourceSignatures/Old/no-csigs.py2
-rw-r--r--test/Repository/variants.py66
-rw-r--r--test/SConsignFile/default.py15
-rw-r--r--test/SConsignFile/explicit-dbm-module.py15
-rw-r--r--test/SConsignFile/explicit-file.py9
-rw-r--r--test/SConsignFile/make-directory.py3
-rw-r--r--test/SConsignFile/use-dbhash.py22
-rw-r--r--test/SConsignFile/use-dbm.py25
-rw-r--r--test/SConsignFile/use-dumbdbm.py35
-rw-r--r--test/SConsignFile/use-gdbm.py20
-rw-r--r--test/implicit/changed-node.py3
-rw-r--r--test/option/hash-format.py41
-rw-r--r--test/option/option-n.py15
-rw-r--r--test/question/Configure.py14
-rw-r--r--test/sconsign/corrupt.py18
-rw-r--r--test/sconsign/nonwritable.py9
-rw-r--r--test/sconsign/script/Configure.py6
-rw-r--r--test/sconsign/script/SConsignFile.py22
-rw-r--r--test/sconsign/script/Signatures.py8
-rw-r--r--test/sconsign/script/dblite.py2
-rw-r--r--test/sconsign/script/no-SConsignFile.py26
-rw-r--r--testing/framework/TestSCons.py28
38 files changed, 713 insertions, 214 deletions
diff --git a/.gitignore b/.gitignore
index a7b59b3..7dc253f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,7 +34,7 @@ venv.bak/
# SCons files
-.sconsign.*
+.sconsign*
# Tool output
.coverage
diff --git a/CHANGES.txt b/CHANGES.txt
index 0dd0cf3..2f5c5c2 100755
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -9,6 +9,14 @@ NOTE: The 4.2.0 Release of SCons will deprecate Python 3.5 Support. Python 3.5 s
RELEASE VERSION/DATE TO BE FILLED IN LATER
+ From Jacob Cassagnol:
+ - Default hash algorithm check updated for SCons FIPS compliance. Now checks for hash viability
+ first and then walks the tree to use the first viable hash as the default one. This typically
+ selects SHA1 on FIPS-enabled systems less than Python 3.9 as the new default instead of MD5,
+ unless SHA1 has also been disabled by security policy, at which point SCons selects SHA256
+ as the default. For systems running Python 3.9 and later, the hashlib bug has been fixed,
+ and SCons will once again default to MD5 as the preferred algorithm.
+
From Joseph Brill:
- Fix MSVS tests (vs-N.N-exec.py) for MSVS 6.0, 7.0, and 7.1 (import missing module).
- Add support for Visual Studio 2022.
diff --git a/SCons/Environment.py b/SCons/Environment.py
index 957a6e4..c8cddf7 100644
--- a/SCons/Environment.py
+++ b/SCons/Environment.py
@@ -2252,7 +2252,7 @@ class Base(SubstitutionEnvironment):
nkw = self.subst_kw(kw)
return SCons.Scanner.ScannerBase(*nargs, **nkw)
- def SConsignFile(self, name=".sconsign", dbm_module=None):
+ def SConsignFile(self, name=SCons.SConsign.current_sconsign_filename(), dbm_module=None):
if name is not None:
name = self.subst(name)
if not os.path.isabs(name):
diff --git a/SCons/EnvironmentTests.py b/SCons/EnvironmentTests.py
index 2ed897a..cd36fd2 100644
--- a/SCons/EnvironmentTests.py
+++ b/SCons/EnvironmentTests.py
@@ -41,6 +41,7 @@ from SCons.Environment import (
is_valid_construction_var,
)
from SCons.Util import CLVar
+from SCons.SConsign import current_sconsign_filename
def diff_env(env1, env2):
@@ -3321,7 +3322,7 @@ def generate(env):
assert dbms[-1] == 7, dbms
env.SConsignFile()
- assert fnames[-1] == os.path.join(os.sep, 'dir', '.sconsign'), fnames
+ assert fnames[-1] == os.path.join(os.sep, 'dir', current_sconsign_filename()), fnames
assert dbms[-1] is None, dbms
env.SConsignFile(None)
diff --git a/SCons/Node/NodeTests.py b/SCons/Node/NodeTests.py
index 29a3887..b36c2e9 100644
--- a/SCons/Node/NodeTests.py
+++ b/SCons/Node/NodeTests.py
@@ -592,7 +592,7 @@ class NodeTestCase(unittest.TestCase):
node = SCons.Node.Node()
node._func_get_contents = 4
result = node.get_csig()
- assert result == '550a141f12de6341fba65b0ad0433500', result
+ assert result in ('550a141f12de6341fba65b0ad0433500', '9a3e61b6bcc8abec08f195526c3132d5a4a98cc0', '3538a1ef2e113da64249eea7bd068b585ec7ce5df73b2d1e319d8c9bf47eb314'), result
finally:
SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase
@@ -609,7 +609,7 @@ class NodeTestCase(unittest.TestCase):
node = SCons.Node.Node()
node._func_get_contents = 4
result = node.get_cachedir_csig()
- assert result == '15de21c670ae7c3f6f3f1f37029303c9', result
+ assert result in ('15de21c670ae7c3f6f3f1f37029303c9', 'cfa1150f1787186742a9a884b73a43d8cf219f9b', '91a73fd806ab2c005c13b4dc19130a884e909dea3f72d46e30266fe1a1f588d8'), result
finally:
SCons.Node.Node.NodeInfo = SCons.Node.NodeInfoBase
diff --git a/SCons/SConsign.py b/SCons/SConsign.py
index ade8a10..5b78855 100644
--- a/SCons/SConsign.py
+++ b/SCons/SConsign.py
@@ -58,16 +58,25 @@ DB_Module = SCons.dblite
DB_Name = None
DB_sync_list = []
+def current_sconsign_filename():
+ hash_format = SCons.Util.get_hash_format()
+ current_hash_algorithm = SCons.Util.get_current_hash_algorithm_used()
+ # if the user left the options defaulted AND the default algorithm set by
+ # SCons is md5, then set the database name to be the special default name
+ #
+ # otherwise, if it defaults to something like 'sha1' or the user explicitly
+ # set 'md5' as the hash format, set the database name to .sconsign_<algorithm>
+ # eg .sconsign_sha1, etc.
+ if hash_format is None and current_hash_algorithm == 'md5':
+ return ".sconsign"
+ else:
+ return ".sconsign_" + current_hash_algorithm
def Get_DataBase(dir):
global DataBase, DB_Module, DB_Name
if DB_Name is None:
- hash_format = SCons.Util.get_hash_format()
- if hash_format is None:
- DB_Name = ".sconsign"
- else:
- DB_Name = ".sconsign_%s" % hash_format
+ DB_Name = current_sconsign_filename()
top = dir.fs.Top
if not os.path.isabs(DB_Name) and top.repositories:
@@ -335,7 +344,7 @@ class DirFile(Dir):
"""
self.dir = dir
- self.sconsign = os.path.join(dir.get_internal_path(), '.sconsign')
+ self.sconsign = os.path.join(dir.get_internal_path(), current_sconsign_filename())
try:
fp = open(self.sconsign, 'rb')
diff --git a/SCons/SConsignTests.py b/SCons/SConsignTests.py
index d4f4418..e9f2071 100644
--- a/SCons/SConsignTests.py
+++ b/SCons/SConsignTests.py
@@ -28,6 +28,7 @@ import TestCmd
import SCons.dblite
import SCons.SConsign
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
class BuildInfo:
def merge(self, object):
@@ -295,7 +296,10 @@ class SConsignFileTestCase(SConsignTestCase):
file = test.workpath('sconsign_file')
assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase
- assert SCons.SConsign.DB_Name == ".sconsign", SCons.SConsign.DB_Name
+ if get_hash_format() is None and get_current_hash_algorithm_used() == 'md5':
+ assert SCons.SConsign.DB_Name == ".sconsign", SCons.SConsign.DB_Name
+ else:
+ assert SCons.SConsign.DB_Name == ".sconsign_{}".format(get_current_hash_algorithm_used()), SCons.SConsign.DB_Name
assert SCons.SConsign.DB_Module is SCons.dblite, SCons.SConsign.DB_Module
SCons.SConsign.File(file)
diff --git a/SCons/Scanner/Dir.py b/SCons/Scanner/Dir.py
index 162e9ef..239b1ec 100644
--- a/SCons/Scanner/Dir.py
+++ b/SCons/Scanner/Dir.py
@@ -63,6 +63,25 @@ skip_entry_list = [
'.sconsign_md5.dblite',
'.sconsign_sha1.dblite',
'.sconsign_sha256.dblite',
+ # and all the duplicate files for each sub-sconsfile type
+ '.sconsign_md5',
+ '.sconsign_md5.dir',
+ '.sconsign_md5.pag',
+ '.sconsign_md5.dat',
+ '.sconsign_md5.bak',
+ '.sconsign_md5.db',
+ '.sconsign_sha1',
+ '.sconsign_sha1.dir',
+ '.sconsign_sha1.pag',
+ '.sconsign_sha1.dat',
+ '.sconsign_sha1.bak',
+ '.sconsign_sha1.db',
+ '.sconsign_sha256',
+ '.sconsign_sha256.dir',
+ '.sconsign_sha256.pag',
+ '.sconsign_sha256.dat',
+ '.sconsign_sha256.bak',
+ '.sconsign_sha256.db',
]
for skip in skip_entry_list:
diff --git a/SCons/Scanner/DirTests.py b/SCons/Scanner/DirTests.py
index 2e73fe2..1c46c6c 100644
--- a/SCons/Scanner/DirTests.py
+++ b/SCons/Scanner/DirTests.py
@@ -28,6 +28,7 @@ import TestCmd
import SCons.Node.FS
import SCons.Scanner.Dir
+from SCons.SConsign import current_sconsign_filename
#class DummyNode:
# def __init__(self, name, fs):
@@ -57,23 +58,25 @@ class DirScannerTestBase(unittest.TestCase):
self.test.subdir('dir', ['dir', 'sub'])
+ sconsign = current_sconsign_filename()
+
self.test.write(['dir', 'f1'], "dir/f1\n")
self.test.write(['dir', 'f2'], "dir/f2\n")
- self.test.write(['dir', '.sconsign'], "dir/.sconsign\n")
- self.test.write(['dir', '.sconsign.bak'], "dir/.sconsign.bak\n")
- self.test.write(['dir', '.sconsign.dat'], "dir/.sconsign.dat\n")
- self.test.write(['dir', '.sconsign.db'], "dir/.sconsign.db\n")
- self.test.write(['dir', '.sconsign.dblite'], "dir/.sconsign.dblite\n")
- self.test.write(['dir', '.sconsign.dir'], "dir/.sconsign.dir\n")
- self.test.write(['dir', '.sconsign.pag'], "dir/.sconsign.pag\n")
+ self.test.write(['dir', '{}'.format(sconsign)], "dir/{}\n".format(sconsign))
+ self.test.write(['dir', '{}.bak'.format(sconsign)], "dir/{}.bak\n".format(sconsign))
+ self.test.write(['dir', '{}.dat'.format(sconsign)], "dir/{}.dat\n".format(sconsign))
+ self.test.write(['dir', '{}.db'.format(sconsign)], "dir/{}.db\n".format(sconsign))
+ self.test.write(['dir', '{}.dblite'.format(sconsign)], "dir/{}.dblite\n".format(sconsign))
+ self.test.write(['dir', '{}.dir'.format(sconsign)], "dir/{}.dir\n".format(sconsign))
+ self.test.write(['dir', '{}.pag'.format(sconsign)], "dir/{}.pag\n".format(sconsign))
self.test.write(['dir', 'sub', 'f3'], "dir/sub/f3\n")
self.test.write(['dir', 'sub', 'f4'], "dir/sub/f4\n")
- self.test.write(['dir', 'sub', '.sconsign'], "dir/.sconsign\n")
- self.test.write(['dir', 'sub', '.sconsign.bak'], "dir/.sconsign.bak\n")
- self.test.write(['dir', 'sub', '.sconsign.dat'], "dir/.sconsign.dat\n")
- self.test.write(['dir', 'sub', '.sconsign.dblite'], "dir/.sconsign.dblite\n")
- self.test.write(['dir', 'sub', '.sconsign.dir'], "dir/.sconsign.dir\n")
- self.test.write(['dir', 'sub', '.sconsign.pag'], "dir/.sconsign.pag\n")
+ self.test.write(['dir', 'sub', '{}'.format(sconsign)], "dir/{}\n".format(sconsign))
+ self.test.write(['dir', 'sub', '{}.bak'.format(sconsign)], "dir/{}.bak\n".format(sconsign))
+ self.test.write(['dir', 'sub', '{}.dat'.format(sconsign)], "dir/{}.dat\n".format(sconsign))
+ self.test.write(['dir', 'sub', '{}.dblite'.format(sconsign)], "dir/{}.dblite\n".format(sconsign))
+ self.test.write(['dir', 'sub', '{}.dir'.format(sconsign)], "dir/{}.dir\n".format(sconsign))
+ self.test.write(['dir', 'sub', '{}.pag'.format(sconsign)], "dir/{}.pag\n".format(sconsign))
class DirScannerTestCase(DirScannerTestBase):
def runTest(self):
@@ -88,7 +91,7 @@ class DirScannerTestCase(DirScannerTestBase):
]
deps = s(env.Dir('dir'), env, ())
sss = list(map(str, deps))
- assert sss == expect, sss
+ assert sss == expect, "Found {}, expected {}".format(sss, expect)
expect = [
os.path.join('dir', 'sub', 'f3'),
@@ -96,7 +99,7 @@ class DirScannerTestCase(DirScannerTestBase):
]
deps = s(env.Dir('dir/sub'), env, ())
sss = list(map(str, deps))
- assert sss == expect, sss
+ assert sss == expect, "Found {}, expected {}".format(sss, expect)
class DirEntryScannerTestCase(DirScannerTestBase):
def runTest(self):
@@ -106,16 +109,16 @@ class DirEntryScannerTestCase(DirScannerTestBase):
deps = s(env.Dir('dir'), env, ())
sss = list(map(str, deps))
- assert sss == [], sss
+ assert sss == [], "Found {}, expected {}".format(sss, [])
deps = s(env.Dir('dir/sub'), env, ())
sss = list(map(str, deps))
- assert sss == [], sss
+ assert sss == [], "Found {}, expected {}".format(sss, [])
# Make sure we don't blow up if handed a non-Dir node.
deps = s(env.File('dir/f1'), env, ())
sss = list(map(str, deps))
- assert sss == [], sss
+ assert sss == [], "Found {}, expected {}".format(sss, [])
if __name__ == "__main__":
unittest.main()
diff --git a/SCons/Util.py b/SCons/Util.py
index 0dd6ff1..cbd99b0 100644
--- a/SCons/Util.py
+++ b/SCons/Util.py
@@ -1669,10 +1669,86 @@ def AddMethod(obj, function, name=None):
# 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_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 the
+ key "usedforsecurity"=false. This lets us continue to use algorithms that have
+ been deprecated either by FIPS or by Python itself, as the MD5 algorithm SCons
+ 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, which
+ 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 caller.
+ """
+ if hash_function_object is None:
+ return None
+
+ # https://stackoverflow.com/a/11887885 details how to check versions with the "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 and 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 prior 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 screen.
+ 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 supported.
+
+ 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 variable.
+
+ This function checks the DEFAULT_HASH_FORMATS and sets the ALLOWED_HASH_FORMATS
+ to only the ones that can be called. In Python >= 3.9 this will always default to
+ MD5 as in Python 3.9 there is an optional attribute "usedforsecurity" set for 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 needed.
+ # 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 FIPS mode,
+ # meaning if we call the default algorithm returned with no parameters, 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():
"""Retrieves the hash format or ``None`` if not overridden.
@@ -1684,8 +1760,27 @@ def get_hash_format():
"""
return _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 being enabled
+ # however, if we get to this point, the viable hash function check has either 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 error.
+ return None
-def set_hash_format(hash_format):
+def set_hash_format(hash_format, hashlib_used=hashlib, sys_used=sys):
"""Sets the default hash format used by SCons.
If `hash_format` is ``None`` or
@@ -1702,24 +1797,58 @@ def set_hash_format(hash_format):
if hash_format_lower not in ALLOWED_HASH_FORMATS:
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 supported by
+ # SCons, example, selecting MD5 in an OS with FIPS-mode turned on. Therefore 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, the '
+ '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 local 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' %
(hash_format_lower,
- ', '.join(ALLOWED_HASH_FORMATS)))
-
- _HASH_FUNCTION = getattr(hashlib, hash_format_lower, None)
+ ', '.join(ALLOWED_HASH_FORMATS))
+ )
+ else:
+ raise UserError('Hash format "%s" is not supported by SCons. '
+ 'SCons supports more hash formats than your local system '
+ '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_viable_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_used, sys_used)
+
if _HASH_FUNCTION is None:
from SCons.Errors import UserError # pylint: disable=import-outside-toplevel
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_default_hashes, '
+ 'Assertion error in SCons.'
% hash_format_lower
)
else:
# 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 compatibility.
+ # in FIPS-compliant systems this usually defaults to SHA1, unless that too has been
+ # disabled.
for choice in ALLOWED_HASH_FORMATS:
- _HASH_FUNCTION = getattr(hashlib, choice, None)
+ _HASH_FUNCTION = _attempt_get_hash_function(choice, hashlib_used, sys_used)
+
if _HASH_FUNCTION is not None:
break
else:
@@ -1728,7 +1857,9 @@ def set_hash_format(hash_format):
raise UserError(
'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:
# 1. This code is running in a unit test.
@@ -1737,7 +1868,19 @@ def set_hash_format(hash_format):
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.
Args:
@@ -1752,7 +1895,7 @@ def _get_hash_object(hash_format):
raise UserError('There is no default hash function. Did you call '
'a hashing function before SCons was initialized?')
- return _HASH_FUNCTION()
+ return _attempt_init_of_python_3_9_hash_object(getattr(hashlib_used, _HASH_FUNCTION, None), sys_used)
if not hasattr(hashlib, hash_format):
from SCons.Errors import UserError # pylint: disable=import-outside-toplevel
@@ -1761,7 +1904,7 @@ def _get_hash_object(hash_format):
'Hash format "%s" is not available in your Python interpreter.' %
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):
diff --git a/SCons/UtilTests.py b/SCons/UtilTests.py
index 616ea37..8001d2e 100644
--- a/SCons/UtilTests.py
+++ b/SCons/UtilTests.py
@@ -26,13 +26,17 @@ import io
import os
import sys
import unittest
-from collections import UserDict, UserList, UserString
+import unittest.mock
+import hashlib
+import warnings
+from collections import UserDict, UserList, UserString, namedtuple
import TestCmd
import SCons.Errors
import SCons.compat
from SCons.Util import (
+ ALLOWED_HASH_FORMATS,
AddPathIfNotExists,
AppendPath,
CLVar,
@@ -42,6 +46,10 @@ from SCons.Util import (
Proxy,
Selector,
WhereIs,
+ _attempt_init_of_python_3_9_hash_object,
+ _attempt_get_hash_function,
+ _get_hash_object,
+ _set_allowed_viable_default_hashes,
adjustixes,
containsAll,
containsAny,
@@ -61,6 +69,7 @@ from SCons.Util import (
is_Tuple,
print_tree,
render_tree,
+ set_hash_format,
silent_intern,
splitext,
to_String,
@@ -838,12 +847,17 @@ class HashTestCase(unittest.TestCase):
'25235f0fcab8767b7b5ac6568786fbc4f7d5d83468f0626bf07c3dbeed391a7a',
'f8d3d0729bf2427e2e81007588356332e7e8c4133fae4bceb173b93f33411d17'),
}.items():
- hs = functools.partial(hash_signature, hash_format=algorithm)
- s = list(map(hs, ('111', '222', '333')))
-
- assert expected[0] == hash_collect(s[0:1], hash_format=algorithm)
- assert expected[1] == hash_collect(s[0:2], hash_format=algorithm)
- assert expected[2] == hash_collect(s, hash_format=algorithm)
+ # if the current platform does not support the algorithm we're looking at,
+ # skip the test steps for that algorithm, but display a warning to the user
+ if algorithm not in ALLOWED_HASH_FORMATS:
+ warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
+ else:
+ hs = functools.partial(hash_signature, hash_format=algorithm)
+ s = list(map(hs, ('111', '222', '333')))
+
+ assert expected[0] == hash_collect(s[0:1], hash_format=algorithm)
+ assert expected[1] == hash_collect(s[0:2], hash_format=algorithm)
+ assert expected[2] == hash_collect(s, hash_format=algorithm)
def test_MD5signature(self):
"""Test generating a signature"""
@@ -855,11 +869,162 @@ class HashTestCase(unittest.TestCase):
'sha256': ('f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae',
'9b871512327c09ce91dd649b3f96a63b7408ef267c8cc5710114e629730cb61f'),
}.items():
- s = hash_signature('111', hash_format=algorithm)
- assert expected[0] == s, s
-
- s = hash_signature('222', hash_format=algorithm)
- assert expected[1] == s, s
+ # if the current platform does not support the algorithm we're looking at,
+ # skip the test steps for that algorithm, but display a warning to the user
+ if algorithm not in ALLOWED_HASH_FORMATS:
+ warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
+ else:
+ s = hash_signature('111', hash_format=algorithm)
+ assert expected[0] == s, s
+
+ s = hash_signature('222', hash_format=algorithm)
+ assert expected[1] == s, s
+
+# this uses mocking out, which is platform specific, however, the FIPS
+# behavior this is testing is also platform-specific, and only would be
+# visible in hosts running Linux with the fips_mode kernel flag along
+# with using OpenSSL.
+
+class FIPSHashTestCase(unittest.TestCase):
+ def __init__(self, *args, **kwargs):
+ super(FIPSHashTestCase, self).__init__(*args, **kwargs)
+
+ ###############################
+ # algorithm mocks, can check if we called with usedforsecurity=False for python >= 3.9
+ self.fake_md5=lambda usedforsecurity=True: (usedforsecurity, 'md5')
+ self.fake_sha1=lambda usedforsecurity=True: (usedforsecurity, 'sha1')
+ self.fake_sha256=lambda usedforsecurity=True: (usedforsecurity, 'sha256')
+ ###############################
+
+ ###############################
+ # hashlib mocks
+ md5Available = unittest.mock.Mock(md5=self.fake_md5)
+ del md5Available.sha1
+ del md5Available.sha256
+ self.md5Available=md5Available
+
+ md5Default = unittest.mock.Mock(md5=self.fake_md5, sha1=self.fake_sha1)
+ del md5Default.sha256
+ self.md5Default=md5Default
+
+ sha1Default = unittest.mock.Mock(sha1=self.fake_sha1, sha256=self.fake_sha256)
+ del sha1Default.md5
+ self.sha1Default=sha1Default
+
+ sha256Default = unittest.mock.Mock(sha256=self.fake_sha256, **{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError})
+ self.sha256Default=sha256Default
+
+ all_throw = unittest.mock.Mock(**{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError, 'sha256.side_effect': ValueError})
+ self.all_throw=all_throw
+
+ no_algorithms = unittest.mock.Mock()
+ del no_algorithms.md5
+ del no_algorithms.sha1
+ del no_algorithms.sha256
+ del no_algorithms.nonexist
+ self.no_algorithms=no_algorithms
+
+ unsupported_algorithm = unittest.mock.Mock(unsupported=self.fake_sha256)
+ del unsupported_algorithm.md5
+ del unsupported_algorithm.sha1
+ del unsupported_algorithm.sha256
+ del unsupported_algorithm.unsupported
+ self.unsupported_algorithm=unsupported_algorithm
+ ###############################
+
+ ###############################
+ # system version mocks
+ VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
+ v3_8 = VersionInfo(3, 8, 199, 'super-beta', 1337)
+ v3_9 = VersionInfo(3, 9, 0, 'alpha', 0)
+ v4_8 = VersionInfo(4, 8, 0, 'final', 0)
+
+ self.sys_v3_8 = unittest.mock.Mock(version_info=v3_8)
+ self.sys_v3_9 = unittest.mock.Mock(version_info=v3_9)
+ self.sys_v4_8 = unittest.mock.Mock(version_info=v4_8)
+ ###############################
+
+ def test_basic_failover_bad_hashlib_hash_init(self):
+ """Tests that if the hashing function is entirely missing from hashlib (hashlib returns None),
+ the hash init function returns None"""
+ assert _attempt_init_of_python_3_9_hash_object(None) is None
+
+ def test_basic_failover_bad_hashlib_hash_get(self):
+ """Tests that if the hashing function is entirely missing from hashlib (hashlib returns None),
+ the hash get function returns None"""
+ assert _attempt_get_hash_function("nonexist", self.no_algorithms) is None
+
+ def test_usedforsecurity_flag_behavior(self):
+ """Test usedforsecurity flag -> should be set to 'True' on older versions of python, and 'False' on Python >= 3.9"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'md5'),
+ self.sys_v3_9: (False, 'md5'),
+ self.sys_v4_8: (False, 'md5'),
+ }.items():
+ assert _attempt_init_of_python_3_9_hash_object(self.fake_md5, version) == expected
+
+ def test_automatic_default_to_md5(self):
+ """Test automatic default to md5 even if sha1 available"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'md5'),
+ self.sys_v3_9: (False, 'md5'),
+ self.sys_v4_8: (False, 'md5'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.md5Default, version)
+ set_hash_format(None, self.md5Default, version)
+ assert _get_hash_object(None, self.md5Default, version) == expected
+
+ def test_automatic_default_to_sha256(self):
+ """Test automatic default to sha256 if other algorithms available but throw"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'sha256'),
+ self.sys_v3_9: (False, 'sha256'),
+ self.sys_v4_8: (False, 'sha256'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.sha256Default, version)
+ set_hash_format(None, self.sha256Default, version)
+ assert _get_hash_object(None, self.sha256Default, version) == expected
+
+ def test_automatic_default_to_sha1(self):
+ """Test automatic default to sha1 if md5 is missing from hashlib entirely"""
+ for version, expected in {
+ self.sys_v3_8: (True, 'sha1'),
+ self.sys_v3_9: (False, 'sha1'),
+ self.sys_v4_8: (False, 'sha1'),
+ }.items():
+ _set_allowed_viable_default_hashes(self.sha1Default, version)
+ set_hash_format(None, self.sha1Default, version)
+ assert _get_hash_object(None, self.sha1Default, version) == expected
+
+ def test_no_available_algorithms(self):
+ """expect exceptions on no available algorithms or when all algorithms throw"""
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.no_algorithms)
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.all_throw)
+ self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.unsupported_algorithm)
+
+ def test_bad_algorithm_set_attempt(self):
+ """expect exceptions on user setting an unsupported algorithm selections, either by host or by SCons"""
+
+ # nonexistant hash algorithm, not supported by SCons
+ _set_allowed_viable_default_hashes(self.md5Available)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'blah blah blah', hashlib_used=self.no_algorithms)
+
+ # md5 is default-allowed, but in this case throws when we attempt to use it
+ _set_allowed_viable_default_hashes(self.md5Available)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)
+
+ # user attempts to use an algorithm that isn't supported by their current system but is supported by SCons
+ _set_allowed_viable_default_hashes(self.sha1Default)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)
+
+ # user attempts to use an algorithm that is supported by their current system but isn't supported by SCons
+ _set_allowed_viable_default_hashes(self.sha1Default)
+ self.assertRaises(SCons.Errors.UserError, set_hash_format, 'unsupported', hashlib_used=self.unsupported_algorithm)
+
+ def tearDown(self):
+ """Return SCons back to the normal global state for the hashing functions."""
+ _set_allowed_viable_default_hashes(hashlib, sys)
+ set_hash_format(None)
class NodeListTestCase(unittest.TestCase):
diff --git a/doc/man/scons.xml b/doc/man/scons.xml
index 199e694..6b5bece 100644
--- a/doc/man/scons.xml
+++ b/doc/man/scons.xml
@@ -1185,9 +1185,15 @@ to use the specified algorithm.</para>
For example, <option>--hash-format=sha256</option> will create a SConsign
database with name <filename>.sconsign_sha256.dblite</filename>.</para>
-<para>If this option is not specified, a hash format of
-md5 is used, and the SConsign database is
-<filename>.sconsign.dblite</filename>.</para>
+<para>If this option is not specified, a the first supported hash format found
+is selected. Typically this is MD5, however, if you are on a FIPS-compliant system
+and using a version of Python less than 3.9, SHA1 or SHA256 will be chosen as the default.
+Python 3.9 and onwards clients will always default to MD5, even in FIPS mode, unless
+otherwise specified with the <option>--hash-format</option> option.</para>
+
+<para>For MD5 databases (either explicitly specified with <option>--hash-format=md5</option> or
+defaulted), the SConsign database is<filename>.sconsign.dblite</filename>. The newer SHA1 and
+SHA256 selections meanwhile store their databases to <filename>.sconsign_algorithmname.dblite</filename></para>
<para><emphasis>Available since &scons; 4.2.</emphasis></para>
</listitem>
diff --git a/test/Configure/ConfigureDryRunError.py b/test/Configure/ConfigureDryRunError.py
index 3648518..224154b 100644
--- a/test/Configure/ConfigureDryRunError.py
+++ b/test/Configure/ConfigureDryRunError.py
@@ -31,6 +31,8 @@ import os
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
+
_obj = TestSCons._obj
test = TestSCons.TestSCons()
@@ -65,7 +67,16 @@ test.run(arguments='-n', status=2, stderr=expect)
test.must_not_exist('config.log')
test.subdir('.sconf_temp')
-conftest_0_c = os.path.join(".sconf_temp", "conftest_df286a1d2f67e69d030b4eff75ca7e12_0.c")
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_df286a1d2f67e69d030b4eff75ca7e12_0.c",
+ 'sha1': "conftest_6e784ac3248d146c68396335df2f9428d78c1d24_0.c",
+ 'sha256': "conftest_be4b3c1d600e20dfc3e8d98748cd8193c850331643466d800ef8a4229a5be410_0.c"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
+conftest_0_c = os.path.join(".sconf_temp", test_filename)
SConstruct_file_line = test.python_file_line(SConstruct_path, 6)[:-1]
expect = """
diff --git a/test/Configure/VariantDir-SConscript.py b/test/Configure/VariantDir-SConscript.py
index deb7b8f..5818fc7 100644
--- a/test/Configure/VariantDir-SConscript.py
+++ b/test/Configure/VariantDir-SConscript.py
@@ -128,7 +128,7 @@ test.checkLogAndStdout( ["Checking for C header file math.h... ",
import shutil
shutil.rmtree(test.workpath(".sconf_temp"))
-test.unlink(".sconsign.dblite")
+test.unlink(test.get_sconsignname()+".dblite")
# now with SConscriptChdir(1)
test.run(arguments='chdir=yes')
diff --git a/test/Configure/implicit-cache.py b/test/Configure/implicit-cache.py
index f4f3e94..2cf74d1 100644
--- a/test/Configure/implicit-cache.py
+++ b/test/Configure/implicit-cache.py
@@ -55,6 +55,7 @@ get longer and longer until it blew out the users's memory.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import TestSConsign
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
test = TestSConsign.TestSConsign()
@@ -75,7 +76,28 @@ test.write('foo.h', "#define FOO 1\n")
test.run(arguments = '.')
-test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+# this test is different than the other tests, as the database name is used here.
+# when the database defaults to md5, that's a different name than when the user selects md5 directly.
+possible_filenames = {
+ 'default': "conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c",
+ 'md5': "conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c",
+ 'sha1': "conftest_80e5b88f2c7427a92f0e6c7184f144f874f10e60_0.c",
+ 'sha256': "conftest_ba8270c26647ad00993cd7777f4c5d3751018372b97d16eb993563bea051c3df_0.c"
+}
+# user left algorithm default, it defaulted to md5, with the special database name
+if get_hash_format() is None and get_current_hash_algorithm_used() == 'md5':
+ test_filename = possible_filenames['default']
+# either user selected something (like explicitly setting md5) or algorithm defaulted to something else.
+# SCons can default to something else if it detects the hashlib doesn't support it, example md5 in FIPS
+# mode prior to Python 3.9
+else:
+ test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
+database_name=test.get_sconsignname() + ".dblite"
+
+test.run_sconsign('-d .sconf_temp -e {} --raw {}'.format(test_filename, database_name))
old_sconsign_dblite = test.stdout()
# Second run: Have the configure subsystem also look for foo.h, so
@@ -88,11 +110,11 @@ old_sconsign_dblite = test.stdout()
test.run(arguments = '--implicit-cache USE_FOO=1 .')
-test.run_sconsign('-d .sconf_temp -e conftest_5a3fa36d51dd2a28d521d6cc0e2e1d04_0.c --raw .sconsign.dblite')
+test.run_sconsign('-d .sconf_temp -e {} --raw {}'.format(test_filename, database_name))
new_sconsign_dblite = test.stdout()
if old_sconsign_dblite != new_sconsign_dblite:
- print(".sconsign.dblite did not match:")
+ print("{} did not match:".format(database_name))
print("FIRST RUN ==========")
print(old_sconsign_dblite)
print("SECOND RUN ==========")
diff --git a/test/Configure/option--config.py b/test/Configure/option--config.py
index f31336f..3fd0691 100644
--- a/test/Configure/option--config.py
+++ b/test/Configure/option--config.py
@@ -32,6 +32,7 @@ import os.path
from TestSCons import TestSCons, ConfigCheckInfo, _obj
from TestCmd import IS_WINDOWS
+from SCons.Util import get_current_hash_algorithm_used
test = TestSCons()
@@ -42,6 +43,11 @@ CR = test.CR # cached rebuild (up to date)
NCF = test.NCF # non-cached build failure
CF = test.CF # cached build failure
+# as this test is somewhat complicated, skip it if the library doesn't support md5
+# as the default hashing algorithm.
+if get_current_hash_algorithm_used() != 'md5':
+ test.skip_test('Skipping test as could not continue without the hash algorithm set to md5!')
+
SConstruct_path = test.workpath('SConstruct')
test.write(SConstruct_path, """
diff --git a/test/Removed/SourceSignatures/Old/no-csigs.py b/test/Removed/SourceSignatures/Old/no-csigs.py
index 60c0460..483eeea 100644
--- a/test/Removed/SourceSignatures/Old/no-csigs.py
+++ b/test/Removed/SourceSignatures/Old/no-csigs.py
@@ -66,7 +66,7 @@ f2.out: \S+ \d+ \d+
\S+ \[build\(target, source, env\)\]
"""
-test.run_sconsign(arguments = test.workpath('.sconsign'),
+test.run_sconsign(arguments = test.workpath(test.get_sconsignname()),
stdout = expect)
diff --git a/test/Repository/variants.py b/test/Repository/variants.py
index f89605b..c95e853 100644
--- a/test/Repository/variants.py
+++ b/test/Repository/variants.py
@@ -216,12 +216,14 @@ repository/src1/bbb.c: REPOSITORY_FOO
repository/src1/main.c: REPOSITORY_FOO
""")
+database_name=test.get_sconsignname()
+
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.run(program=repository_build2_foo_src2_xxx_xxx, stdout="""\
repository/src2/include/my_string.h: FOO
@@ -236,11 +238,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
# Make the entire repository non-writable, so we'll detect
# if we try to write into it accidentally.
@@ -270,11 +272,11 @@ repository/src1/main.c: REPOSITORY_BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=bar", arguments='build1')
@@ -301,11 +303,11 @@ repository/src1/main.c: WORK_BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=bar", arguments='build1')
@@ -319,11 +321,11 @@ repository/src1/main.c: WORK_FOO
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
test.up_to_date(chdir='work1', options=opts + " OS=foo", arguments='build1')
@@ -376,11 +378,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
# Ensure file time stamps will be newer.
time.sleep(2)
@@ -411,11 +413,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
#
test.unlink(['work2', 'src2', 'include', 'my_string.h'])
@@ -435,11 +437,11 @@ repository/src2/xxx/main.c: BAR
""")
test.fail_test(os.path.exists(
- test.workpath('repository', 'src1', '.sconsign')))
+ test.workpath('repository', 'src1', database_name)))
test.fail_test(os.path.exists(
- test.workpath('repository', 'src2', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work1', 'src1', '.sconsign')))
-test.fail_test(os.path.exists(test.workpath('work2', 'src2', '.sconsign')))
+ test.workpath('repository', 'src2', database_name)))
+test.fail_test(os.path.exists(test.workpath('work1', 'src1', database_name)))
+test.fail_test(os.path.exists(test.workpath('work2', 'src2', database_name)))
#
test.pass_test()
diff --git a/test/SConsignFile/default.py b/test/SConsignFile/default.py
index 868f9d7..99ae54e 100644
--- a/test/SConsignFile/default.py
+++ b/test/SConsignFile/default.py
@@ -59,9 +59,12 @@ test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
test.run()
-test.must_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+database_name = test.get_sconsignname()
+database_filename = database_name + ".dblite"
+
+test.must_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -70,9 +73,9 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments='.')
-test.must_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+test.must_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.pass_test()
diff --git a/test/SConsignFile/explicit-dbm-module.py b/test/SConsignFile/explicit-dbm-module.py
index c093271..628d8b5 100644
--- a/test/SConsignFile/explicit-dbm-module.py
+++ b/test/SConsignFile/explicit-dbm-module.py
@@ -61,9 +61,12 @@ test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
test.run()
-test.must_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+database_name = test.get_sconsignname()
+database_filename = database_name + ".dblite"
+
+test.must_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -72,9 +75,9 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments='.')
-test.must_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+test.must_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.pass_test()
diff --git a/test/SConsignFile/explicit-file.py b/test/SConsignFile/explicit-file.py
index 850b0ef..c601f7f 100644
--- a/test/SConsignFile/explicit-file.py
+++ b/test/SConsignFile/explicit-file.py
@@ -59,9 +59,10 @@ test.write(['subdir', 'f8.in'], "subdir/f8.in\n")
test.run()
+database_name = test.get_sconsignname()
test.must_exist(test.workpath('my_sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.must_match('f5.out', "f5.in\n")
test.must_match('f6.out', "f6.in\n")
@@ -71,8 +72,8 @@ test.must_match(['subdir', 'f8.out'], "subdir/f8.in\n")
test.up_to_date(arguments='.')
test.must_exist(test.workpath('my_sconsign.dblite'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
+test.must_not_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath('subdir', database_name))
test.pass_test()
diff --git a/test/SConsignFile/make-directory.py b/test/SConsignFile/make-directory.py
index 264ee26..f2a0db1 100644
--- a/test/SConsignFile/make-directory.py
+++ b/test/SConsignFile/make-directory.py
@@ -57,7 +57,8 @@ test.must_not_exist(['bar', 'foo.txt'])
test.must_not_exist('sub')
test.must_not_exist(['sub', 'dir'])
-test.must_not_exist(['sub', 'dir', '.sconsign.dblite'])
+database_name = test.get_sconsignname()
+test.must_not_exist(['sub', 'dir', database_name + '.dblite'])
test.run(stdout=expect)
diff --git a/test/SConsignFile/use-dbhash.py b/test/SConsignFile/use-dbhash.py
index 2968cd7..65eb92c 100644
--- a/test/SConsignFile/use-dbhash.py
+++ b/test/SConsignFile/use-dbhash.py
@@ -48,10 +48,12 @@ with open(sys.argv[1], 'wb') as ofp, open(sys.argv[2], 'rb') as ifp:
sys.exit(0)
""")
+database_name = test.get_sconsignname()
+
#
test.write('SConstruct', """
import %(use_dbm)s
-SConsignFile('.sconsign', %(use_dbm)s)
+SConsignFile('%(database_name)s', %(use_dbm)s)
DefaultEnvironment(tools=[])
B = Builder(action = r'%(_python_)s build.py $TARGETS $SOURCES')
env = Environment(BUILDERS={'B': B}, tools=[])
@@ -68,10 +70,12 @@ test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
test.run()
-test.must_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+database_name = test.get_sconsignname()
+database_filename = database_name + ".dblite"
+test.must_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -80,10 +84,10 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments = '.')
-test.must_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+test.must_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.pass_test()
diff --git a/test/SConsignFile/use-dbm.py b/test/SConsignFile/use-dbm.py
index a1ef1b2..6b6c96b 100644
--- a/test/SConsignFile/use-dbm.py
+++ b/test/SConsignFile/use-dbm.py
@@ -49,10 +49,11 @@ with open(sys.argv[1], 'wb') as ofp, open(sys.argv[2], 'rb') as ifp:
sys.exit(0)
""")
+database_name = test.get_sconsignname()
#
test.write('SConstruct', """
import %(use_dbm)s
-SConsignFile('.sconsign', %(use_dbm)s)
+SConsignFile('%(database_name)s', %(use_dbm)s)
DefaultEnvironment(tools=[])
B = Builder(action=r'%(_python_)s build.py $TARGETS $SOURCES')
env = Environment(BUILDERS={'B': B}, tools=[])
@@ -72,13 +73,15 @@ test.run()
# We don't check for explicit .db or other file, because base "dbm"
# can use different file extensions on different implementations.
+database_name = test.get_sconsignname()
+database_filename = database_name + '.dblite'
test.fail_test(
- os.path.exists('.sconsign') and 'dbm' not in dbm.whichdb('.sconsign'),
- message=".sconsign existed and wasn't any type of dbm file",
+ os.path.exists(database_name) and 'dbm' not in dbm.whichdb(database_name),
+ message="{} existed and wasn't any type of dbm file".format(database_name),
)
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -87,11 +90,11 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments='.')
-test.fail_test(os.path.exists('.sconsign') and 'dbm' not in dbm.whichdb('.sconsign'),
- message=".sconsign existed and wasn't any type of dbm file")
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+test.fail_test(os.path.exists(database_name) and 'dbm' not in dbm.whichdb(database_name),
+ message="{} existed and wasn't any type of dbm file".format(database_name))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.pass_test()
diff --git a/test/SConsignFile/use-dumbdbm.py b/test/SConsignFile/use-dumbdbm.py
index 875f3fc..e7fb091 100644
--- a/test/SConsignFile/use-dumbdbm.py
+++ b/test/SConsignFile/use-dumbdbm.py
@@ -48,10 +48,11 @@ with open(sys.argv[1], 'wb') as ofp, open(sys.argv[2], 'rb') as ifp:
sys.exit(0)
""")
+database_name = test.get_sconsignname()
#
test.write('SConstruct', """
import %(use_dbm)s
-SConsignFile('.sconsign', %(use_dbm)s)
+SConsignFile('%(database_name)s', %(use_dbm)s)
DefaultEnvironment(tools=[])
B = Builder(action=r'%(_python_)s build.py $TARGETS $SOURCES')
env = Environment(BUILDERS={'B': B}, tools=[])
@@ -68,14 +69,14 @@ test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
test.run()
-test.must_exist(test.workpath('.sconsign.dat'))
-test.must_exist(test.workpath('.sconsign.dir'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dat'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dir'))
+test.must_exist(test.workpath('{}.dat'.format(database_name)))
+test.must_exist(test.workpath('{}.dir'.format(database_name)))
+test.must_not_exist(test.workpath('{}'.format(database_name)))
+test.must_not_exist(test.workpath('{}.dblite'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dblite'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dat'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dir'.format(database_name)))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -84,14 +85,14 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments='.')
-test.must_exist(test.workpath('.sconsign.dat'))
-test.must_exist(test.workpath('.sconsign.dir'))
-test.must_not_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dat'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dir'))
+test.must_exist(test.workpath('{}.dat'.format(database_name)))
+test.must_exist(test.workpath('{}.dir'.format(database_name)))
+test.must_not_exist(test.workpath('{}'.format(database_name)))
+test.must_not_exist(test.workpath('{}.dblite'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dblite'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dat'.format(database_name)))
+test.must_not_exist(test.workpath('subdir', '{}.dir'.format(database_name)))
test.pass_test()
diff --git a/test/SConsignFile/use-gdbm.py b/test/SConsignFile/use-gdbm.py
index c1f0c4d..11ae052 100644
--- a/test/SConsignFile/use-gdbm.py
+++ b/test/SConsignFile/use-gdbm.py
@@ -48,10 +48,12 @@ with open(sys.argv[1], 'wb') as ofp, open(sys.argv[2], 'rb') as ifp:
sys.exit(0)
""")
+database_name = test.get_sconsignname()
+database_filename = database_name + '.dblite'
#
test.write('SConstruct', """
import %(use_dbm)s
-SConsignFile('.sconsign', %(use_dbm)s)
+SConsignFile('%(database_name)s', %(use_dbm)s)
DefaultEnvironment(tools=[])
B = Builder(action='%(_python_)s build.py $TARGETS $SOURCES')
env = Environment(BUILDERS={'B': B}, tools=[])
@@ -68,10 +70,10 @@ test.write(['subdir', 'f4.in'], "subdir/f4.in\n")
test.run()
-test.must_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+test.must_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.must_match('f1.out', "f1.in\n")
test.must_match('f2.out', "f2.in\n")
@@ -80,10 +82,10 @@ test.must_match(['subdir', 'f4.out'], "subdir/f4.in\n")
test.up_to_date(arguments='.')
-test.must_exist(test.workpath('.sconsign'))
-test.must_not_exist(test.workpath('.sconsign.dblite'))
-test.must_not_exist(test.workpath('subdir', '.sconsign'))
-test.must_not_exist(test.workpath('subdir', '.sconsign.dblite'))
+test.must_exist(test.workpath(database_name))
+test.must_not_exist(test.workpath(database_filename))
+test.must_not_exist(test.workpath('subdir', database_name))
+test.must_not_exist(test.workpath('subdir', database_filename))
test.pass_test()
diff --git a/test/implicit/changed-node.py b/test/implicit/changed-node.py
index c8c5a01..d89c14b 100644
--- a/test/implicit/changed-node.py
+++ b/test/implicit/changed-node.py
@@ -126,8 +126,9 @@ test.pass_test()
#def clean(full=0):
# for f in ('d','b','a','SConstruct'):
# rm(f)
+# database_name = test.get_sconsignname()
# if full:
-# for f in ('.sconsign.dblite', 'build.py'):
+# for f in (database_name + '.dblite', 'build.py'):
# rm(f)
#
#clean(1)
diff --git a/test/option/hash-format.py b/test/option/hash-format.py
index 9fa10ee..663ad2e 100644
--- a/test/option/hash-format.py
+++ b/test/option/hash-format.py
@@ -27,28 +27,51 @@ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import hashlib
import os
import TestSCons
+from SCons.Util import ALLOWED_HASH_FORMATS, DEFAULT_HASH_FORMATS
# Test passing the hash format by command-line.
INVALID_ALGORITHM = 'testfailure'
-for algorithm in ['md5', 'sha1', 'sha256', INVALID_ALGORITHM, None]:
+
+for algorithm in [*DEFAULT_HASH_FORMATS, INVALID_ALGORITHM, None]:
test = TestSCons.TestSCons()
test.dir_fixture('hash-format')
+ # Expect failure due to an unsupported/invalid algorithm.
+ # The error message however changes if SCons detects that the host system doesn't support one or more algorithms
+ # Primary reason the message changes is so user doesn't have to start with unsupported algorithm A and then attempt
+ # to switch to unsupported algorithm B.
+ # On normal systems (allowed=default) this will output a fixed message, but on FIPS-enabled or other weird systems
+ # that don't have default allowed algorithms, it informs the user of the mismatch _and_ the currently supported
+ # algorithms on the system they're using.
+ # In Python 3.9 this becomes somewhat obselete as the hashlib is informed we don't use hashing for security but
+ # for loose integrity.
if algorithm == INVALID_ALGORITHM:
- # Expect failure due to an unsupported/invalid algorithm.
- test.run('--hash-format=%s .' % algorithm, stderr=r"""
-scons: \*\*\* Hash format "{}" is not supported by SCons. Only the following hash formats are supported: md5, sha1, sha256
+ if ALLOWED_HASH_FORMATS == DEFAULT_HASH_FORMATS:
+ test.run('--hash-format=%s .' % algorithm, stderr=r"""
+scons: \*\*\* Hash format "{}" is not supported by SCons. Only the following hash formats are supported: {}
+File "[^"]+", line \d+, in \S+
+""".format(algorithm, ', '.join(DEFAULT_HASH_FORMATS)), status=2, match=TestSCons.match_re)
+ else:
+ test.run('--hash-format=%s .' % algorithm, stderr=r"""
+scons: \*\*\* Hash format "{}" is not supported by SCons. SCons supports more hash formats than your local system is reporting; SCons supports: {}. Your local system only supports: {}
File "[^"]+", line \d+, in \S+
-""".format(algorithm), status=2, match=TestSCons.match_re)
+""".format(algorithm, ', '.join(DEFAULT_HASH_FORMATS), ', '.join(ALLOWED_HASH_FORMATS)), status=2, match=TestSCons.match_re)
continue
elif algorithm is not None:
- # Skip any algorithm that the Python interpreter doesn't have.
- if hasattr(hashlib, algorithm):
+ if algorithm in ALLOWED_HASH_FORMATS:
expected_dblite = test.workpath('.sconsign_%s.dblite' % algorithm)
test.run('--hash-format=%s .' % algorithm)
else:
- print('Skipping test with --hash-format=%s because that '
- 'algorithm is not available.' % algorithm)
+ test.run('--hash-format=%s' % algorithm, stderr=r"""
+scons: \*\*\* While hash format "{}" is supported by SCons, the local system indicates only the following hash formats are supported by the hashlib library: {}
+File "[^"]+", line \d+, in \S+
+Error in atexit._run_exitfuncs:
+Traceback \(most recent call last\):
+ File "[^"]+", line \d+, in \S+
+ assert csig == '[a-z0-9]+', csig
+AssertionError: [a-z0-9]+
+""".format(algorithm, ', '.join(ALLOWED_HASH_FORMATS)), status=2, match=TestSCons.match_re)
+ continue
else:
# The SConsign file in the hash-format folder has logic to call
# SCons.Util.set_hash_format('sha256') if the default algorithm is
diff --git a/test/option/option-n.py b/test/option/option-n.py
index e647b8e..eb8eb62 100644
--- a/test/option/option-n.py
+++ b/test/option/option-n.py
@@ -43,6 +43,7 @@ import os
import re
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
_python_ = TestSCons._python_
@@ -118,7 +119,7 @@ test.fail_test(not os.path.exists(test.workpath('f1.out')))
expect = test.wrap_stdout("""\
%(_python_)s build.py f1.out
""" % locals())
-test.unlink('.sconsign.dblite')
+test.unlink(test.get_sconsignname()+'.dblite')
test.write('f1.out', "X1.out\n")
test.run(arguments='-n f1.out', stdout=expect)
test.run(arguments='-n f1.out', stdout=expect)
@@ -204,13 +205,23 @@ test.run(arguments="-n", stderr=stderr, status=2,
test.fail_test(os.path.exists(test.workpath("configure", "config.test")))
test.fail_test(os.path.exists(test.workpath("configure", "config.log")))
+
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in",
+ 'sha1': "conftest_0a4d55a8d778e5022fab701977c5d840bbc486d0_0.in",
+ 'sha256': "conftest_a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e_0.in"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
# test that targets are not built, if conf_dir exists.
# verify that .cache and config.log are not created.
# an error should be raised
stderr = r"""
scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
File \S+, line \S+, in \S+
-""" % re.escape(os.path.join("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+""" % re.escape(os.path.join("config.test", test_filename))
test.subdir(['configure', 'config.test'])
test.run(arguments="-n", stderr=stderr, status=2,
chdir=test.workpath("configure"))
diff --git a/test/question/Configure.py b/test/question/Configure.py
index 7df29f5..d01d0fa 100644
--- a/test/question/Configure.py
+++ b/test/question/Configure.py
@@ -36,6 +36,7 @@ import re
import TestCmd
import TestSCons
+from SCons.Util import get_current_hash_algorithm_used
test = TestSCons.TestSCons(match = TestCmd.match_re_dotall)
@@ -80,13 +81,22 @@ test.run(arguments="-q aaa.out",stderr=stderr,status=2)
test.must_not_exist(test.workpath("config.test"))
test.must_not_exist(test.workpath("config.log"))
+# depending on which default hash function we're using, we'd expect one of the following filenames.
+# The filenames are generated by the conftest changes in #3543 : https://github.com/SCons/scons/pull/3543/files
+possible_filenames = {
+ 'md5': "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in",
+ 'sha1': "conftest_0a4d55a8d778e5022fab701977c5d840bbc486d0_0.in",
+ 'sha256': "conftest_a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e_0.in"
+}
+test_filename = possible_filenames[get_current_hash_algorithm_used()]
+
# test that targets are not built, if conf_dir exists.
# verify that .cache and config.log are not created.
# an error should be raised
stderr=r"""
scons: \*\*\* Cannot update configure test "%s" within a dry-run\.
File \S+, line \S+, in \S+
-""" % re.escape(os.path.join("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+""" % re.escape(os.path.join("config.test", test_filename))
test.subdir('config.test')
@@ -94,7 +104,7 @@ test.run(arguments="-q aaa.out",stderr=stderr,status=2)
test.must_not_exist(test.workpath("config.test", ".cache"))
test.must_not_exist(test.workpath("config.test", "conftest_0"))
-test.must_not_exist(test.workpath("config.test", "conftest_b10a8db164e0754105b7a99be72e3fe5_0.in"))
+test.must_not_exist(test.workpath("config.test", test_filename))
test.must_not_exist(test.workpath("config.log"))
# test that no error is raised, if all targets are up-to-date. In this
diff --git a/test/sconsign/corrupt.py b/test/sconsign/corrupt.py
index 25b48e2..fa6a0e9 100644
--- a/test/sconsign/corrupt.py
+++ b/test/sconsign/corrupt.py
@@ -30,14 +30,20 @@ Test that we get proper warnings when .sconsign* files are corrupt.
import TestSCons
import TestCmd
+import re
test = TestSCons.TestSCons(match = TestCmd.match_re)
test.subdir('work1', ['work1', 'sub'],
'work2', ['work2', 'sub'])
-work1__sconsign_dblite = test.workpath('work1', '.sconsign.dblite')
-work2_sub__sconsign = test.workpath('work2', 'sub', '.sconsign')
+database_name = test.get_sconsignname()
+database_filename = database_name + ".dblite"
+
+# for test1 we're using the default database filename
+work1__sconsign_dblite = test.workpath('work1', database_filename)
+# for test 2 we have an explicit hardcode to .sconsign
+work2_sub__sconsign = test.workpath('work2', 'sub', database_name)
SConstruct_contents = """\
def build1(target, source, env):
@@ -57,9 +63,9 @@ test.write(['work1', 'SConstruct'], SConstruct_contents)
test.write(['work1', 'foo.in'], "work1/foo.in\n")
stderr = r'''
-scons: warning: Ignoring corrupt .sconsign file: \.sconsign\.dblite
+scons: warning: Ignoring corrupt .sconsign file: {}
.*
-'''
+'''.format(re.escape(database_filename))
stdout = test.wrap_stdout(r'build1\(\["sub.foo\.out"\], \["foo\.in"\]\)' + '\n')
@@ -82,9 +88,9 @@ test.write(['work2', 'SConstruct'], SConstruct_contents)
test.write(['work2', 'foo.in'], "work2/foo.in\n")
stderr = r'''
-scons: warning: Ignoring corrupt .sconsign file: sub.\.sconsign
+scons: warning: Ignoring corrupt .sconsign file: sub.{}
.*
-'''
+'''.format(database_name)
stdout = test.wrap_stdout(r'build1\(\["sub.foo\.out"\], \["foo\.in"\]\)' + '\n')
diff --git a/test/sconsign/nonwritable.py b/test/sconsign/nonwritable.py
index 812a476..e952078 100644
--- a/test/sconsign/nonwritable.py
+++ b/test/sconsign/nonwritable.py
@@ -44,10 +44,11 @@ test.subdir('work1',
['work2', 'sub2'],
['work2', 'sub3'])
-work1__sconsign_dblite = test.workpath('work1', '.sconsign.dblite')
-work2_sub1__sconsign = test.workpath('work2', 'sub1', '.sconsign')
-work2_sub2__sconsign = test.workpath('work2', 'sub2', '.sconsign')
-work2_sub3__sconsign = test.workpath('work2', 'sub3', '.sconsign')
+database_name = test.get_sconsignname()
+work1__sconsign_dblite = test.workpath('work1', database_name + '.dblite')
+work2_sub1__sconsign = test.workpath('work2', 'sub1', database_name)
+work2_sub2__sconsign = test.workpath('work2', 'sub2', database_name)
+work2_sub3__sconsign = test.workpath('work2', 'sub3', database_name)
SConstruct_contents = """\
def build1(target, source, env):
diff --git a/test/sconsign/script/Configure.py b/test/sconsign/script/Configure.py
index 125022d..02a2c20 100644
--- a/test/sconsign/script/Configure.py
+++ b/test/sconsign/script/Configure.py
@@ -66,7 +66,7 @@ env = conf.Finish()
test.run(arguments = '.')
-sig_re = r'[0-9a-fA-F]{32}'
+sig_re = r'[0-9a-fA-F]{32,64}'
date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
_sconf_temp_conftest_0_c = '.sconf_temp/conftest_%(sig_re)s_0.c'%locals()
@@ -90,7 +90,9 @@ conftest_%(sig_re)s_0_%(sig_re)s%(_obj)s:
%(CC_file)s: %(sig_re)s \d+ \d+
""" % locals()
-test.run_sconsign(arguments = ".sconsign",
+# grab .sconsign or .sconsign_<hashname>
+database_name=test.get_sconsignname()
+test.run_sconsign(arguments = database_name,
stdout = expect)
test.pass_test()
diff --git a/test/sconsign/script/SConsignFile.py b/test/sconsign/script/SConsignFile.py
index c54e17b..4e29ef0 100644
--- a/test/sconsign/script/SConsignFile.py
+++ b/test/sconsign/script/SConsignFile.py
@@ -141,9 +141,11 @@ test.write(['sub2', 'inc2.h'], r"""\
test.run(arguments = '--implicit-cache .')
-sig_re = r'[0-9a-fA-F]{32}'
+sig_re = r'[0-9a-fA-F]{32,64}'
-test.run_sconsign(arguments = ".sconsign",
+database_name = test.get_sconsignname()
+
+test.run_sconsign(arguments = database_name,
stdout = r"""=== .:
SConstruct: None \d+ \d+
fake_cc\.py: %(sig_re)s \d+ \d+
@@ -174,7 +176,7 @@ inc1.h: %(sig_re)s \d+ \d+
inc2.h: %(sig_re)s \d+ \d+
""" % locals())
-test.run_sconsign(arguments = "--raw .sconsign",
+test.run_sconsign(arguments = "--raw " + database_name,
stdout = r"""=== .:
SConstruct: {'csig': None, 'timestamp': \d+L?, 'size': \d+L?, '_version_id': 2}
fake_cc\.py: {'csig': '%(sig_re)s', 'timestamp': \d+L?, 'size': \d+L?, '_version_id': 2}
@@ -302,9 +304,9 @@ inc2.h:
size: \d+
""" % locals()
-test.run_sconsign(arguments = "-v .sconsign", stdout=expect)
+test.run_sconsign(arguments = "-v " + database_name, stdout=expect)
-test.run_sconsign(arguments = "-c -v .sconsign",
+test.run_sconsign(arguments = "-c -v " + database_name,
stdout = r"""=== .:
SConstruct:
csig: None
@@ -332,7 +334,7 @@ inc2.h:
csig: %(sig_re)s
""" % locals())
-test.run_sconsign(arguments = "-s -v .sconsign",
+test.run_sconsign(arguments = "-s -v " + database_name,
stdout = r"""=== .:
SConstruct:
size: \d+
@@ -360,7 +362,7 @@ inc2.h:
size: \d+
""" % locals())
-test.run_sconsign(arguments = "-t -v .sconsign",
+test.run_sconsign(arguments = "-t -v " + database_name,
stdout = r"""=== .:
SConstruct:
timestamp: \d+
@@ -388,7 +390,7 @@ inc2.h:
timestamp: \d+
""" % locals())
-test.run_sconsign(arguments = "-e hello.obj .sconsign",
+test.run_sconsign(arguments = "-e hello.obj " + database_name,
stdout = r"""=== .:
=== sub1:
hello.obj: %(sig_re)s \d+ \d+
@@ -406,7 +408,7 @@ hello.obj: %(sig_re)s \d+ \d+
stderr = r"""sconsign: no entry `hello\.obj' in `\.'
""" % locals())
-test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj .sconsign",
+test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj " + database_name,
stdout = r"""=== .:
=== sub1:
hello.obj: %(sig_re)s \d+ \d+
@@ -444,7 +446,7 @@ sconsign: no entry `hello\.exe' in `\.'
sconsign: no entry `hello\.obj' in `\.'
""" % locals())
-#test.run_sconsign(arguments = "-i -v .sconsign",
+#test.run_sconsign(arguments = "-i -v " + database_name,
# stdout = r"""=== sub1:
#hello.exe:
# implicit:
diff --git a/test/sconsign/script/Signatures.py b/test/sconsign/script/Signatures.py
index 0f21496..ced5b44 100644
--- a/test/sconsign/script/Signatures.py
+++ b/test/sconsign/script/Signatures.py
@@ -149,10 +149,12 @@ test.sleep()
test.run(arguments = '. --max-drift=1')
-sig_re = r'[0-9a-fA-F]{32}'
+sig_re = r'[0-9a-fA-F]{32,64}'
date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
-test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/.sconsign",
+database_name = test.get_sconsignname()
+
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj sub1/{}".format(database_name),
stdout = r"""hello.exe: %(sig_re)s \d+ \d+
%(sub1_hello_obj)s: %(sig_re)s \d+ \d+
fake_link\.py: None \d+ \d+
@@ -163,7 +165,7 @@ hello.obj: %(sig_re)s \d+ \d+
%(sig_re)s \[.*\]
""" % locals())
-test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.exe -e hello.obj -r sub1/{}".format(database_name),
stdout = r"""hello.exe: %(sig_re)s '%(date_re)s' \d+
%(sub1_hello_obj)s: %(sig_re)s '%(date_re)s' \d+
fake_link\.py: None '%(date_re)s' \d+
diff --git a/test/sconsign/script/dblite.py b/test/sconsign/script/dblite.py
index 41cf7e2..1fcf8c0 100644
--- a/test/sconsign/script/dblite.py
+++ b/test/sconsign/script/dblite.py
@@ -109,7 +109,7 @@ test.sleep()
test.run(arguments = '. --max-drift=1')
-sig_re = r'[0-9a-fA-F]{32}'
+sig_re = r'[0-9a-fA-F]{32,64}'
date_re = r'\S+ \S+ [ \d]\d \d\d:\d\d:\d\d \d\d\d\d'
if sys.platform == 'win32':
diff --git a/test/sconsign/script/no-SConsignFile.py b/test/sconsign/script/no-SConsignFile.py
index c345829..bdd878b 100644
--- a/test/sconsign/script/no-SConsignFile.py
+++ b/test/sconsign/script/no-SConsignFile.py
@@ -40,6 +40,8 @@ test = TestSConsign.TestSConsign(match = TestSConsign.match_re)
test.subdir('sub1', 'sub2')
+database_name = test.get_sconsignname()
+
# Because this test sets SConsignFile(None), we execute our fake
# scripts directly, not by feeding them to the Python executable.
# That is, we chmod 0o755 and use a "#!/usr/bin/env python" first
@@ -152,7 +154,7 @@ test.write(['sub2', 'inc2.h'], r"""\
test.run(arguments = '--implicit-cache --tree=prune .')
-sig_re = r'[0-9a-fA-F]{32}'
+sig_re = r'[0-9a-fA-F]{32,64}'
expect = r"""hello.c: %(sig_re)s \d+ \d+
hello.exe: %(sig_re)s \d+ \d+
@@ -165,9 +167,9 @@ hello.obj: %(sig_re)s \d+ \d+
%(sig_re)s \[.*\]
""" % locals()
-test.run_sconsign(arguments = "sub1/.sconsign", stdout=expect)
+test.run_sconsign(arguments = "sub1/{}".format(database_name), stdout=expect)
-test.run_sconsign(arguments = "--raw sub1/.sconsign",
+test.run_sconsign(arguments = "--raw sub1/{}".format(database_name),
stdout = r"""hello.c: {'csig': '%(sig_re)s', 'timestamp': \d+L?, 'size': \d+L?, '_version_id': 2}
hello.exe: {'csig': '%(sig_re)s', 'timestamp': \d+L?, 'size': \d+L?, '_version_id': 2}
%(sub1_hello_obj)s: {'csig': '%(sig_re)s', 'timestamp': \d+L?, 'size': \d+L?, '_version_id': 2}
@@ -179,7 +181,7 @@ hello.obj: {'csig': '%(sig_re)s', 'timestamp': \d+L?, 'size': \d+L?, '_version_i
%(sig_re)s \[.*\]
""" % locals())
-test.run_sconsign(arguments = "-v sub1/.sconsign",
+test.run_sconsign(arguments = "-v sub1/{}".format(database_name),
stdout = r"""hello.c:
csig: %(sig_re)s
timestamp: \d+
@@ -214,7 +216,7 @@ hello.obj:
action: %(sig_re)s \[.*\]
""" % locals())
-test.run_sconsign(arguments = "-c -v sub1/.sconsign",
+test.run_sconsign(arguments = "-c -v sub1/{}".format(database_name),
stdout = r"""hello.c:
csig: %(sig_re)s
hello.exe:
@@ -223,7 +225,7 @@ hello.obj:
csig: %(sig_re)s
""" % locals())
-test.run_sconsign(arguments = "-s -v sub1/.sconsign",
+test.run_sconsign(arguments = "-s -v sub1/{}".format(database_name),
stdout = r"""hello.c:
size: \d+
hello.exe:
@@ -232,7 +234,7 @@ hello.obj:
size: \d+
""" % locals())
-test.run_sconsign(arguments = "-t -v sub1/.sconsign",
+test.run_sconsign(arguments = "-t -v sub1/{}".format(database_name),
stdout = r"""hello.c:
timestamp: \d+
hello.exe:
@@ -241,14 +243,14 @@ hello.obj:
timestamp: \d+
""" % locals())
-test.run_sconsign(arguments = "-e hello.obj sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj sub1/{}".format(database_name),
stdout = r"""hello.obj: %(sig_re)s \d+ \d+
%(sub1_hello_c)s: %(sig_re)s \d+ \d+
fake_cc\.py: %(sig_re)s \d+ \d+
%(sig_re)s \[.*\]
""" % locals())
-test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj -e hello.exe -e hello.obj sub1/{}".format(database_name),
stdout = r"""hello.obj: %(sig_re)s \d+ \d+
%(sub1_hello_c)s: %(sig_re)s \d+ \d+
fake_cc\.py: %(sig_re)s \d+ \d+
@@ -263,7 +265,7 @@ hello.obj: %(sig_re)s \d+ \d+
%(sig_re)s \[.*\]
""" % locals())
-test.run_sconsign(arguments = "sub2/.sconsign",
+test.run_sconsign(arguments = "sub2/{}".format(database_name),
stdout = r"""hello.c: %(sig_re)s \d+ \d+
hello.exe: %(sig_re)s \d+ \d+
%(sub2_hello_obj)s: %(sig_re)s \d+ \d+
@@ -279,7 +281,7 @@ inc1.h: %(sig_re)s \d+ \d+
inc2.h: %(sig_re)s \d+ \d+
""" % locals())
-#test.run_sconsign(arguments = "-i -v sub2/.sconsign",
+#test.run_sconsign(arguments = "-i -v sub2/{}".format(database_name),
# stdout = r"""hello.c: %(sig_re)s \d+ \d+
#hello.exe: %(sig_re)s \d+ \d+
# implicit:
@@ -291,7 +293,7 @@ inc2.h: %(sig_re)s \d+ \d+
# inc2.h: %(sig_re)s \d+ \d+
#""" % locals())
-test.run_sconsign(arguments = "-e hello.obj sub2/.sconsign sub1/.sconsign",
+test.run_sconsign(arguments = "-e hello.obj sub2/{} sub1/{}".format(database_name, database_name),
stdout = r"""hello.obj: %(sig_re)s \d+ \d+
%(sub2_hello_c)s: %(sig_re)s \d+ \d+
%(sub2_inc1_h)s: %(sig_re)s \d+ \d+
diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py
index 50280be..29bd123 100644
--- a/testing/framework/TestSCons.py
+++ b/testing/framework/TestSCons.py
@@ -45,6 +45,7 @@ from collections import namedtuple
from TestCommon import *
from TestCommon import __all__
+from SCons.Util import get_hash_format, get_current_hash_algorithm_used
from TestCmd import Popen
from TestCmd import PIPE
@@ -719,6 +720,27 @@ class TestSCons(TestCommon):
for p in patterns:
result.extend(sorted(glob.glob(p)))
return result
+
+ def get_sconsignname(self):
+ """Get the scons database name used, and return both the prefix and full filename.
+ if the user left the options defaulted AND the default algorithm set by
+ SCons is md5, then set the database name to be the special default name
+
+ otherwise, if it defaults to something like 'sha1' or the user explicitly
+ set 'md5' as the hash format, set the database name to .sconsign_<algorithm>
+ eg .sconsign_sha1, etc.
+
+ Returns:
+ a pair containing: the current dbname, the dbname.dblite filename
+ """
+ hash_format = get_hash_format()
+ current_hash_algorithm = get_current_hash_algorithm_used()
+ if hash_format is None and current_hash_algorithm == 'md5':
+ return ".sconsign"
+ else:
+ database_prefix=".sconsign_%s" % current_hash_algorithm
+ return database_prefix
+
def unlink_sconsignfile(self, name='.sconsign.dblite'):
"""Delete the sconsign file.
@@ -1415,10 +1437,10 @@ SConscript(sconscript)
for ext, flag in bld_desc: # each file in TryBuild
if ext in ['.c', '.cpp']:
conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
- r'_[a-z0-9]{32}_\d+%s' % re.escape(ext)
+ r'_[a-z0-9]{32,64}_\d+%s' % re.escape(ext)
elif ext == '':
conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
- r'_[a-z0-9]{32}(_\d+_[a-z0-9]{32})?'
+ r'_[a-z0-9]{32,64}(_\d+_[a-z0-9]{32,64})?'
else:
# We allow the second hash group to be optional because
@@ -1430,7 +1452,7 @@ SConscript(sconscript)
# TODO: perhaps revisit and/or fix file naming for intermediate files in
# Configure context logic
conf_filename = re.escape(os.path.join(sconf_dir, "conftest")) +\
- r'_[a-z0-9]{32}_\d+(_[a-z0-9]{32})?%s' % re.escape(ext)
+ r'_[a-z0-9]{32,64}_\d+(_[a-z0-9]{32,64})?%s' % re.escape(ext)
if flag == self.NCR:
# NCR = Non Cached Rebuild