diff --git a/CHANGES.txt b/CHANGES.txt
index 1ac704339e..2964a1cdc4 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -23,6 +23,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
the test fails. The rest is cleanup and type annotations. Be more
careful that the returns from stderr() and stdout(), which *can*
return None, are not used without checking.
+ - Temporary files created by TempFileMunge() are now cleaned up on
+ scons exit, instead of at the time they're used. Fixes #4595.
RELEASE 4.8.1 - Tue, 03 Sep 2024 17:22:20 -0700
diff --git a/RELEASE.txt b/RELEASE.txt
index 196103cfbc..fcc4f46a6f 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -36,6 +36,8 @@ FIXES
if the variable is used on the command line with one of the enabling
string as the value: the variable's default value is produced (previously
it always produced True in this case).
+- Temporary files created by TempFileMunge() are now cleaned up on
+ scons exit, instead of at the time they're used. Fixes #4595.
IMPROVEMENTS
------------
diff --git a/SCons/Platform/Platform.xml b/SCons/Platform/Platform.xml
index 0beafd1fdc..809cd9c431 100644
--- a/SCons/Platform/Platform.xml
+++ b/SCons/Platform/Platform.xml
@@ -298,16 +298,41 @@ The suffix used for shared object file names.
-A callable object used to handle overly long command line strings,
-since operations which call out to a shell will fail
-if the line is longer than the shell can accept.
-This tends to particularly impact linking.
-The tempfile object stores the command line in a temporary
-file in the appropriate format, and returns
-an alternate command line so the invoked tool will make
-use of the contents of the temporary file.
-If you need to replace the default tempfile object,
-the callable should take into account the settings of
+Holds a callable object which will be invoked to transform long
+command lines (string or list) into an alternate form.
+Length limits on various operating systems
+may cause long command lines to fail when calling out to
+a shell to run the command.
+Most often affects linking, when there are many object files and/or
+libraries to be linked, but may also affect other
+compilation steps which have many arguments.
+&cv-TEMPFILE; is not called directly,
+but rather is typically embedded in another
+&consvar;, to be expanded when used. Example:
+
+
+
+env["TEMPFILE"] = TempFileMunge
+env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES', '$LINKCOMSTR')}"
+
+
+
+The SCons default value for &cv-TEMPFILE;,
+TempFileMunge,
+performs command substitution on the passed command line,
+calculates whether modification is needed,
+then puts all but the first word (assumed to be the command name)
+of the resulting list into a temporary file
+(sometimes called a response file or command file),
+and returns a new command line consisting of the
+the command name and an appropriately formatted reference
+to the temporary file.
+
+
+
+A replacement for the default tempfile object would need
+to do fundamentally the same thing, including taking into account
+the values of
&cv-link-MAXLINELENGTH;,
&cv-link-TEMPFILEPREFIX;,
&cv-link-TEMPFILESUFFIX;,
@@ -315,6 +340,11 @@ the callable should take into account the settings of
&cv-link-TEMPFILEDIR;
and
&cv-link-TEMPFILEARGESCFUNC;.
+If a particular use case requires a different transformation
+than the default, it is recommended to copy the mechanism and
+define a new &consvar; and rewrite the relevant *COM
+variable(s) to use it, to avoid possibly disrupting existing uses
+of &cv-TEMPFILE;.
@@ -324,6 +354,8 @@ and
The prefix for the name of the temporary file used
to store command lines exceeding &cv-link-MAXLINELENGTH;.
+The prefix must include the compiler syntax to actually
+include and process the file.
The default prefix is '@', which works for the &MSVC;
and GNU toolchains on Windows.
Set this appropriately for other toolchains,
@@ -338,7 +370,7 @@ or '-via' for ARM toolchain.
The suffix for the name of the temporary file used
to store command lines exceeding &cv-link-MAXLINELENGTH;.
-The suffix should include the dot ('.') if one is wanted as
+The suffix should include the dot ('.') if one is needed as
it will not be added automatically.
The default is .lnk.
@@ -363,6 +395,9 @@ Note this value is used literally and not expanded by the subst logic.
The directory to create the long-lines temporary file in.
+If unset, some suitable default should be chosen.
+The default tempfile object lets the &Python;
+tempfile module choose.
diff --git a/SCons/Platform/PlatformTests.py b/SCons/Platform/PlatformTests.py
index c786aa7d8d..691aed2598 100644
--- a/SCons/Platform/PlatformTests.py
+++ b/SCons/Platform/PlatformTests.py
@@ -207,9 +207,7 @@ def test_MAXLINELENGTH(self) -> None:
assert cmd != defined_cmd, cmd
def test_TEMPFILEARGJOINBYTE(self) -> None:
- """
- Test argument join byte TEMPFILEARGJOINBYTE
- """
+ """Test argument join byte TEMPFILEARGJOINBYTE."""
# Init class with cmd, such that the fully expanded
# string reads "a test command line".
@@ -232,19 +230,24 @@ def test_TEMPFILEARGJOINBYTE(self) -> None:
SCons.Action.print_actions = 0
env['MAXLINELENGTH'] = len(expanded_cmd)-1
cmd = t(None, None, env, 0)
- # print("CMD is:%s"%cmd)
+ # print(f"[CMD is: {cmd}]")
- with open(cmd[-1],'rb') as f:
+ if cmd[-1].startswith('@'):
+ tempfile = cmd[-1][1:]
+ else:
+ tempfile = cmd[-1]
+ with open(tempfile, 'rb') as f:
file_content = f.read()
- # print("Content is:[%s]"%file_content)
+ # print(f"[Content of {tempfile} is:{file_content}]")
# ...and restoring its setting.
SCons.Action.print_actions = old_actions
- assert file_content != env['TEMPFILEARGJOINBYTE'].join(['test','command','line'])
+ assert file_content != bytearray(
+ env['TEMPFILEARGJOINBYTE'].join(['test', 'command', 'line']),
+ encoding='utf-8',
+ )
def test_TEMPFILEARGESCFUNC(self) -> None:
- """
- Test a custom TEMPFILEARGESCFUNC
- """
+ """Test a custom TEMPFILEARGESCFUNC."""
def _tempfile_arg_esc_func(arg):
return str(arg).replace("line", "newarg")
@@ -262,12 +265,16 @@ def _tempfile_arg_esc_func(arg):
SCons.Action.print_actions = 0
env['TEMPFILEARGESCFUNC'] = _tempfile_arg_esc_func
cmd = t(None, None, env, 0)
- # print("CMD is: %s"%cmd)
+ # print(f"[CMD is: {cmd}]")
- with open(cmd[-1], 'rb') as f:
+ if cmd[-1].startswith('@'):
+ tempfile = cmd[-1][1:]
+ else:
+ tempfile = cmd[-1]
+ with open(tempfile, 'rb') as f:
file_content = f.read()
- # print("Content is:[%s]"%file_content)
- # # ...and restoring its setting.
+ # print(f"[Content of {tempfile} is:{file_content}]")
+ # ...and restoring its setting.
SCons.Action.print_actions = old_actions
assert b"newarg" in file_content
diff --git a/SCons/Platform/__init__.py b/SCons/Platform/__init__.py
index 77eea5c099..aec1a8989a 100644
--- a/SCons/Platform/__init__.py
+++ b/SCons/Platform/__init__.py
@@ -42,6 +42,7 @@
import SCons.compat
+import atexit
import importlib
import os
import sys
@@ -150,7 +151,7 @@ class TempFileMunge:
the length of command lines. Example::
env["TEMPFILE"] = TempFileMunge
- env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES','$LINKCOMSTR')}"
+ env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES', '$LINKCOMSTR')}"
By default, the name of the temporary file used begins with a
prefix of '@'. This may be configured for other tool chains by
@@ -258,31 +259,27 @@ def __call__(self, target, source, env, for_signature):
fd, tmp = tempfile.mkstemp(suffix, dir=tempfile_dir, text=True)
native_tmp = SCons.Util.get_native_path(tmp)
+ # arrange for cleanup on exit:
+
+ def tmpfile_cleanup(file) -> None:
+ os.remove(file)
+
+ atexit.register(tmpfile_cleanup, tmp)
+
if env.get('SHELL', None) == 'sh':
# The sh shell will try to escape the backslashes in the
# path, so unescape them.
native_tmp = native_tmp.replace('\\', r'\\\\')
- # In Cygwin, we want to use rm to delete the temporary
- # file, because del does not exist in the sh shell.
- rm = env.Detect('rm') or 'del'
- else:
- # Don't use 'rm' if the shell is not sh, because rm won't
- # work with the Windows shells (cmd.exe or command.com) or
- # Windows path names.
- rm = 'del'
if 'TEMPFILEPREFIX' in env:
prefix = env.subst('$TEMPFILEPREFIX')
else:
- prefix = '@'
+ prefix = "@"
tempfile_esc_func = env.get('TEMPFILEARGESCFUNC', SCons.Subst.quote_spaces)
- args = [
- tempfile_esc_func(arg)
- for arg in cmd[1:]
- ]
+ args = [tempfile_esc_func(arg) for arg in cmd[1:]]
join_char = env.get('TEMPFILEARGJOIN', ' ')
- os.write(fd, bytearray(join_char.join(args) + "\n", 'utf-8'))
+ os.write(fd, bytearray(join_char.join(args) + "\n", encoding="utf-8"))
os.close(fd)
# XXX Using the SCons.Action.print_actions value directly
@@ -301,15 +298,20 @@ def __call__(self, target, source, env, for_signature):
# purity get in the way of just being helpful, so we'll
# reach into SCons.Action directly.
if SCons.Action.print_actions:
- cmdstr = env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target,
- source) if self.cmdstr is not None else ''
+ cmdstr = (
+ env.subst(self.cmdstr, SCons.Subst.SUBST_RAW, target, source)
+ if self.cmdstr is not None
+ else ''
+ )
# Print our message only if XXXCOMSTR returns an empty string
- if len(cmdstr) == 0 :
- cmdstr = ("Using tempfile "+native_tmp+" for command line:\n"+
- str(cmd[0]) + " " + " ".join(args))
+ if not cmdstr:
+ cmdstr = (
+ f"Using tempfile {native_tmp} for command line:\n"
+ f'{cmd[0]} {" ".join(args)}'
+ )
self._print_cmd_str(target, source, env, cmdstr)
- cmdlist = [cmd[0], prefix + native_tmp + '\n' + rm, native_tmp]
+ cmdlist = [cmd[0], prefix + native_tmp]
# Store the temporary file command list into the target Node.attributes
# to avoid creating two temporary files one for print and one for execute.
diff --git a/test/TEX/glossaries.py b/test/TEX/glossaries.py
index 069fcc3769..d418ee266e 100644
--- a/test/TEX/glossaries.py
+++ b/test/TEX/glossaries.py
@@ -23,7 +23,7 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""
+r"""
Validate that use of \glossaries in TeX source files causes SCons to
be aware of the necessary created glossary files.
diff --git a/test/TEX/subdir_variantdir_include2.py b/test/TEX/subdir_variantdir_include2.py
index 953a2293e1..2b615f194d 100644
--- a/test/TEX/subdir_variantdir_include2.py
+++ b/test/TEX/subdir_variantdir_include2.py
@@ -23,7 +23,7 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-"""
+r"""
Verify that we execute TeX in a subdirectory (if that's where the document
resides) by checking that all the auxiliary files get created there and
not in the top-level directory. Test this when variantDir is used