From bfe5ed5b06d0fa0ba08f81dd5e8eaf4285593208 Mon Sep 17 00:00:00 2001 From: stevenknight Date: Tue, 30 Dec 2008 19:59:43 +0000 Subject: [PATCH] Issue 1417: Fix use of attributes (${SOURCES.windows}, e.g.) with null lists of targets and sources. git-svn-id: http://scons.tigris.org/svn/scons/trunk@3865 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- src/CHANGES.txt | 3 ++ src/engine/SCons/Subst.py | 14 +++++--- src/engine/SCons/Util.py | 39 +++++++++----------- test/Subst/null-sources-attr.py | 63 +++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 test/Subst/null-sources-attr.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 4b759f06..d5e1bf9f 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -18,6 +18,9 @@ RELEASE 1.X - XXX - Add a --warn=future-deprecated option for advance warnings about deprecated features that still have warnings hidden by default. + - Fix use of $SOURCE and $SOURCES attributes when there are no + sources specified in the Builder call. + From Arve Knudsen: - Document TestCommon.shobj_prefix variable. diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py index 752bbff0..fc9d7d96 100644 --- a/src/engine/SCons/Subst.py +++ b/src/engine/SCons/Subst.py @@ -253,6 +253,12 @@ class Target_or_Source: return repr(nl[0]) return '' +class NullNodeList(SCons.Util.NullSeq): + def __call__(self, *args, **kwargs): return '' + def __str__(self): return '' + +NullNodesList = NullNodeList() + def subst_dict(target, source): """Create a dictionary for substitution of special construction variables. @@ -280,8 +286,8 @@ def subst_dict(target, source): dict['TARGETS'] = Targets_or_Sources(tnl) dict['TARGET'] = Target_or_Source(tnl) else: - dict['TARGETS'] = None - dict['TARGET'] = None + dict['TARGETS'] = NullNodesList + dict['TARGET'] = NullNodesList if source: def get_src_subst_proxy(node): @@ -299,8 +305,8 @@ def subst_dict(target, source): dict['SOURCES'] = Targets_or_Sources(snl) dict['SOURCE'] = Target_or_Source(snl) else: - dict['SOURCES'] = None - dict['SOURCE'] = None + dict['SOURCES'] = NullNodesList + dict['SOURCE'] = NullNodesList return dict diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py index 641db9c7..bbd985d3 100644 --- a/src/engine/SCons/Util.py +++ b/src/engine/SCons/Util.py @@ -136,11 +136,6 @@ class NodeList(UserList): return string.join(map(str, self.data)) def __getattr__(self, name): - if not self.data: - # If there is nothing in the list, then we have no attributes to - # pass through, so raise AttributeError for everything. - raise AttributeError, "NodeList has no attribute: %s" % name - # Return a list of the attribute, gotten from every element # in the list attrList = map(lambda x, n=name: getattr(x, n), self.data) @@ -1549,29 +1544,29 @@ def MD5collect(signatures): # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205 # ASPN: Python Cookbook: Null Object Design Pattern +# TODO(1.5): +#class Null(object): class Null: - """ Null objects always and reliably "do nothging." """ - + """ Null objects always and reliably "do nothing." """ def __new__(cls, *args, **kwargs): if not '_inst' in vars(cls): #cls._inst = type.__new__(cls, *args, **kwargs) cls._inst = apply(type.__new__, (cls,) + args, kwargs) return cls._inst - def __init__(self, *args, **kwargs): - pass - def __call__(self, *args, **kwargs): - return self - def __repr__(self): - return "Null()" - def __nonzero__(self): - return False - def __getattr__(self, mname): - return self - def __setattr__(self, name, value): - return self - def __delattr__(self, name): - return self - + def __init__(self, *args, **kwargs): pass + def __call__(self, *args, **kwargs): return self + def __repr__(self): return "Null(0x%08X)" % id(self) + def __nonzero__(self): return False + def __getattr__(self, mname): return self + def __setattr__(self, name, value): return self + def __delattr__(self, name): return self + +class NullSeq(Null): + def __len__(self): return 0 + def __iter__(self): return iter(()) + def __getitem__(self, i): return self + def __delitem__(self, i): return self + def __setitem__(self, i, v): return self del __revision__ diff --git a/test/Subst/null-sources-attr.py b/test/Subst/null-sources-attr.py new file mode 100644 index 00000000..ac3ffd87 --- /dev/null +++ b/test/Subst/null-sources-attr.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# 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__" + +""" +Verify that $SOURCES attributes work even when there is no +sources list in the builder call. +""" + +import TestSCons + +_python_ = TestSCons._python_ + +test = TestSCons.TestSCons() + +test.write('cat.py', """\ +import sys +fp = open(sys.argv[1], 'wb') +for infile in sys.argv[2:]: + fp.write(open(infile, 'rb').read()) +fp.close() +sys.exit(0) +""") + +test.write('SConstruct', """ +env = Environment(CATCOM=r'%(_python_)s cat.py $TARGET ${SOURCES.posix} $FILES') +env['BUILDERS']['Cat'] = Builder(action='$CATCOM') +env.Cat('out1', ['file1', 'file2', 'file3']) +env.Cat('out2', [], FILES=['file1', 'file2', 'file3']) +""" % locals()) + +test.write('file1', "file1\n") +test.write('file2', "file2\n") +test.write('file3', "file3\n") + +test.run(arguments = '.') + +test.must_match('out1', "file1\nfile2\nfile3\n") +test.must_match('out2', "file1\nfile2\nfile3\n") + +test.pass_test() -- 2.26.2