summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2001-11-02 03:12:07 (GMT)
committerSteven Knight <knight@baldmt.com>2001-11-02 03:12:07 (GMT)
commit2f51f69cf055ec44e37eaac22b1992bd5218278f (patch)
tree60165bd248d020974a0be3f5477825876a682445 /src
parent00c3ada748b164ecf909727fa81f521dde72fda9 (diff)
downloadSCons-2f51f69cf055ec44e37eaac22b1992bd5218278f.zip
SCons-2f51f69cf055ec44e37eaac22b1992bd5218278f.tar.gz
SCons-2f51f69cf055ec44e37eaac22b1992bd5218278f.tar.bz2
Rebuild in response to a changed build command.
Diffstat (limited to 'src')
-rw-r--r--src/engine/SCons/Builder.py113
-rw-r--r--src/engine/SCons/BuilderTests.py31
-rw-r--r--src/engine/SCons/Node/NodeTests.py11
-rw-r--r--src/engine/SCons/Node/__init__.py17
-rw-r--r--src/engine/SCons/Sig/MD5.py7
-rw-r--r--src/engine/SCons/Sig/MD5Tests.py9
-rw-r--r--src/engine/SCons/Sig/SigTests.py23
-rw-r--r--src/engine/SCons/Sig/__init__.py6
8 files changed, 183 insertions, 34 deletions
diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py
index 4023530..3bb17da 100644
--- a/src/engine/SCons/Builder.py
+++ b/src/engine/SCons/Builder.py
@@ -188,6 +188,12 @@ class BuilderBase:
"""
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
@@ -301,47 +307,85 @@ class ActionBase:
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."""
@@ -352,7 +396,24 @@ class FunctionAction(ActionBase):
# 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."""
@@ -365,3 +426,11 @@ class ListAction(ActionBase):
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, "")
diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py
index e24784d..89ba1fc 100644
--- a/src/engine/SCons/BuilderTests.py
+++ b/src/engine/SCons/BuilderTests.py
@@ -161,7 +161,7 @@ class BuilderTestCase(unittest.TestCase):
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
@@ -172,7 +172,7 @@ class BuilderTestCase(unittest.TestCase):
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)
@@ -182,7 +182,7 @@ class BuilderTestCase(unittest.TestCase):
assert c == "class1a\n", c
class class1b:
- def __call__(self, kw):
+ def __call__(self, **kw):
open(kw['out'], 'w').write("class1b\n")
return 2
@@ -194,17 +194,17 @@ class BuilderTestCase(unittest.TestCase):
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])
@@ -213,6 +213,25 @@ class BuilderTestCase(unittest.TestCase):
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
"""
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index 035bc90..24ad955 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -39,6 +39,8 @@ class Builder:
global built_it
built_it = 1
return 0
+ def get_contents(self, env):
+ return 7
class FailBuilder:
def execute(self, **kw):
@@ -89,6 +91,15 @@ class NodeTestCase(unittest.TestCase):
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
"""
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 0e1a8d9..513b260 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -79,6 +79,23 @@ class Node:
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
diff --git a/src/engine/SCons/Sig/MD5.py b/src/engine/SCons/Sig/MD5.py
index e13669e..bd5643f 100644
--- a/src/engine/SCons/Sig/MD5.py
+++ b/src/engine/SCons/Sig/MD5.py
@@ -79,9 +79,10 @@ def signature(obj):
"""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):
diff --git a/src/engine/SCons/Sig/MD5Tests.py b/src/engine/SCons/Sig/MD5Tests.py
index f9cf61f..2d42fc0 100644
--- a/src/engine/SCons/Sig/MD5Tests.py
+++ b/src/engine/SCons/Sig/MD5Tests.py
@@ -73,12 +73,17 @@ class MD5TestCase(unittest.TestCase):
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"
diff --git a/src/engine/SCons/Sig/SigTests.py b/src/engine/SCons/Sig/SigTests.py
index 95789e8..0870c93 100644
--- a/src/engine/SCons/Sig/SigTests.py
+++ b/src/engine/SCons/Sig/SigTests.py
@@ -87,9 +87,23 @@ class DummyNode:
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
@@ -269,6 +283,13 @@ class CalcTestCase(unittest.TestCase):
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
@@ -318,7 +339,7 @@ class CalcTestCase(unittest.TestCase):
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
diff --git a/src/engine/SCons/Sig/__init__.py b/src/engine/SCons/Sig/__init__.py
index 4095792..43e3b8a 100644
--- a/src/engine/SCons/Sig/__init__.py
+++ b/src/engine/SCons/Sig/__init__.py
@@ -150,9 +150,13 @@ class Calculator:
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):
@@ -163,6 +167,8 @@ class Calculator:
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)