diff options
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | RELEASE.txt | 6 | ||||
-rw-r--r-- | SCons/Script/SConsOptions.py | 6 | ||||
-rw-r--r-- | SCons/Warnings.py | 230 | ||||
-rw-r--r-- | SCons/WarningsTests.py | 116 | ||||
-rw-r--r-- | doc/man/scons.xml | 27 | ||||
-rw-r--r-- | doc/sphinx/SCons.rst | 2 |
7 files changed, 243 insertions, 149 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index a07fcbe..f1e9117 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -99,6 +99,11 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER describe the Microsoft C++ compiler. Update the version table slightly. Amplified the usage of MSVC_VERSION. - Improve SharedLibrary docs a bit. + - Update warnings module: adds docstrings, drop three unused warnings + (DeprecatedSourceCodeWarning, TaskmasterNeedsExecuteWarning, + DeprecatedMissingSConscriptWarning) add two warnings to manpage + (cache-cleanup-error, future-reserved-variable), improve unittest, tweak + Sphinx build. RELEASE 4.6.0 - Sun, 19 Nov 2023 17:22:20 -0700 diff --git a/RELEASE.txt b/RELEASE.txt index 0ca6aa1..b339c44 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -34,6 +34,10 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY prototype when including a header file. Fixes GH Issue #4320 - Now supports pre-release Python 3.13 - Support for Python versions without support for the `threading` package has been removed +- Dropped three unused warning classes: DeprecatedSourceCodeWarning, + TaskmasterNeedsExecuteWarning, DeprecatedMissingSConscriptWarning. +* Two warning classes that are actually used were added to manpage section on + enabling warnings (cache-cleanup-error, future-reserved-variable). FIXES ----- @@ -90,6 +94,8 @@ DOCUMENTATION the Scanner Objects section of the manpage. - The manpage entry for Pseudo was clarified. - The manpage entry for SharedLibrary was clarified. +- Update API docs for Warnings framework; add two warns to manpage + enable/disable control. DEVELOPMENT ----------- diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py index 18fe0e4..e8e5cbf 100644 --- a/SCons/Script/SConsOptions.py +++ b/SCons/Script/SConsOptions.py @@ -875,9 +875,9 @@ def Parser(version): def warn_md5_chunksize_deprecated(option, opt, value, parser) -> None: if opt == '--md5-chunksize': - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Parameter %s is deprecated. Use " - "--hash-chunksize instead." % opt) + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, + f"Option {opt} is deprecated. " + "Use --hash-chunksize instead.") setattr(parser.values, option.dest, value) diff --git a/SCons/Warnings.py b/SCons/Warnings.py index f6809fb..d604659 100644 --- a/SCons/Warnings.py +++ b/SCons/Warnings.py @@ -21,159 +21,211 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -"""The SCons warnings framework.""" +"""The SCons Warnings framework. + +Enables issuing warnings in situations where it is useful to alert +the user of a condition that does not warrant raising an exception +that could terminate the program. + +A new warning class should inherit (perhaps indirectly) from one of +two base classes: :exc:`SConsWarning` or :exc:`WarningOnByDefault`, +which are the same except warnings derived from the latter will start +out in an enabled state. Enabled warnings cause a message to be +printed when called, disabled warnings are silent. + +There is also a hierarchy for indicating deprecations and future +changes: for these, derive from :exc:`DeprecatedWarning`, +:exc:`MandatoryDeprecatedWarning`, :exc:`FutureDeprecatedWarning` +or :exc:`FutureReservedVariableWarning`. + +Whether or not to display warnings, beyond those that are on by +default, is controlled through the command line (``--warn``) or +through ``SetOption('warn')``. The names used there use a different +naming style than the warning class names. :func:`process_warn_strings` +converts the names before enabling/disabling. + +The behavior of issuing only a message (for "enabled" warnings) can +be toggled to raising an exception instead by calling the +:func:`warningAsException` function. + +For new/removed warnings, the manpage needs to be kept in sync. +Any warning class defined here is accepted, but we don't want to make +people have to dig around to find the names. Warnings do not have to +be defined in this file, though it is preferred: those defined elsewhere +cannot use the enable/disable functionality unless they monkeypatch the +warning into this module's namespace. + +You issue a warning, either in SCons code or in a build project's +SConscripts, by calling the :func:`warn` function defined in this module. +Raising directly with an instance of a warning class bypasses the +framework and it will behave like an ordinary exception. +""" import sys +from typing import Callable, Sequence, Optional import SCons.Errors +# _enabled is a list of 2-tuples with a warning class object and a +# boolean (True if that warning is enabled). Initialized in SCons/Main.py. +_enabled = [] + +# If False, just emit the msg for an enabled warning; else raise exception +_warningAsException: bool = False + +# Function to emit the warning. Initialized by SCons/Main.py for regular use; +# the unit test will set to a capturing version for testing. +_warningOut: Optional[Callable] = None + + class SConsWarning(SCons.Errors.UserError): - pass + """Base class for all SCons warnings.""" class WarningOnByDefault(SConsWarning): - pass + """Base class for SCons warnings that are enabled by default.""" +SConsWarningOnByDefault = WarningOnByDefault # transition to new name -# NOTE: If you add a new warning class, add it to the man page, too! -# Not all warnings are defined here, some are defined in the location of use -class TargetNotBuiltWarning(SConsWarning): # Should go to OnByDefault - pass +class LinkWarning(WarningOnByDefault): + """Base class for linker warnings.""" + +# NOTE: If you add a new warning class here, add it to the man page, too! + +# General warnings class CacheVersionWarning(WarningOnByDefault): - pass + """The derived-file cache directory has an out of date config.""" class CacheWriteErrorWarning(SConsWarning): - pass + """Problems writing a derived file to the cache.""" class CacheCleanupErrorWarning(SConsWarning): - pass + """Problems removing retrieved target prior to rebuilding.""" class CorruptSConsignWarning(WarningOnByDefault): - pass + """Problems decoding the contents of the sconsign database.""" class DependencyWarning(SConsWarning): - pass + """A scanner identified a dependency but did not add it.""" class DevelopmentVersionWarning(WarningOnByDefault): - pass + """Use of a deprecated feature.""" class DuplicateEnvironmentWarning(WarningOnByDefault): - pass + """A target appears in more than one consenv with identical actions. -class FutureReservedVariableWarning(WarningOnByDefault): - pass + A duplicate target with different rules cannot be built; + with the same rule it can, but this could indicate a problem in + the build configuration. + """ -class LinkWarning(WarningOnByDefault): - pass +class FortranCxxMixWarning(LinkWarning): + """Fortran and C++ objects appear together in a link line. + + Some compilers support this, others do not. + """ + +class FutureReservedVariableWarning(WarningOnByDefault): + """Setting a variable marked to become reserved in a future release.""" class MisleadingKeywordsWarning(WarningOnByDefault): - pass + """Use of possibly misspelled kwargs in Builder calls.""" -# TODO: no longer needed, now an error instead of warning. Leave for a bit. class MissingSConscriptWarning(WarningOnByDefault): - pass + """The script specified in an SConscript() call was not found. + + TODO: this is now an error, so no need for a warning. Left in for + a while in case anyone is using, remove eventually. + + Manpage entry removed in 4.6.0. + """ class NoObjectCountWarning(WarningOnByDefault): - pass + """Object counting (debug mode) could not be enabled.""" class NoParallelSupportWarning(WarningOnByDefault): - pass + """Fell back to single-threaded build, as no thread support found.""" class ReservedVariableWarning(WarningOnByDefault): - pass + """Attempt to set reserved construction variable names.""" class StackSizeWarning(WarningOnByDefault): - pass + """Requested thread stack size could not be set.""" + +class TargetNotBuiltWarning(SConsWarning): # TODO: should go to OnByDefault + """A target build indicated success but the file is not found.""" class VisualCMissingWarning(WarningOnByDefault): - pass + """Requested MSVC version not found and policy is to not fail.""" -# Used when MSVC_VERSION and MSVS_VERSION do not point to the -# same version (MSVS_VERSION is deprecated) class VisualVersionMismatch(WarningOnByDefault): - pass + """``MSVC_VERSION`` and ``MSVS_VERSION`` do not match. -class VisualStudioMissingWarning(SConsWarning): - pass + Note ``MSVS_VERSION`` is deprecated, use ``MSVC_VERSION``. + """ -class FortranCxxMixWarning(LinkWarning): +class VisualStudioMissingWarning(SConsWarning): # TODO: unused pass # Deprecation warnings class FutureDeprecatedWarning(SConsWarning): - pass + """Base class for features that will become deprecated in a future release.""" class DeprecatedWarning(SConsWarning): - pass + """Base class for deprecated features, will be removed in future.""" class MandatoryDeprecatedWarning(DeprecatedWarning): - pass + """Base class for deprecated features where warning cannot be disabled.""" # Special case; base always stays DeprecatedWarning class PythonVersionWarning(DeprecatedWarning): - pass - -class DeprecatedSourceCodeWarning(FutureDeprecatedWarning): - pass - -class TaskmasterNeedsExecuteWarning(DeprecatedWarning): - pass + """SCons was run with a deprecated Python version.""" class DeprecatedOptionsWarning(MandatoryDeprecatedWarning): - pass + """Options that are deprecated.""" class DeprecatedDebugOptionsWarning(MandatoryDeprecatedWarning): - pass + """Option-arguments to --debug that are deprecated.""" -class DeprecatedMissingSConscriptWarning(DeprecatedWarning): +class ToolQtDeprecatedWarning(DeprecatedWarning): # TODO: unused pass -class ToolQtDeprecatedWarning(DeprecatedWarning): - pass - -# The below is a list of 2-tuples. The first element is a class object. -# The second element is true if that class is enabled, false if it is disabled. -_enabled = [] - -# If set, raise the warning as an exception -_warningAsException = False - -# If not None, a function to call with the warning -_warningOut = None def suppressWarningClass(clazz) -> None: - """Suppresses all warnings of type clazz or derived from clazz.""" + """Suppresses all warnings of type *clazz* or derived from *clazz*.""" _enabled.insert(0, (clazz, False)) def enableWarningClass(clazz) -> None: - """Enables all warnings of type clazz or derived from clazz.""" + """Enables all warnings of type *clazz* or derived from *clazz*.""" _enabled.insert(0, (clazz, True)) -def warningAsException(flag: bool=True): - """Set global _warningAsExeption flag. +def warningAsException(flag: bool = True) -> bool: + """Sets global :data:`_warningAsExeption` flag. + + If true, any enabled warning will cause an exception to be raised. Args: - flag: value to set warnings-as-exceptions to [default: True] + flag: new value for warnings-as-exceptions. Returns: The previous value. """ - global _warningAsException + global _warningAsException # pylint: disable=global-statement old = _warningAsException _warningAsException = flag return old -def warn(clazz, *args): +def warn(clazz, *args) -> None: """Issue a warning, accounting for SCons rules. - Check if warnings for this class are enabled. - If warnings are treated as exceptions, raise exception. - Use the global warning-emitter _warningOut, which allows selecting - different ways of presenting a traceback (see Script/Main.py) + Check if warnings for this class are enabled. If warnings are treated + as exceptions, raise exception. Use the global warning emitter + :data:`_warningOut`, which allows selecting different ways of + presenting a traceback (see Script/Main.py). """ warning = clazz(args) for cls, flag in _enabled: @@ -182,30 +234,32 @@ def warn(clazz, *args): if _warningAsException: raise warning - if _warningOut: + if _warningOut is not None: _warningOut(warning) break -def process_warn_strings(arguments) -> None: +def process_warn_strings(arguments: Sequence[str]) -> None: """Process requests to enable/disable warnings. - The requests are strings passed to the --warn option or the - SetOption('warn') function. + The requests come from the option-argument string passed to the + ``--warn`` command line option or as the value passed to the + ``SetOption`` function with a first argument of ``warn``; - An argument to this option should be of the form "warning-class" - or "no-warning-class". The warning class is munged and has - the suffix "Warning" added in order to get an actual class name - from the classes above, which we need to pass to the - {enable,disable}WarningClass() functions. - For example, "deprecated" will enable the DeprecatedWarning class. - "no-dependency" will disable the DependencyWarning class. + The arguments are expected to be as documented in the SCons manual + page for the ``--warn`` option, in the style ``some-type``, + which is converted here to a camel-case name like ``SomeTypeWarning``, + to try to match the warning classes defined here, which are then + passed to :func:`enableWarningClass` or :func:`suppressWarningClass`. - As a special case, --warn=all and --warn=no-all will enable or - disable (respectively) the base class of all SCons warnings. - """ + For example, a string``"deprecated"`` enables the + :exc:`DeprecatedWarning` class, while a string``"no-dependency"`` + disables the :exc:`DependencyWarning` class. - def _classmunge(s): + As a special case, the string ``"all"`` disables all warnings and + a the string ``"no-all"`` disables all warnings. + """ + def _classmunge(s: str) -> str: """Convert a warning argument to SConsCase. The result is CamelCase, except "Scons" is changed to "SCons" @@ -215,7 +269,10 @@ def process_warn_strings(arguments) -> None: for arg in arguments: enable = True - if arg.startswith("no-"): + if arg.startswith("no-") and arg not in ( + "no-object-count", + "no-parallel-support", + ): enable = False arg = arg[len("no-") :] if arg == 'all': @@ -225,13 +282,12 @@ def process_warn_strings(arguments) -> None: try: clazz = globals()[class_name] except KeyError: - sys.stderr.write("No warning type: '%s'\n" % arg) + sys.stderr.write(f"No warning type: {arg!r}\n") else: if enable: enableWarningClass(clazz) elif issubclass(clazz, MandatoryDeprecatedWarning): - fmt = "Can not disable mandataory warning: '%s'\n" - sys.stderr.write(fmt % arg) + sys.stderr.write(f"Can not disable mandataory warning: {arg!r}\n") else: suppressWarningClass(clazz) diff --git a/SCons/WarningsTests.py b/SCons/WarningsTests.py index 85dbb6e..70b4598 100644 --- a/SCons/WarningsTests.py +++ b/SCons/WarningsTests.py @@ -21,11 +21,28 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +"""Test Warnings module.""" + import unittest import SCons.Warnings +from SCons.Warnings import ( + DependencyWarning, + DeprecatedWarning, + MandatoryDeprecatedWarning, + SConsWarning, + WarningOnByDefault, +) + class TestOutput: + """Callable class to use as ``_warningOut`` for capturing test output. + + If we've already been called can reset by ``instance.out = None`` + """ + def __init__(self) -> None: + self.out = None + def __call__(self, x) -> None: args = x.args[0] if len(args) == 1: @@ -33,92 +50,73 @@ class TestOutput: self.out = str(args) class WarningsTestCase(unittest.TestCase): + def setUp(self) -> None: + # Setup global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = False + to = TestOutput() + SCons.Warnings._warningOut = to + def test_Warning(self) -> None: """Test warn function.""" + to = SCons.Warnings._warningOut - # Reset global state - SCons.Warnings._enabled = [] - SCons.Warnings._warningAsException = 0 - - to = TestOutput() - SCons.Warnings._warningOut=to - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Foo") + SCons.Warnings.enableWarningClass(SConsWarning) + SCons.Warnings.warn(DeprecatedWarning, "Foo") assert to.out == "Foo", to.out - SCons.Warnings.warn(SCons.Warnings.DependencyWarning, - "Foo", 1) + SCons.Warnings.warn(DependencyWarning, "Foo", 1) assert to.out == "('Foo', 1)", to.out def test_WarningAsExc(self) -> None: """Test warnings as exceptions.""" - - # Reset global state - SCons.Warnings._enabled = [] - SCons.Warnings._warningAsException = 0 - - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) - old = SCons.Warnings.warningAsException() - assert old == 0, old - exc_caught = 0 - try: - SCons.Warnings.warn(SCons.Warnings.SConsWarning, "Foo") - except: - exc_caught = 1 - assert exc_caught == 1 - - old = SCons.Warnings.warningAsException(old) - assert old == 1, old - exc_caught = 0 - try: - SCons.Warnings.warn(SCons.Warnings.SConsWarning, "Foo") - except: - exc_caught = 1 - assert exc_caught == 0 + to = SCons.Warnings._warningOut + + SCons.Warnings.enableWarningClass(WarningOnByDefault) + old = SCons.Warnings.warningAsException(True) + self.assertFalse(old) + # an enabled warning should raise exception + self.assertRaises(SConsWarning, SCons.Warnings.warn, WarningOnByDefault, "Foo") + to.out = None + # a disabled exception should not raise + SCons.Warnings.warn(DeprecatedWarning, "Foo") + assert to.out == None, to.out + + # make sure original behavior can be restored + prev = SCons.Warnings.warningAsException(old) + self.assertTrue(prev) + SCons.Warnings.warn(WarningOnByDefault, "Foo") + assert to.out == "Foo", to.out def test_Disable(self) -> None: """Test disabling/enabling warnings.""" - - # Reset global state - SCons.Warnings._enabled = [] - SCons.Warnings._warningAsException = 0 - - to = TestOutput() - SCons.Warnings._warningOut=to - to.out = None + to = SCons.Warnings._warningOut # No warnings by default - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Foo") + SCons.Warnings.warn(DeprecatedWarning, "Foo") assert to.out is None, to.out - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Foo") + SCons.Warnings.enableWarningClass(SConsWarning) + SCons.Warnings.warn(DeprecatedWarning, "Foo") assert to.out == "Foo", to.out to.out = None - SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning) - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Foo") + SCons.Warnings.suppressWarningClass(DeprecatedWarning) + SCons.Warnings.warn(DeprecatedWarning, "Foo") assert to.out is None, to.out - SCons.Warnings.warn(SCons.Warnings.MandatoryDeprecatedWarning, - "Foo") + SCons.Warnings.warn(MandatoryDeprecatedWarning, "Foo") assert to.out is None, to.out # Dependency warnings should still be enabled though - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) - SCons.Warnings.warn(SCons.Warnings.DependencyWarning, - "Foo") + SCons.Warnings.enableWarningClass(SConsWarning) + SCons.Warnings.warn(DependencyWarning, "Foo") assert to.out == "Foo", to.out # Try reenabling all warnings... - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) + SCons.Warnings.enableWarningClass(SConsWarning) - SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning) - SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, - "Foo") + SCons.Warnings.enableWarningClass(SConsWarning) + SCons.Warnings.warn(DeprecatedWarning, "Foo") assert to.out == "Foo", to.out if __name__ == "__main__": diff --git a/doc/man/scons.xml b/doc/man/scons.xml index 1ef6186..21d1ce3 100644 --- a/doc/man/scons.xml +++ b/doc/man/scons.xml @@ -2174,6 +2174,15 @@ These warnings are disabled by default.</para> </varlistentry> <varlistentry> + <term><emphasis role="bold">cache-cleanup-error</emphasis></term> + <listitem> +<para>Warnings about errors when a file retrieved +from the derived-file cache could not be removed. +</para> + </listitem> + </varlistentry> + + <varlistentry> <term><emphasis role="bold">corrupt-sconsign</emphasis></term> <listitem> <para>Warnings about unfamiliar signature data in @@ -2228,6 +2237,16 @@ which can yield unpredictable behavior with some compilers.</para> </varlistentry> <varlistentry> + <term><emphasis role="bold">future-reserved-variable</emphasis></term> + <listitem> +<para>Warnings about construction variables which +are currently allowed, +but will become reserved variables in a future release. +</para> + </listitem> + </varlistentry> + + <varlistentry> <term><emphasis role="bold">future-deprecated</emphasis></term> <listitem> <para>Warnings about features @@ -2296,6 +2315,10 @@ feature not working when is run with the &Python; <option>-O</option> option or from optimized &Python; (<filename>.pyo</filename>) modules.</para> +<para> +Note the "no-" prefix is part of the name of this warning. +Add an additional "-no" to disable. +</para> </listitem> </varlistentry> @@ -2307,6 +2330,10 @@ not being able to support parallel builds when the <option>-j</option> option is used. These warnings are enabled by default.</para> +<para> +Note the "no-" prefix is part of the name of this warning. +Add an additional "-no" to disable. +</para> </listitem> </varlistentry> diff --git a/doc/sphinx/SCons.rst b/doc/sphinx/SCons.rst index 85f5878..9ab44b0 100644 --- a/doc/sphinx/SCons.rst +++ b/doc/sphinx/SCons.rst @@ -143,11 +143,13 @@ SCons.Subst module SCons.Warnings module --------------------- +.. Turn off inherited members to quiet fluff from the Python base Exception .. automodule:: SCons.Warnings :members: :undoc-members: :show-inheritance: + :no-inherited-members: SCons.cpp module ---------------- |