diff options
author | Steven Knight <knight@baldmt.com> | 2002-06-05 23:35:56 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2002-06-05 23:35:56 (GMT) |
commit | 2f7d4f660fd048edc342a989d25c94d7b52ab13e (patch) | |
tree | 20def5c6f0f7b68ab7151cac4ada5c470a3b31e0 /src/engine/SCons | |
parent | 42717c855f7cbb73d3017ac243a34491d3cc0c53 (diff) | |
download | SCons-2f7d4f660fd048edc342a989d25c94d7b52ab13e.zip SCons-2f7d4f660fd048edc342a989d25c94d7b52ab13e.tar.gz SCons-2f7d4f660fd048edc342a989d25c94d7b52ab13e.tar.bz2 |
Changes from Charles Crain.
Diffstat (limited to 'src/engine/SCons')
-rw-r--r-- | src/engine/SCons/Builder.py | 31 | ||||
-rw-r--r-- | src/engine/SCons/BuilderTests.py | 24 | ||||
-rw-r--r-- | src/engine/SCons/Defaults.py | 58 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 74 | ||||
-rw-r--r-- | src/engine/SCons/EnvironmentTests.py | 44 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/C.py | 4 | ||||
-rw-r--r-- | src/engine/SCons/Scanner/CTests.py | 13 | ||||
-rw-r--r-- | src/engine/SCons/Script/__init__.py | 102 | ||||
-rw-r--r-- | src/engine/SCons/Warnings.py | 81 | ||||
-rw-r--r-- | src/engine/SCons/WarningsTests.py | 115 |
10 files changed, 472 insertions, 74 deletions
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py index 42c9484..d3feb26 100644 --- a/src/engine/SCons/Builder.py +++ b/src/engine/SCons/Builder.py @@ -40,6 +40,7 @@ import SCons.Action import SCons.Node import SCons.Node.FS import SCons.Util +import SCons.Warnings class DictCmdGenerator: """This is a callable class that can be used as a @@ -73,6 +74,9 @@ class DictCmdGenerator: def Builder(**kw): """A factory for builder objects.""" + if kw.has_key('name'): + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "The use of the 'name' parameter to Builder() is deprecated.") if kw.has_key('generator'): if kw.has_key('action'): raise UserError, "You must not specify both an action and a generator." @@ -124,7 +128,7 @@ def _init_nodes(builder, env, args, tlist, slist): if isinstance(t.builder, ListBuilder) and isinstance(builder, ListBuilder) and t.builder.builder == builder.builder: raise UserError, "Two different target sets have a target in common: %s"%str(t) else: - raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.name, builder.name, str(t)) + raise UserError, "Two different builders (%s and %s) were specified for the same target: %s"%(t.builder.get_name(env), builder.get_name(env), str(t)) elif t.sources != slist: raise UserError, "Multiple ways to build the same target were specified for: %s" % str(t) @@ -202,8 +206,6 @@ class BuilderBase: scanner = None, emitter = None, multi = 0): - if name is None: - raise UserError, "You must specify a name for the builder." self.name = name self.action = SCons.Action.Action(action) self.multi = multi @@ -231,6 +233,22 @@ class BuilderBase: self.emitter = emitter + def get_name(self, env): + """Attempts to get the name of the Builder. + + If the Builder's name attribute is None, then we will look at + the BUILDERS variable of env, expecting it to be a dictionary + containing this Builder, and we will return the key of the + dictionary.""" + + if self.name: + return self.name + try: + index = env['BUILDERS'].values().index(self) + return env['BUILDERS'].keys()[index] + except (AttributeError, KeyError, ValueError): + return str(self.__class__) + def __cmp__(self, other): return cmp(self.__dict__, other.__dict__) @@ -342,8 +360,11 @@ class ListBuilder: def __init__(self, builder, env, tlist): self.builder = builder - self.tlist = tlist - self.name = "ListBuilder(%s)"%builder.name + self.tlist = tlist + self.name = "ListBuilder(%s)"%builder.get_name(env) + + def get_name(self, env): + return self.name def execute(self, **kw): if hasattr(self, 'status'): diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py index d7fd92d..c69aaa6 100644 --- a/src/engine/SCons/BuilderTests.py +++ b/src/engine/SCons/BuilderTests.py @@ -39,6 +39,7 @@ import TestCmd import SCons.Builder import SCons.Errors import SCons.Node.FS +import SCons.Warnings # Initial setup of the common environment for all tests, # a temporary working directory containing a @@ -158,17 +159,24 @@ class BuilderTestCase(unittest.TestCase): assert target.sources[1].name == uni('n19') def test_noname(self): - """Test error reporting for missing name + """Test deprecated warning for Builder name. - Verify that the Builder constructor gives an error message if the - name is missing. + Using the name argument for Builder() is deprectaed and the + user should receive a warning. """ + SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warningAsException(1) + try: - b = SCons.Builder.Builder() - except SCons.Errors.UserError: - pass - else: - assert 0 + try: + b = SCons.Builder.Builder(name='foo') + except SCons.Warnings.DeprecatedWarning: + pass + else: + assert 0 + finally: + SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warningAsException(0) def test_action(self): """Test Builder creation diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py index 2259f40..e8bf376 100644 --- a/src/engine/SCons/Defaults.py +++ b/src/engine/SCons/Defaults.py @@ -68,8 +68,9 @@ class SharedCmdGenerator: def __call__(self, target, source, env, shared=0, **kw): for src in source: try: - if src.attributes.shared != shared: - raise SCons.Errors.UserError("Source file: %s must be built with shared=%s in order to be compatible with the selected target." % (src, str(shared))) + if (src.attributes.shared and not shared) or \ + (shared and not src.attributes.shared): + raise SCons.Errors.UserError("Source file: %s must be built with shared=%s in order to be compatible with target: %s" % (src, str(shared), target[0])) except AttributeError: pass for t in target: @@ -90,15 +91,13 @@ def yaccEmitter(target, source, env, **kw): '.h') return (target, source) -CFile = SCons.Builder.Builder(name = 'CFile', - action = { '.l' : '$LEXCOM', +CFile = SCons.Builder.Builder(action = { '.l' : '$LEXCOM', '.y' : '$YACCCOM', }, emitter = yaccEmitter, suffix = '$CFILESUFFIX') -CXXFile = SCons.Builder.Builder(name = 'CXXFile', - action = { '.ll' : '$LEXCOM', +CXXFile = SCons.Builder.Builder(action = { '.ll' : '$LEXCOM', '.yy' : '$YACCCOM', }, emitter = yaccEmitter, @@ -156,9 +155,8 @@ static_obj = SCons.Builder.DictCmdGenerator({ ".C" : C_static, ".FOR" : F77Action, ".fpp" : F77PPAction, ".FPP" : F77PPAction }) - -Object = SCons.Builder.Builder(name = 'Object', - generator = \ + +Object = SCons.Builder.Builder(generator = \ SharedCmdGenerator(static=SCons.Action.CommandGeneratorAction(static_obj), shared=SCons.Action.CommandGeneratorAction(shared_obj)), prefix = '$OBJPREFIX', @@ -166,7 +164,7 @@ Object = SCons.Builder.Builder(name = 'Object', src_suffix = static_obj.src_suffixes(), src_builder = [CFile, CXXFile]) -def win32TempFileMunge(env, cmd_list, for_signature): +def win32TempFileMunge(env, cmd_list, for_signature): """Given a list of command line arguments, see if it is too long to pass to the win32 command line interpreter. If so, create a temp file, then pass "@tempfile" as the sole argument @@ -194,8 +192,7 @@ def win32LinkGenerator(env, target, source, for_signature, **kw): args.extend(map(SCons.Util.to_String, source)) return win32TempFileMunge(env, args, for_signature) -Program = SCons.Builder.Builder(name='Program', - action='$LINKCOM', +Program = SCons.Builder.Builder(action='$LINKCOM', prefix='$PROGPREFIX', suffix='$PROGSUFFIX', src_suffix='$OBJSUFFIX', @@ -266,8 +263,7 @@ def win32LibEmitter(target, source, env, shared=0, env.subst("$LIBSUFFIX"))) return (target, source) -Library = SCons.Builder.Builder(name = 'Library', - generator = \ +Library = SCons.Builder.Builder(generator = \ SharedCmdGenerator(shared="$SHLINKCOM", static="$ARCOM"), emitter="$LIBEMITTER", @@ -282,8 +278,7 @@ Library = SCons.Builder.Builder(name = 'Library', LaTeXAction = SCons.Action.Action('$LATEXCOM') -DVI = SCons.Builder.Builder(name = 'DVI', - action = { '.tex' : '$TEXCOM', +DVI = SCons.Builder.Builder(action = { '.tex' : '$TEXCOM', '.ltx' : LaTeXAction, '.latex' : LaTeXAction, }, @@ -295,8 +290,7 @@ DVI = SCons.Builder.Builder(name = 'DVI', PDFLaTeXAction = SCons.Action.Action('$PDFLATEXCOM') -PDF = SCons.Builder.Builder(name = 'PDF', - action = { '.dvi' : '$PDFCOM', +PDF = SCons.Builder.Builder(action = { '.dvi' : '$PDFCOM', '.tex' : '$PDFTEXCOM', '.ltx' : PDFLaTeXAction, '.latex' : PDFLaTeXAction, @@ -304,8 +298,7 @@ PDF = SCons.Builder.Builder(name = 'PDF', prefix = '$PDFPREFIX', suffix = '$PDFSUFFIX') -PostScript = SCons.Builder.Builder(name = 'PostScript', - action = '$PSCOM', +PostScript = SCons.Builder.Builder(action = '$PSCOM', prefix = '$PSPREFIX', suffix = '$PSSUFFIX', src_suffix = '.dvi', @@ -316,8 +309,7 @@ CScan = SCons.Scanner.C.CScan() def alias_builder(env, target, source): pass -Alias = SCons.Builder.Builder(name = 'Alias', - action = alias_builder, +Alias = SCons.Builder.Builder(action = alias_builder, target_factory = SCons.Node.Alias.default_ans.Alias, source_factory = SCons.Node.FS.default_fs.Entry, multi = 1) @@ -481,8 +473,15 @@ def make_win32_env_from_paths(include, lib, path): 'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES', 'PSPREFIX' : '', 'PSSUFFIX' : '.ps', - 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object, - PDF, PostScript, Program], + 'BUILDERS' : { 'Alias' : Alias, + 'CFile' : CFile, + 'CXXFile' : CXXFile, + 'DVI' : DVI, + 'Library' : Library, + 'Object' : Object, + 'PDF' : PDF, + 'PostScript' : PostScript, + 'Program' : Program }, 'SCANNERS' : [CScan], 'LIBDIRPREFIX' : '/LIBPATH:', 'LIBDIRSUFFIX' : '', @@ -583,8 +582,15 @@ if os.name == 'posix': 'PSCOM' : '$DVIPS $DVIPSFLAGS -o $TARGET $SOURCES', 'PSPREFIX' : '', 'PSSUFFIX' : '.ps', - 'BUILDERS' : [Alias, CFile, CXXFile, DVI, Library, Object, - PDF, PostScript, Program], + 'BUILDERS' : { 'Alias' : Alias, + 'CFile' : CFile, + 'CXXFile' : CXXFile, + 'DVI' : DVI, + 'Library' : Library, + 'Object' : Object, + 'PDF' : PDF, + 'PostScript' : PostScript, + 'Program' : Program }, 'SCANNERS' : [CScan], 'LIBDIRPREFIX' : '-L', 'LIBDIRSUFFIX' : '', diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 8c61ceb..787a66d 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -45,6 +45,8 @@ from SCons.Errors import UserError import SCons.Node import SCons.Node.FS import SCons.Util +import SCons.Warnings +from UserDict import UserDict def installFunc(env, target, source): try: @@ -82,6 +84,29 @@ def our_deepcopy(x): copy = x return copy +class BuilderDict(UserDict): + """This is a dictionary-like class used by Environment + to hold Builders. We need to do this, because every time + someone changes the Builders in the Environment's BUILDERS + dictionary, we need to update the Environment's attributes.""" + def setEnvironment(self, env): + self.env = env + + def __setitem__(self, item, val): + UserDict.__setitem__(self, item, val) + try: + self.env.Replace() # re-compute Builders + except AttributeError: + # Have to catch this because sometimes + # __setitem__ gets called out of __init__, when + # we don't have an env attribute yet, nor do + # we want one! + pass + + def __delitem__(self, item): + UserDict.__delitem__(self, item) + self.env.Replace() + class Environment: """Base class for construction Environments. These are the primary objects used to communicate dependency and @@ -155,14 +180,13 @@ class Environment: """ apply(self.Replace, (), kw) - def Replace(self, **kw): - """Replace existing construction variables in an Environment - with new construction variables and/or values. - """ - self._dict.update(our_deepcopy(kw)) - if self._dict.has_key('BUILDERS') and \ - not SCons.Util.is_List(self._dict['BUILDERS']): - self._dict['BUILDERS'] = [self._dict['BUILDERS']] + def __updateBuildersAndScanners(self): + """Update attributes for builders and scanners. + + Have to be careful in this function...we can't + call functions like __setitem__() or Replace(), or + we will have infinite recursion.""" + if self._dict.has_key('SCANNERS') and \ not SCons.Util.is_List(self._dict['SCANNERS']): self._dict['SCANNERS'] = [self._dict['SCANNERS']] @@ -190,11 +214,37 @@ class Environment: kw['env'] = self.env apply(self.builder.execute, (), kw) - for b in self._dict['BUILDERS']: - setattr(self, b.name, BuilderWrapper(self, b)) + if self._dict.has_key('BUILDERS'): + if SCons.Util.is_Dict(self._dict['BUILDERS']): + bd = self._dict['BUILDERS'] + if not isinstance(bd, BuilderDict): + # Convert it to a BuilderDict. This class + # Updates our builder attributes every time + # someone changes it. + bd = BuilderDict(bd) + bd.setEnvironment(self) + self._dict['BUILDERS'] = bd + for name, builder in bd.items(): + setattr(self, name, BuilderWrapper(self, builder)) + else: + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "The use of the BUILDERS Environment variable as a list or Builder instance is deprecated. BUILDERS should be a dictionary of name->Builder instead.") + + if not SCons.Util.is_List(self._dict['BUILDERS']): + self._dict['BUILDERS'] = [self._dict['BUILDERS']] + for b in self._dict['BUILDERS']: + setattr(self, b.name, BuilderWrapper(self, b)) for s in self._dict['SCANNERS']: setattr(self, s.name, s) + + + def Replace(self, **kw): + """Replace existing construction variables in an Environment + with new construction variables and/or values. + """ + self._dict.update(our_deepcopy(kw)) + self.__updateBuildersAndScanners() def Append(self, **kw): """Append values to existing construction variables @@ -212,6 +262,7 @@ class Environment: self._dict[key] = [ self._dict[key] ] + kw[key] else: self._dict[key] = self._dict[key] + kw[key] + self.__updateBuildersAndScanners() def Depends(self, target, dependency): """Explicity specify that 'target's depend on 'dependency'.""" @@ -257,6 +308,7 @@ class Environment: def __setitem__(self, key, value): self._dict[key] = value + self.__updateBuildersAndScanners() def __getitem__(self, key): return self._dict[key] @@ -272,7 +324,7 @@ class Environment: source files using the supplied action. Action may be any type that the Builder constructor will accept for an action.""" - bld = SCons.Builder.Builder(name="Command", action=action) + bld = SCons.Builder.Builder(action=action) return bld(self, target, source) def Install(self, dir, source): diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 3066fc2..6859ee3 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -27,7 +27,7 @@ import sys import unittest from SCons.Environment import * - +import SCons.Warnings built_it = {} @@ -76,6 +76,7 @@ class EnvironmentTestCase(unittest.TestCase): b1 = Builder(name = 'builder1') b2 = Builder(name = 'builder2') + # BUILDERS as a list or instance, now deprecated... built_it = {} env1 = Environment(BUILDERS = b1) env1.builder1.execute(target = 'out1') @@ -100,6 +101,47 @@ class EnvironmentTestCase(unittest.TestCase): assert env4.builder1.env is env4 assert env4.builder2.env is env4 + # Now test BUILDERS as a dictionary. + built_it = {} + env5 = Environment(BUILDERS={ 'foo' : b1 }) + env5['BUILDERS']['bar'] = b2 + env5.foo.execute(target='out1') + env5.bar.execute(target='out2') + assert built_it['out1'] + assert built_it['out2'] + + built_it = {} + env6 = Environment() + env6['BUILDERS'] = { 'foo' : b1, + 'bar' : b2 } + env6.foo.execute(target='out1') + env6.bar.execute(target='out2') + assert built_it['out1'] + assert built_it['out2'] + + # Now test deprecated warning for BUILDERS as a list + # or instance. + + SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warningAsException(1) + try: + try: + env=Environment(BUILDERS=b1) + except SCons.Warnings.DeprecatedWarning: + pass + else: + assert 0 + + try: + env=Environment(BUILDERS=[b1, b2]) + except SCons.Warnings.DeprecatedWarning: + pass + else: + assert 0 + finally: + SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warningAsException(0) + def test_Scanners(self): """Test Scanner execution through different environments diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py index c81417a..77b9525 100644 --- a/src/engine/SCons/Scanner/C.py +++ b/src/engine/SCons/Scanner/C.py @@ -38,6 +38,7 @@ import SCons.Node import SCons.Node.FS import SCons.Scanner import SCons.Util +import SCons.Warnings include_re = re.compile('^[ \t]*#[ \t]*include[ \t]+(<|")([\\w./\\\\]+)(>|")', re.M) @@ -114,6 +115,9 @@ def scan(node, env, target, fs = SCons.Node.FS.default_fs): if not n is None: nodes.append(n) + else: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (include[1], node)) node.found_includes[cpppath] = nodes # Schwartzian transform from the Python FAQ Wizard diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py index f99af18..cc9e116 100644 --- a/src/engine/SCons/Scanner/CTests.py +++ b/src/engine/SCons/Scanner/CTests.py @@ -30,6 +30,7 @@ import sys import os import os.path import SCons.Node.FS +import SCons.Warnings test = TestCmd.TestCmd(workdir = '') @@ -230,11 +231,23 @@ class CScannerTestCase8(unittest.TestCase): class CScannerTestCase9(unittest.TestCase): def runTest(self): + SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning) + class TestOut: + def __call__(self, x): + self.out = x + + to = TestOut() + to.out = None + SCons.Warnings._warningOut = to test.write('fa.h','\n') fs = SCons.Node.FS.FS(test.workpath('')) s = SCons.Scanner.C.CScan(fs=fs) env = DummyEnvironment([]) deps = s.scan(fs.File('fa.cpp'), env, DummyTarget()) + + # Did we catch the warning associated with not finding fb.h? + assert to.out + deps_match(self, deps, [ 'fa.h' ]) test.unlink('fa.h') diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index c42a700..d95f8f0 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -63,6 +63,7 @@ import SCons.Sig.MD5 from SCons.Taskmaster import Taskmaster import SCons.Builder import SCons.Script.SConscript +import SCons.Warnings # @@ -200,24 +201,20 @@ def _scons_syntax_error(e): def find_deepest_user_frame(tb): """ Find the deepest stack frame that is not part of SCons. + + Input is a "pre-processed" stack trace in the form + returned by traceback.extract_tb() or traceback.extract_stack() """ - stack = [tb] - while tb.tb_next is not None: - tb = tb.tb_next - stack.append(tb) - - stack.reverse() + tb.reverse() # find the deepest traceback frame that is not part # of SCons: - for frame in stack: - filename = frame.tb_frame.f_code.co_filename + for frame in tb: + filename = frame[0] if string.find(filename, os.sep+'SCons'+os.sep) == -1: - tb = frame - break - - return tb + return frame + return tb[0] def _scons_user_error(e): """Handle user errors. Print out a message and a description of the @@ -226,10 +223,7 @@ def _scons_user_error(e): not part of SCons itself. """ etype, value, tb = sys.exc_info() - tb = find_deepest_user_frame(tb) - lineno = traceback.tb_lineno(tb) - filename = tb.tb_frame.f_code.co_filename - routine = tb.tb_frame.f_code.co_name + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) sys.stderr.write("\nSCons error: %s\n" % value) sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) sys.exit(2) @@ -241,10 +235,15 @@ def _scons_user_warning(e): not part of SCons itself. """ etype, value, tb = sys.exc_info() - tb = find_deepest_user_frame(tb) - lineno = traceback.tb_lineno(tb) - filename = tb.tb_frame.f_code.co_filename - routine = tb.tb_frame.f_code.co_name + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) + sys.stderr.write("\nSCons warning: %s\n" % e) + sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) + +def _scons_internal_warning(e): + """Slightly different from _scons_user_warning in that we use the + *current call stack* rather than sys.exc_info() to get our stack trace. + This is used by the warnings framework to print warnings.""" + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack()) sys.stderr.write("\nSCons warning: %s\n" % e) sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) @@ -685,9 +684,62 @@ def options_init(): short = 'W', long = ['what-if', 'new-file', 'assume-new'], arg = 'FILE', help = "Consider FILE to be changed.") - Option(func = opt_not_yet, future = 1, - long = ['warn-undefined-variables'], - help = "Warn when an undefined variable is referenced.") + def opt_warn(opt, arg): + """The --warn option. An argument to this option + should be of the form <warning-class> or no-<warning-class>. + The warning class is munged in order to get an actual class + name from the SCons.Warnings module to enable or disable. + The supplied <warning-class> is split on hyphens, each element + is captialized, then smushed back together. Then the string + "SCons.Warnings." is added to the front and "Warning" is added + to the back to get the fully qualified class name. + + For example, --warn=deprecated will enable the + SCons.Warnings.DeprecatedWarning class. + + --warn=no-dependency will disable the + SCons.Warnings.DependencyWarning class. + + As a special case, --warn=all and --warn=no-all + will enable or disable (respectively) the base + class of all warnings, which is SCons.Warning.Warning.""" + + elems = string.split(string.lower(arg), '-') + enable = 1 + if elems[0] == 'no': + enable = 0 + del elems[0] + + if len(elems) == 1 and elems[0] == 'all': + class_name = "Warning" + else: + class_name = string.join(map(string.capitalize, elems), '') + \ + "Warning" + try: + clazz = getattr(SCons.Warnings, class_name) + except AttributeError: + sys.stderr.write("No warning type: '%s'\n" % arg) + else: + if enable: + SCons.Warnings.enableWarningClass(clazz) + else: + SCons.Warnings.suppressWarningClass(clazz) + + Option(func = opt_warn, + long = [ 'warn', 'warning' ], arg='WARNING-SPEC', + help = "Enable or disable warnings.") + + + # We want to preserve the --warn-undefined-variables option for + # compatibility with GNU Make. Unfortunately, this conflicts with + # the --warn=type option that we're using for our own warning + # control. The getopt module reports "--warn not a unique prefix" + # when both are defined. We may be able to support both in the + # future with a more robust getopt solution. + # + #Option(func = opt_not_yet, future = 1, + # long = ['warn-undefined-variables'], + # help = "Warn when an undefined variable is referenced.") Option(func = opt_not_yet, future = 1, short = 'Y', long = ['repository'], arg = 'REPOSITORY', @@ -749,6 +801,10 @@ def _main(): except: getopt_err = getopt.error + # Enable deprecated warnings by default. + SCons.Warnings._warningOut = _scons_internal_warning + SCons.Warnings.enableWarningClass(SCons.Warnings.DeprecatedWarning) + try: cmd_opts, t = getopt.getopt(string.split(os.environ['SCONSFLAGS']), short_opts, long_opts) diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py new file mode 100644 index 0000000..5ba3aec --- /dev/null +++ b/src/engine/SCons/Warnings.py @@ -0,0 +1,81 @@ +# +# Copyright (c) 2001, 2002 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +"""SCons.Warnings + +This file implements the warnings framework for SCons. + +""" + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import copy +import traceback + +import SCons.Errors + +class Warning(SCons.Errors.UserError): + pass + +class DeprecatedWarning(Warning): + pass + +class DependencyWarning(Warning): + pass + +_warningAsException = 0 + +# 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 = [] + +_warningOut = None + +def suppressWarningClass(clazz): + """Suppresses all warnings that are of type clazz or + derived from clazz.""" + _enabled.insert(0, (clazz, 0)) + +def enableWarningClass(clazz): + """Suppresses all warnings that are of type clazz or + derived from clazz.""" + _enabled.insert(0, (clazz, 1)) + +def warningAsException(flag=1): + global _warningAsException + _warningAsException = flag + +def warn(clazz, *args): + global _enabled, _warningAsException, _warningOut + + warning = clazz(args) + for clazz, flag in _enabled: + if isinstance(warning, clazz): + if flag: + if _warningAsException: + raise warning + + if _warningOut: + _warningOut(warning) + break diff --git a/src/engine/SCons/WarningsTests.py b/src/engine/SCons/WarningsTests.py new file mode 100644 index 0000000..efc9adc --- /dev/null +++ b/src/engine/SCons/WarningsTests.py @@ -0,0 +1,115 @@ +# +# Copyright (c) 2001, 2002 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import sys +import unittest +import SCons.Warnings + +class TestOutput: + def __call__(self, x): + self.out = str(x) + +class WarningsTestCase(unittest.TestCase): + def test_Warning(self): + """Test warn function.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + to = TestOutput() + SCons.Warnings._warningOut=to + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "Foo", 1) + assert to.out == "('Foo', 1)", to.out + + def test_WarningAsExc(self): + """Test warnings as exceptions.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warningAsException() + try: + SCons.Warnings.warn(SCons.Warnings.Warning, "Foo") + except: + pass + else: + assert 0 + + SCons.Warnings.warningAsException(0) + SCons.Warnings.warn(SCons.Warnings.Warning, "Foo") + + def test_Disable(self): + """Test disabling/enabling warnings.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + to = TestOutput() + SCons.Warnings._warningOut=to + to.out = None + + # No warnings by default + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out is None, to.out + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + + to.out = None + SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out is None, to.out + + # Dependency warnings should still be enabled though + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "Foo") + assert to.out == "Foo", to.out + + # Try reenabling all warnings... + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + +if __name__ == "__main__": + suite = unittest.makeSuite(WarningsTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) |