diff --git a/CHANGES.txt b/CHANGES.txt
index 126651ab8e..4967daf058 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -134,6 +134,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- More tweaking of test framework overview (which is duplicated onto
the website, but not in the regular documentation section).
- Extend range of recognized Java versions to 20.
+ - Finish the change to make calling SConscript() with a nonexistent
+ file an error. It has issued a warning since 3.0, with "warn instead
+ of fail" deprecated since 3.1. Fixes #3958.
From Jonathon Reinhart:
- Fix another instance of `int main()` in CheckLib() causing failures
diff --git a/README.rst b/README.rst
index 6cc89bdb88..f779b0b1c9 100755
--- a/README.rst
+++ b/README.rst
@@ -167,7 +167,7 @@ messages during installation like this::
Consider adding this directory to PATH or, if you prefer to suppress this warning,
use --no-warn-script-location.
-If you are running on a system which uses a package manager
+If you are running on a system which uses a package manager
(for example most Linux distributions), you may, at your option,
use the package manager (e.g. ``apt``, ``dnf``, ``yum``,
``zypper``, ``brew``, ``pacman`` etc.) to install a version
diff --git a/RELEASE.txt b/RELEASE.txt
index 42199c1596..d045dc2640 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -60,6 +60,12 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY
architecture combination. For example, when using VS2022 on arm64, the arm64 native
tools are only installed for the 14.3x toolsets.
- Extend range of recognized Java versions to 20.
+- Calling SConscript() with a nonexistent file is now an error.
+ Previously this succeeded - prior to SCons 3.0, silently; since 3.0, with
+ a warning. Developers can still instruct such an SConscript() call not
+ to fail by being explicit: pass keyword argument "must_exist=False".
+ The "--warn=missing-sconscript" commandline option is no longer available
+ as the warning was part of the transitional phase.
- Add missing directories to searched paths for mingw installs
FIXES
diff --git a/SCons/Script/SConscript.py b/SCons/Script/SConscript.py
index c0b556c682..2a36a6fe16 100644
--- a/SCons/Script/SConscript.py
+++ b/SCons/Script/SConscript.py
@@ -145,40 +145,32 @@ def Return(*vars, **kw):
stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
-def handle_missing_SConscript(f, must_exist=None):
+def handle_missing_SConscript(f: str, must_exist: bool = True) -> None:
"""Take appropriate action on missing file in SConscript() call.
Print a warning or raise an exception on missing file, unless
- missing is explicitly allowed by the *must_exist* value.
- On first warning, print a deprecation message.
+ missing is explicitly allowed by the *must_exist* parameter or by
+ a global flag.
Args:
- f (str): path of missing configuration file
- must_exist (bool): if true, fail. If false, but not ``None``,
- allow the file to be missing. The default is ``None``,
- which means issue the warning. The default is deprecated.
+ f: path to missing configuration file
+ must_exist: if true (the default), fail. If false
+ do nothing, allowing a build to declare it's okay to be missing.
Raises:
- UserError: if *must_exist* is true or if global
+ UserError: if *must_exist* is true or if global
:data:`SCons.Script._no_missing_sconscript` is true.
+
+ .. versionchanged: 4.6.0
+ Changed default from False.
"""
+ if not must_exist: # explicitly set False: ok
+ return
+ if not SCons.Script._no_missing_sconscript: # system default changed: ok
+ return
+ msg = f"missing SConscript file {f.get_internal_path()!r}"
+ raise SCons.Errors.UserError(msg)
- if must_exist or (SCons.Script._no_missing_sconscript and must_exist is not False):
- msg = "Fatal: missing SConscript '%s'" % f.get_internal_path()
- raise SCons.Errors.UserError(msg)
-
- if must_exist is None:
- if SCons.Script._warn_missing_sconscript_deprecated:
- msg = (
- "Calling missing SConscript without error is deprecated.\n"
- "Transition by adding must_exist=False to SConscript calls.\n"
- "Missing SConscript '%s'" % f.get_internal_path()
- )
- SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, msg)
- SCons.Script._warn_missing_sconscript_deprecated = False
- else:
- msg = "Ignoring missing SConscript '%s'" % f.get_internal_path()
- SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, msg)
def _SConscript(fs, *files, **kw):
top = fs.Top
@@ -294,7 +286,7 @@ def _SConscript(fs, *files, **kw):
call_stack[-1].globals.update({__file__:old_file})
else:
- handle_missing_SConscript(f, kw.get('must_exist', None))
+ handle_missing_SConscript(f, kw.get('must_exist', True))
finally:
SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
diff --git a/SCons/Script/SConscript.xml b/SCons/Script/SConscript.xml
index 7a4bc2916d..e5d9a72f93 100644
--- a/SCons/Script/SConscript.xml
+++ b/SCons/Script/SConscript.xml
@@ -538,17 +538,20 @@ TODO??? SConscript('build/SConscript', src_dir='src')
If the optional
must_exist
-is True,
+is True (the default),
causes an exception to be raised if a requested
-SConscript file is not found. The current default is
-False,
-causing only a warning to be emitted, but this default is deprecated
-(since 3.1).
-For scripts which truly intend to be optional, transition to
-explicitly supplying
+SConscript file is not found.
+To allow missing scripts to be silently ignored
+(the default behavior prior to &SCons; version 3.1),
+pass
must_exist=False to the &f-SConscript; call.
+
+Changed in 4.6.0: must_exist
+now defaults to True.
+
+
Here are some composite examples:
diff --git a/SCons/Script/__init__.py b/SCons/Script/__init__.py
index e398ecf1b2..324981748c 100644
--- a/SCons/Script/__init__.py
+++ b/SCons/Script/__init__.py
@@ -269,10 +269,10 @@ def HelpFunction(text, append: bool=False) -> None:
# Will be non-zero if we are reading an SConscript file.
sconscript_reading = 0
-_no_missing_sconscript = False
-_warn_missing_sconscript_deprecated = True
+_no_missing_sconscript = True
+_warn_missing_sconscript_deprecated = False # TODO: now unused
-def set_missing_sconscript_error(flag: int=1):
+def set_missing_sconscript_error(flag: bool = True) -> bool:
"""Set behavior on missing file in SConscript() call.
Returns:
diff --git a/SCons/Warnings.py b/SCons/Warnings.py
index 5c2a0db620..f6809fb9b5 100644
--- a/SCons/Warnings.py
+++ b/SCons/Warnings.py
@@ -70,6 +70,7 @@ class LinkWarning(WarningOnByDefault):
class MisleadingKeywordsWarning(WarningOnByDefault):
pass
+# TODO: no longer needed, now an error instead of warning. Leave for a bit.
class MissingSConscriptWarning(WarningOnByDefault):
pass
diff --git a/doc/man/scons.xml b/doc/man/scons.xml
index 2d68b48ac2..6a888cbc9c 100644
--- a/doc/man/scons.xml
+++ b/doc/man/scons.xml
@@ -2272,13 +2272,13 @@ of this tool module.
-
+
no-object-count
diff --git a/test/SConscript/must_exist_deprecation.py b/test/Removed/Old/SConscript-must_exist_deprecation.py
similarity index 100%
rename from test/SConscript/must_exist_deprecation.py
rename to test/Removed/Old/SConscript-must_exist_deprecation.py
diff --git a/test/option/warn-missing-sconscript.py b/test/Removed/Old/warn-missing-sconscript.py
similarity index 88%
rename from test/option/warn-missing-sconscript.py
rename to test/Removed/Old/warn-missing-sconscript.py
index 5413229be0..7859b6423e 100644
--- a/test/option/warn-missing-sconscript.py
+++ b/test/Removed/Old/warn-missing-sconscript.py
@@ -61,14 +61,10 @@ def build(target, source, env):
#scons: warning: Ignoring missing SConscript 'no_such_file'
"" + TestSCons.file_expr
-test.run(arguments = '--warn=missing-sconscript .', stderr = expect)
-
-test.run(arguments = '--warn=no-missing-sconscript .', stderr = "")
-
-test.run(arguments = 'WARN=missing-sconscript .', stderr = expect)
-
-test.run(arguments = 'WARN=no-missing-sconscript .', stderr = "")
-
+test.run(arguments='--warn=missing-sconscript .', stderr=expect)
+test.run(arguments='--warn=no-missing-sconscript .', stderr="")
+test.run(arguments='WARN=missing-sconscript .', stderr=expect)
+test.run(arguments='WARN=no-missing-sconscript .', stderr="")
test.pass_test()
diff --git a/test/SConscript/must_exist.py b/test/SConscript/must_exist.py
index 81e018afcd..90f447ef11 100644
--- a/test/SConscript/must_exist.py
+++ b/test/SConscript/must_exist.py
@@ -37,76 +37,93 @@
# this gives us traceability of the line responsible
SConstruct_path = test.workpath('SConstruct')
test.write(SConstruct_path, """\
-import SCons
-from SCons.Warnings import _warningOut
import sys
+import traceback
+
+import SCons.Script
+from SCons.Errors import UserError
+from SCons.Script.Main import find_deepest_user_frame
DefaultEnvironment(tools=[])
-# 1. 1st default call should succeed with deprecation warning
-try:
- SConscript('missing/SConscript')
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-# 2. 2nd default call should succeed with warning (no depr)
+
+def user_error(e):
+ "Synthesize msg from UserError."
+ # Borrowed from SCons.Script._scons_user_error which we don't use
+ # because it exits - we only want the message.
+ etype, value, tb = sys.exc_info()
+ filename, lineno, routine, _ = find_deepest_user_frame(traceback.extract_tb(tb))
+ sys.stderr.write(f"\\nscons: *** {value}\\n")
+ sys.stderr.write(f'File "{filename}", line {lineno}, in {routine}\\n')
+
+# 1. Call with defaults raises exception
try:
- SConscript('missing/SConscript')
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-# 3. must_exist True call should raise exception
+ SConscript("missing/SConscript")
+except UserError as e:
+ user_error(e)
+
+# 2. Call with must_exist=True raises exception
try:
- SConscript('missing/SConscript', must_exist=True)
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-# 4. must_exist False call should succeed silently
+ SConscript("missing/SConscript", must_exist=True)
+except UserError as e:
+ user_error(e)
+
+# 3. Call with must_exist=False call should succeed silently
try:
- SConscript('missing/SConscript', must_exist=False)
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-# 5. with system setting changed, should raise exception
-SCons.Script.set_missing_sconscript_error()
+ SConscript("missing/SConscript", must_exist=False)
+except UserError as e:
+ user_error(e)
+
+# 4. with system setting changed, should succeed silently
+SCons.Script.set_missing_sconscript_error(flag=False)
try:
- SConscript('missing/SConscript')
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-# 6. must_exist=False overrides system setting, should emit warning
+ SConscript("missing/SConscript")
+except UserError as e:
+ user_error(e)
+
+# 5. must_exist=True "wins" over system setting
try:
- SConscript('missing/SConscript', must_exist=False)
-except SCons.Errors.UserError as e:
- if _warningOut:
- _warningOut(e)
-""")
-
-# we should see two exceptions as "Fatal" and
-# and see four warnings, the first having the depr message
-# need to build the path in the expected msg in an OS-agnostic way
-missing = os.path.normpath('missing/SConscript')
-warn1 = """
-scons: warning: Calling missing SConscript without error is deprecated.
-Transition by adding must_exist=False to SConscript calls.
-Missing SConscript '{}'
-""".format(missing) + test.python_file_line(SConstruct_path, 8)
-
-warn2 = """
-scons: warning: Ignoring missing SConscript '{}'
-""".format(missing) + test.python_file_line(SConstruct_path, 14)
-
-err1 = """
-scons: warning: Fatal: missing SConscript '{}'
-""".format(missing) + test.python_file_line(SConstruct_path, 23)
-
-err2 = """
-scons: warning: Fatal: missing SConscript '{}'
-""".format(missing) + test.python_file_line(SConstruct_path, 36)
+ SConscript("missing/SConscript", must_exist=True)
+except UserError as e:
+ user_error(e)
+""",
+)
+
+missing = os.path.join("missing", "SConscript")
+err1 = f"""
+scons: *** missing SConscript file {missing!r}
+""" + test.python_file_line(
+ SConstruct_path, 21
+)
+
+err2 = f"""
+scons: *** missing SConscript file {missing!r}
+""" + test.python_file_line(
+ SConstruct_path, 27
+)
+
+err3 = f"""
+scons: *** missing SConscript file {missing!r}
+""" + test.python_file_line(
+ SConstruct_path, 33
+)
+
+err4 = f"""
+scons: *** missing SConscript file {missing!r}
+""" + test.python_file_line(
+ SConstruct_path, 40
+)
+
+err5 = f"""
+scons: *** missing SConscript file {missing!r}
+""" + test.python_file_line(
+ SConstruct_path, 46
+)
nowarn = ""
-expect_stderr = warn1 + warn2 + err1 + nowarn + err2 + nowarn
-test.run(arguments = ".", stderr = expect_stderr)
+# of the five tests, we actually expect fails from 1 and 2
+expect_stderr = err1 + err2 + nowarn + nowarn + nowarn
+test.run(arguments=".", stderr=expect_stderr)
test.pass_test()
# Local Variables:
diff --git a/test/option/option-f.py b/test/option/option-f.py
index e1343a6c2e..a2c8561adf 100644
--- a/test/option/option-f.py
+++ b/test/option/option-f.py
@@ -34,21 +34,24 @@
subdir_BuildThis = os.path.join('subdir', 'Buildthis')
test.write('SConscript', """
-DefaultEnvironment(tools=[])
import os
+
+DefaultEnvironment(tools=[])
print("SConscript " + os.getcwd())
""")
test.write(subdir_BuildThis, """
-DefaultEnvironment(tools=[])
import os
-print("subdir/BuildThis "+ os.getcwd())
+
+DefaultEnvironment(tools=[])
+print("subdir/BuildThis " + os.getcwd())
""")
test.write('Build2', """
-DefaultEnvironment(tools=[])
import os
-print("Build2 "+ os.getcwd())
+
+DefaultEnvironment(tools=[])
+print("Build2 " + os.getcwd())
""")
wpath = test.workpath()
@@ -56,14 +59,15 @@
test.run(
arguments='-f SConscript .',
stdout=test.wrap_stdout(
- read_str='SConscript %s\n' % wpath, build_str="scons: `.' is up to date.\n"
+ read_str=f'SConscript {wpath}\n',
+ build_str="scons: `.' is up to date.\n"
),
)
test.run(
- arguments='-f %s .' % subdir_BuildThis,
+ arguments=f'-f {subdir_BuildThis} .',
stdout=test.wrap_stdout(
- read_str='subdir/BuildThis %s\n' % wpath,
+ read_str=f'subdir/BuildThis {wpath}\n',
build_str="scons: `.' is up to date.\n",
),
)
@@ -71,14 +75,15 @@
test.run(
arguments='--file=SConscript .',
stdout=test.wrap_stdout(
- read_str='SConscript %s\n' % wpath, build_str="scons: `.' is up to date.\n"
+ read_str=f'SConscript {wpath}\n',
+ build_str="scons: `.' is up to date.\n"
),
)
test.run(
- arguments='--file=%s .' % subdir_BuildThis,
+ arguments=f'--file={subdir_BuildThis} .',
stdout=test.wrap_stdout(
- read_str='subdir/BuildThis %s\n' % wpath,
+ read_str=f'subdir/BuildThis {wpath}\n',
build_str="scons: `.' is up to date.\n",
),
)
@@ -86,14 +91,15 @@
test.run(
arguments='--makefile=SConscript .',
stdout=test.wrap_stdout(
- read_str='SConscript %s\n' % wpath, build_str="scons: `.' is up to date.\n"
+ read_str=f'SConscript {wpath}\n',
+ build_str="scons: `.' is up to date.\n"
),
)
test.run(
- arguments='--makefile=%s .' % subdir_BuildThis,
+ arguments=f'--makefile={subdir_BuildThis} .',
stdout=test.wrap_stdout(
- read_str='subdir/BuildThis %s\n' % wpath,
+ read_str=f'subdir/BuildThis {wpath}\n',
build_str="scons: `.' is up to date.\n",
),
)
@@ -101,14 +107,15 @@
test.run(
arguments='--sconstruct=SConscript .',
stdout=test.wrap_stdout(
- read_str='SConscript %s\n' % wpath, build_str="scons: `.' is up to date.\n"
+ read_str=f'SConscript {wpath}\n',
+ build_str="scons: `.' is up to date.\n"
),
)
test.run(
- arguments='--sconstruct=%s .' % subdir_BuildThis,
+ arguments=f'--sconstruct={subdir_BuildThis} .',
stdout=test.wrap_stdout(
- read_str='subdir/BuildThis %s\n' % wpath,
+ read_str=f'subdir/BuildThis {wpath}\n',
build_str="scons: `.' is up to date.\n",
),
)
@@ -121,28 +128,21 @@
print("STDIN " + os.getcwd())
""",
stdout=test.wrap_stdout(
- read_str='STDIN %s\n' % wpath, build_str="scons: `.' is up to date.\n"
+ read_str=f'STDIN {wpath}\n',
+ build_str="scons: `.' is up to date.\n"
),
)
expect = test.wrap_stdout(
- read_str='Build2 %s\nSConscript %s\n' % (wpath, wpath),
+ read_str=f'Build2 {wpath}\nSConscript {wpath}\n',
build_str="scons: `.' is up to date.\n",
)
test.run(arguments='-f Build2 -f SConscript .', stdout=expect)
-test.run(
- arguments='-f no_such_file .',
- stdout=test.wrap_stdout("scons: `.' is up to date.\n"),
- stderr=None,
-)
-
-expect = """
-scons: warning: Calling missing SConscript without error is deprecated.
-Transition by adding must_exist=False to SConscript calls.
-Missing SConscript 'no_such_file'"""
-stderr = test.stderr()
-test.must_contain_all(test.stderr(), expect)
+missing = "no_such_file"
+test.run(arguments=f"-f {missing} .", status=2, stderr=None)
+expect = [f"scons: *** missing SConscript file {missing!r}"]
+test.must_contain_all_lines(test.stderr(), expect)
test.pass_test()