"""
return apply(self.action.execute, (), kw)
+ def get_contents(self, **kw):
+ """Fetch the "contents" of the builder's action
+ (for signature calculation).
+ """
+ return apply(self.action.get_contents, (), kw)
+
class MultiStepBuilder(BuilderBase):
"""This is a builder subclass that can build targets in
multiple steps. The src_builder parameter to the constructor
def show(self, string):
print string
+ def subst_dict(self, **kw):
+ """Create a dictionary for substitution of construction
+ variables.
+
+ This translates the following special arguments:
+
+ env - the construction environment itself,
+ the values of which (CC, CCFLAGS, etc.)
+ are copied straight into the dictionary
+
+ target - the target (object or array of objects),
+ used to generate the TARGET and TARGETS
+ construction variables
+
+ source - the source (object or array of objects),
+ used to generate the SOURCES construction
+ variable
+
+ Any other keyword arguments are copied into the
+ dictionary."""
+
+ dict = {}
+ if kw.has_key('env'):
+ dict.update(kw['env'])
+ del kw['env']
+
+ if kw.has_key('target'):
+ t = kw['target']
+ del kw['target']
+ if type(t) is type(""):
+ t = [t]
+ dict['TARGETS'] = PathList(map(os.path.normpath, t))
+ dict['TARGET'] = dict['TARGETS'][0]
+ if kw.has_key('source'):
+ s = kw['source']
+ del kw['source']
+ if type(s) is type(""):
+ s = [s]
+ dict['SOURCES'] = PathList(map(os.path.normpath, s))
+
+ dict.update(kw)
+
+ return dict
+
class CommandAction(ActionBase):
"""Class for command-execution actions."""
def __init__(self, string):
- self.command = string
+ self.command = string
def execute(self, **kw):
- loc = {}
- if kw.has_key('target'):
- t = kw['target']
- if type(t) is type(""):
- t = [t]
- loc['TARGETS'] = PathList(map(os.path.normpath, t))
- loc['TARGET'] = loc['TARGETS'][0]
- if kw.has_key('source'):
- s = kw['source']
- if type(s) is type(""):
- s = [s]
- loc['SOURCES'] = PathList(map(os.path.normpath, s))
-
- glob = {}
- if kw.has_key('env'):
- glob = kw['env']
-
- cmd_str = scons_subst(self.command, loc, glob)
+ dict = apply(self.subst_dict, (), kw)
+ cmd_str = scons_subst(self.command, dict, {})
for cmd in string.split(cmd_str, '\n'):
if print_actions:
self.show(cmd)
if execute_actions:
args = string.split(cmd)
try:
- ENV = glob['ENV']
+ ENV = kw['env']['ENV']
except:
import SCons.Defaults
ENV = SCons.Defaults.ConstructionEnvironment['ENV']
ret = spawn(args[0], args, ENV)
if ret:
- #XXX This doesn't account for ignoring errors (-i)
return ret
return 0
+ def get_contents(self, **kw):
+ """Return the signature contents of this action's command line.
+ For signature purposes, it doesn't matter what targets or
+ sources we use, so long as we use the same ones every time
+ so the signature stays the same. We supply an array of two
+ of each to allow for distinction between TARGET and TARGETS.
+ """
+ kw['target'] = ['__t1__', '__t2__']
+ kw['source'] = ['__s1__', '__s2__']
+ dict = apply(self.subst_dict, (), kw)
+ return scons_subst(self.command, dict, {})
class FunctionAction(ActionBase):
"""Class for Python function actions."""
# if print_actions:
# XXX: WHAT SHOULD WE PRINT HERE?
if execute_actions:
- return self.function(kw)
+ dict = apply(self.subst_dict, (), kw)
+ return apply(self.function, (), dict)
+
+ def get_contents(self, **kw):
+ """Return the signature contents of this callable action.
+
+ By providing direct access to the code object of the
+ function, Python makes this extremely easy. Hooray!
+ """
+ #XXX DOES NOT ACCOUNT FOR CHANGES IN ENVIRONMENT VARIABLES
+ #THE FUNCTION MAY USE
+ try:
+ # "self.function" is a function.
+ code = self.function.func_code.co_code
+ except:
+ # "self.function" is a callable object.
+ code = self.function.__call__.im_func.func_code.co_code
+ return str(code)
class ListAction(ActionBase):
"""Class for lists of other actions."""
if r != 0:
return r
return 0
+
+ def get_contents(self, **kw):
+ """Return the signature contents of this action list.
+
+ Simple concatenation of the signatures of the elements.
+ """
+
+ return reduce(lambda x, y: x + str(y.get_contents()), self.list, "")
c = test.read(outfile, 'r')
assert c == "act.py: out5 XYZZY\nact.py: xyzzy\n", c
- def function1(kw):
+ def function1(**kw):
open(kw['out'], 'w').write("function1\n")
return 1
assert c == "function1\n", c
class class1a:
- def __init__(self, kw):
+ def __init__(self, **kw):
open(kw['out'], 'w').write("class1a\n")
builder = SCons.Builder.Builder(action = class1a)
assert c == "class1a\n", c
class class1b:
- def __call__(self, kw):
+ def __call__(self, **kw):
open(kw['out'], 'w').write("class1b\n")
return 2
cmd2 = r'%s %s %s syzygy' % (python, act_py, outfile)
- def function2(kw):
+ def function2(**kw):
open(kw['out'], 'a').write("function2\n")
return 0
class class2a:
- def __call__(self, kw):
+ def __call__(self, **kw):
open(kw['out'], 'a').write("class2a\n")
return 0
class class2b:
- def __init__(self, kw):
+ def __init__(self, **kw):
open(kw['out'], 'a').write("class2b\n")
builder = SCons.Builder.Builder(action = [cmd2, function2, class2a(), class2b])
c = test.read(outfile, 'r')
assert c == "act.py: syzygy\nfunction2\nclass2a\nclass2b\n", c
+ def test_get_contents(self):
+ """Test returning the signature contents of a Builder
+ """
+
+ b1 = SCons.Builder.Builder(action = "foo")
+ contents = b1.get_contents()
+ assert contents == "foo", contents
+
+ def func():
+ pass
+
+ b2 = SCons.Builder.Builder(action = func)
+ contents = b2.get_contents()
+ assert contents == "\177\340\0\177\341\0d\0\0S", contents
+
+ b3 = SCons.Builder.Builder(action = ["foo", func, "bar"])
+ contents = b3.get_contents()
+ assert contents == "foo\177\340\0\177\341\0d\0\0Sbar", contents
+
def test_name(self):
"""Test Builder creation with a specified name
"""
global built_it
built_it = 1
return 0
+ def get_contents(self, env):
+ return 7
class FailBuilder:
def execute(self, **kw):
node.builder_set(b)
assert node.builder == b
+ def test_builder_sig_adapter(self):
+ """Test the node's adapter for builder signatures
+ """
+ node = SCons.Node.Node()
+ node.builder_set(Builder())
+ node.env_set(Environment())
+ c = node.builder_sig_adapter().get_contents()
+ assert c == 7, c
+
def test_current(self):
"""Test the default current() method
"""
def builder_set(self, builder):
self.builder = builder
+ def builder_sig_adapter(self):
+ """Create an adapter for calculating a builder's signature.
+
+ The underlying signature class will call get_contents()
+ to fetch the signature of a builder, but the actual
+ content of that signature depends on the node and the
+ environment (for construction variable substitution),
+ so this adapter provides the right glue between the two.
+ """
+ class Adapter:
+ def __init__(self, node):
+ self.node = node
+ def get_contents(self):
+ env = self.node.env.Dictionary()
+ return self.node.builder.get_contents(env = env)
+ return Adapter(self)
+
def env_set(self, env):
self.env = env
"""Generate a signature for an object
"""
try:
- contents = obj.get_contents()
- except AttributeError:
- raise AttributeError, "unable to fetch contents of '%s'" % str(obj)
+ contents = str(obj.get_contents())
+ except AttributeError, e:
+ raise AttributeError, \
+ "unable to fetch contents of '%s': %s" % (str(obj), e)
return hexdigest(md5.new(contents).digest())
def to_string(signature):
def test_signature(self):
"""Test generating a signature"""
o1 = my_obj(value = '111')
- assert '698d51a19d8a121ce581499d7b701668' == signature(o1)
+ s = signature(o1)
+ assert '698d51a19d8a121ce581499d7b701668' == s, s
+
+ o2 = my_obj(value = 222)
+ s = signature(o2)
+ assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s
try:
signature('string')
except AttributeError, e:
- assert str(e) == "unable to fetch contents of 'string'"
+ assert str(e) == "unable to fetch contents of 'string': 'string' object has no attribute 'get_contents'", e
else:
raise AttributeError, "unexpected get_contents() attribute"
def get_bsig(self):
return self.bsig
+ def set_csig(self, csig):
+ self.csig = csig
+
+ def get_csig(self):
+ return self.bsig
+
def get_prevsiginfo(self):
return (self.oldtime, self.oldbsig, self.oldcsig)
+ def builder_sig_adapter(self):
+ class Adapter:
+ def get_contents(self):
+ return 111
+ def get_timestamp(self):
+ return 222
+ return Adapter()
+
def create_files(test):
args = [(test.workpath('f1.c'), 'blah blah', 111, 0), #0
return 0, self.bsig, self.csig
def get_timestamp(self):
return 1
+ def builder_sig_adapter(self):
+ class MyAdapter:
+ def get_csig(self):
+ return 333
+ def get_timestamp(self):
+ return 444
+ return MyAdapter()
self.module = MySigModule()
self.nodeclass = MyNode
n4 = self.nodeclass('n4', None, None)
n4.builder = 1
n4.kids = [n2, n3]
- assert self.calc.get_signature(n4) == 57
+ assert self.calc.get_signature(n4) == 390
n5 = NE('n5', 55, 56)
assert self.calc.get_signature(n5) is None
already built and updated by someone else, if that's
what's wanted.
"""
+ if not node.use_signature:
+ return None
#XXX If configured, use the content signatures from the
#XXX .sconsign file if the timestamps match.
sigs = map(lambda n,s=self: s.get_signature(n), node.children())
+ if node.builder:
+ sigs.append(self.module.signature(node.builder_sig_adapter()))
return self.module.collect(filter(lambda x: not x is None, sigs))
def csig(self, node):
node - the node
returns - the content signature
"""
+ if not node.use_signature:
+ return None
#XXX If configured, use the content signatures from the
#XXX .sconsign file if the timestamps match.
return self.module.signature(node)
""")
-test.run(arguments = 'foo bar')
+test.run(arguments = '.')
test.run(program = test.workpath('foo'), stdout = "prog.c: FOO\n")
test.run(program = test.workpath('bar'), stdout = "prog.c: BAR\n")
+test.write('SConstruct', """
+bar = Environment(CCFLAGS = '-DBAR')
+bar.Object(target = 'foo.o', source = 'prog.c')
+bar.Object(target = 'bar.o', source = 'prog.c')
+bar.Program(target = 'foo', source = 'foo.o')
+bar.Program(target = 'bar', source = 'bar.o')
+""")
+
+test.run(arguments = '.')
+
+test.run(program = test.workpath('foo'), stdout = "prog.c: BAR\n")
+test.run(program = test.workpath('bar'), stdout = "prog.c: BAR\n")
+
test.pass_test()
--- /dev/null
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 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 TestSCons
+
+python = sys.executable
+
+test = TestSCons.TestSCons()
+
+test.write('build.py', r"""
+import sys
+file = open(sys.argv[1], 'wb')
+file.write(sys.argv[2] + "\n")
+file.write(open(sys.argv[3], 'rb').read())
+file.close
+sys.exit(0)
+""")
+
+test.write('SConstruct', """
+B = Builder(name = 'B', action = r'%s build.py $TARGET 1 $SOURCES')
+env = Environment(BUILDERS = [B])
+env.B(target = 'foo.out', source = 'foo.in')
+""" % python)
+
+test.write('foo.in', "foo.in\n")
+
+test.run(arguments = '.')
+
+test.fail_test(test.read('foo.out') != "1\nfoo.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.write('SConstruct', """
+B = Builder(name = 'B', action = r'%s build.py $TARGET 2 $SOURCES')
+env = Environment(BUILDERS = [B])
+env.B(target = 'foo.out', source = 'foo.in')
+""" % python)
+
+test.run(arguments = '.')
+
+test.fail_test(test.read('foo.out') != "2\nfoo.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.write('SConstruct', """
+import os
+import SCons.Util
+def func(**kw):
+ cmd = SCons.Util.scons_subst(r'%s build.py $TARGET 3 $SOURCES', kw, {})
+ return os.system(cmd)
+B = Builder(name = 'B', action = func)
+env = Environment(BUILDERS = [B])
+env.B(target = 'foo.out', source = 'foo.in')
+""" % python)
+
+test.run(arguments = '.')
+
+test.fail_test(test.read('foo.out') != "3\nfoo.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.write('SConstruct', """
+import os
+import SCons.Util
+class bld:
+ def __init__(self):
+ self.cmd = r'%s build.py $TARGET 4 $SOURCES'
+ def __call__(self, **kw):
+ cmd = SCons.Util.scons_subst(self.cmd, kw, {})
+ return os.system(cmd)
+ def get_contents(self, **kw):
+ cmd = SCons.Util.scons_subst(self.cmd, kw, {})
+ return cmd
+B = Builder(name = 'B', action = bld())
+env = Environment(BUILDERS = [B])
+env.B(target = 'foo.out', source = 'foo.in')
+""" % python)
+
+test.run(arguments = '.')
+
+test.fail_test(test.read('foo.out') != "4\nfoo.in\n")
+
+test.up_to_date(arguments = '.')
+
+test.pass_test()