Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Switch test framework from using profile to cProfile. profile
generates deprecation warnings starting with Python 3.15.
- Improve documentation of builder methods and builder objects.
- Make links clickable in the SetOption and GetOption manpage entries.


RELEASE 4.10.0 - Thu, 02 Oct 2025 11:40:20 -0700
Expand Down
2 changes: 2 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ DOCUMENTATION

- Improve documentation of builder methods and builder objects.

- Make links clickable in the SetOption and GetOption manpage entries.

DEVELOPMENT
-----------

Expand Down
36 changes: 28 additions & 8 deletions SCons/CacheDir.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@
b"# SCons cache directory - see https://bford.info/cachedir/\n"
)

# Defaults for the cache subsystem's globals. Most of these are filled in by
# SCons.Script.Main._build_targets, once the command line has been parsed.
cache_enabled = True
cache_debug = False
cache_force = False
cache_show = False
cache_readonly = False
cache_tmp_uuid = uuid.uuid4().hex
cli_cache_dir = ""

def CacheRetrieveFunc(target, source, env) -> int:
t = target[0]
Expand Down Expand Up @@ -210,9 +213,11 @@ def _mkdir_atomic(self, path: str) -> bool:
return False

try:
parent_dir = os.path.dirname(directory)
os.makedirs(parent_dir, exist_ok=True)
# TODO: Python 3.7. See comment below.
# tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(directory))
tempdir = tempfile.mkdtemp(dir=os.path.dirname(directory))
# tempdir = tempfile.TemporaryDirectory(dir=os.path.dirname(parent_dir))
tempdir = tempfile.mkdtemp(dir=os.path.dirname(parent_dir))
except OSError as e:
msg = "Failed to create cache directory " + path
raise SCons.Errors.SConsEnvironmentError(msg) from e
Expand Down Expand Up @@ -273,18 +278,33 @@ def CacheDebug(self, fmt, target, cachefile) -> None:
if cache_debug == '-':
self.debugFP = sys.stdout
elif cache_debug:
# TODO: this seems fragile. There can be only one debug output
# (terminal, file or none) per run, so it should not
# be reopened. Testing multiple caches showed a problem
# where reopening with 'w' mode meant some of the output
# was lost, so for the moment switched to append mode.
# . Keeping better track of the output file, or switching to
# using the logging module should help. The "persistence"
# of using append mode breaks test/CacheDir/debug.py
def debug_cleanup(debugFP) -> None:
debugFP.close()

self.debugFP = open(cache_debug, 'w')
self.debugFP = open(cache_debug, 'a')
atexit.register(debug_cleanup, self.debugFP)
else:
self.debugFP = None
self.current_cache_debug = cache_debug
if self.debugFP:
# TODO: consider emitting more than the base filename to help
# distinguish retrievals across variantdirs (target.relpath?).
# Separately, showing more of the cache entry path would be
# useful for testing, though possibly not otherwise. How else
# can you tell which target went to which cache if there are >1?
self.debugFP.write(fmt % (target, os.path.split(cachefile)[1]))
self.debugFP.write("requests: %d, hits: %d, misses: %d, hit rate: %.2f%%\n" %
(self.requests, self.hits, self.misses, self.hit_ratio))
self.debugFP.write(
"requests: %d, hits: %d, misses: %d, hit rate: %.2f%%\n" %
(self.requests, self.hits, self.misses, self.hit_ratio)
)

@classmethod
def copy_from_cache(cls, env, src, dst) -> str:
Expand Down Expand Up @@ -336,7 +356,7 @@ def cachepath(self, node) -> tuple:
Given a Node, obtain the configured cache directory and
the path to the cached file, which is generated from the
node's build signature. If caching is not enabled for the
None, return a tuple of None.
node, return a tuple of ``None``.
"""
if not self.is_enabled():
return None, None
Expand All @@ -349,11 +369,11 @@ def cachepath(self, node) -> tuple:
def retrieve(self, node) -> bool:
"""Retrieve a node from cache.

Returns True if a successful retrieval resulted.
Returns ``True`` if a successful retrieval resulted.

This method is called from multiple threads in a parallel build,
so only do thread safe stuff here. Do thread unsafe stuff in
built().
:meth:`built`.

Note that there's a special trick here with the execute flag
(one that's not normally done for other actions). Basically
Expand Down
7 changes: 7 additions & 0 deletions SCons/Environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import SCons.Action
import SCons.Builder
import SCons.CacheDir
import SCons.Debug
from SCons.Debug import logInstanceCreation
import SCons.Defaults
Expand Down Expand Up @@ -1222,6 +1223,12 @@ def __init__(
self._init_special()
self.added_methods = []

# If user specifies a --cache-dir on the command line, then
# use that for all created Environments, user can alter this
# by specifying CacheDir() per environment.
if SCons.CacheDir.cli_cache_dir:
self.CacheDir(SCons.CacheDir.cli_cache_dir)

# We don't use AddMethod, or define these as methods in this
# class, because we *don't* want these functions to be bound
# methods. They need to operate independently so that the
Expand Down
3 changes: 3 additions & 0 deletions SCons/Script/Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,9 @@ def _main(parser):
SCons.Node.implicit_deps_changed = options.implicit_deps_changed
SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged

if options.cache_dir:
SCons.CacheDir.cli_cache_dir = options.cache_dir

if options.no_exec:
SCons.SConf.dryrun = 1
SCons.Action.execute_actions = None
Expand Down
Loading
Loading