summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-11-13 21:56:57 (GMT)
committerSteven Knight <knight@baldmt.com>2004-11-13 21:56:57 (GMT)
commit09cad6ed1ffb436742adbbf6608bb094b4a236e2 (patch)
treeb6c4b1f529c82d39dab94b26fb1440980955c8fe
parent206adf4366654b0c3fcf8c9131140f0c11ae0a5f (diff)
downloadSCons-09cad6ed1ffb436742adbbf6608bb094b4a236e2.zip
SCons-09cad6ed1ffb436742adbbf6608bb094b4a236e2.tar.gz
SCons-09cad6ed1ffb436742adbbf6608bb094b4a236e2.tar.bz2
Allow Alias Nodes to have Actions.
-rw-r--r--doc/man/scons.115
-rw-r--r--src/CHANGES.txt3
-rw-r--r--src/engine/SCons/Environment.py72
-rw-r--r--src/engine/SCons/EnvironmentTests.py19
-rw-r--r--src/engine/SCons/Node/Alias.py45
-rw-r--r--src/engine/SCons/Node/AliasTests.py14
-rw-r--r--src/engine/SCons/Node/NodeTests.py13
-rw-r--r--src/engine/SCons/Node/__init__.py7
-rw-r--r--test/Alias/Alias.py (renamed from test/Alias.py)12
-rw-r--r--test/Alias/action.py138
-rw-r--r--test/Alias/errors.py46
-rw-r--r--test/Alias/scanner.py (renamed from test/Alias-scanner.py)0
12 files changed, 323 insertions, 61 deletions
diff --git a/doc/man/scons.1 b/doc/man/scons.1
index fdfae69..59e0d62 100644
--- a/doc/man/scons.1
+++ b/doc/man/scons.1
@@ -2266,11 +2266,17 @@ can be converted into an Action object
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.TP
-.RI Alias( alias ", [" targets ])
+.RI Alias( alias ", [" targets ", [" action ]])
.TP
-.RI env.Alias( alias ", [" targets ])
+.RI env.Alias( alias ", [" targets ", [" action ]])
Creates one or more phony targets that
expand to one or more other targets.
+An optional
+.I action
+(command)
+or list of actions
+can be specified that will be executed
+whenever the any of the alias targets are out-of-date.
Returns the Node object representing the alias,
which exists outside of any file system.
This Node object, or the alias name,
@@ -2278,7 +2284,8 @@ may be used as a dependency of any other target,
including another alias.
.B Alias
can be called multiple times for the same
-alias to add additional targets to the alias.
+alias to add additional targets to the alias,
+or additional actions to the list for this alias.
.ES
Alias('install')
@@ -2287,6 +2294,8 @@ Alias(['install', 'install-lib'], '/usr/local/lib')
env.Alias('install', ['/usr/local/bin', '/usr/local/lib'])
env.Alias('install', ['/usr/local/man'])
+
+env.Alias('update', ['file1', 'file2'], "update_database $SOURCES")
.EE
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index abfbd65..003163b 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -124,6 +124,9 @@ RELEASE 0.97 - XXX
to extend() list, so Python 1.5.2 doesn't get pretty-printing of Node
lists, but everything should still function.)
+ - Allow Aliases to have actions that will be executed whenever
+ any of the expanded Alias targets are out of date.
+
From Wayne Lee:
- Avoid "maximum recursion limit" errors when removing $(-$) pairs
diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py
index 443c6b0..ef3b2d0 100644
--- a/src/engine/SCons/Environment.py
+++ b/src/engine/SCons/Environment.py
@@ -96,6 +96,7 @@ AliasBuilder = SCons.Builder.Builder(action = alias_builder,
target_factory = SCons.Node.Alias.default_ans.Alias,
source_factory = SCons.Node.FS.default_fs.Entry,
multi = 1,
+ is_explicit = None,
name='AliasBuilder')
def our_deepcopy(x):
@@ -1036,29 +1037,56 @@ class Base:
n.add_post_action(action)
return nodes
- def Alias(self, target, *source, **kw):
- if not SCons.Util.is_List(target):
- target = [target]
- tlist = []
- for t in target:
- if not isinstance(t, SCons.Node.Alias.Alias):
- t = self.arg2nodes(self.subst(t), self.ans.Alias)[0]
- tlist.append(t)
- try:
- s = kw['source']
- except KeyError:
- try:
- s = source[0]
- except IndexError:
- s = None
- if s:
- if not SCons.Util.is_List(s):
- s = [s]
- s = filter(None, s)
- s = self.arg2nodes(s, self.fs.Entry)
+ def Alias(self, target, source=[], action=None, **kw):
+ tlist = self.arg2nodes(target, self.ans.Alias)
+ if not SCons.Util.is_List(source):
+ source = [source]
+ source = filter(None, source)
+
+ if not action:
+ if not source:
+ # There are no source files and no action, so just
+ # return a target list of classic Alias Nodes, without
+ # any builder. The externally visible effect is that
+ # this will make the wrapping Script.BuildTask class
+ # say that there's "Nothing to be done" for this Alias,
+ # instead of that it's "up to date."
+ return tlist
+
+ # No action, but there are sources. Re-call all the target
+ # builders to add the sources to each target.
+ result = []
for t in tlist:
- AliasBuilder(self, t, s)
- return tlist
+ bld = t.get_builder(AliasBuilder)
+ result.extend(bld(self, t, source))
+ return result
+
+ action = SCons.Action.Action(action)
+ nkw = self.subst_kw(kw)
+ nkw['source_factory'] = self.fs.Entry
+ nkw['multi'] = 1
+ nkw['action'] = action
+ bld = apply(SCons.Builder.Builder, (), nkw)
+
+ # Apply the Builder separately to each target so that the Aliases
+ # stay separate. If we did one "normal" Builder call with the
+ # whole target list, then all of the target Aliases would be
+ # associated under a single Executor.
+ result = []
+ for t in tlist:
+ # Calling the convert() method will cause a new Executor to be
+ # created from scratch, so we have to explicitly initialize
+ # it with the target's existing sources, plus our new ones,
+ # so nothing gets lost.
+ b = t.get_builder()
+ if b is None or b is AliasBuilder:
+ b = bld
+ else:
+ nkw['action'] = b.action + action
+ b = apply(SCons.Builder.Builder, (), nkw)
+ t.convert()
+ result.extend(b(self, t, t.sources + source))
+ return result
def AlwaysBuild(self, *targets):
tlist = []
diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py
index 0a0f260..9b84f3c 100644
--- a/src/engine/SCons/EnvironmentTests.py
+++ b/src/engine/SCons/EnvironmentTests.py
@@ -1797,6 +1797,7 @@ f5: \
tgt = env.Alias('new_alias')[0]
assert str(tgt) == 'new_alias', tgt
assert tgt.sources == [], tgt.sources
+ assert not hasattr(tgt, 'builder'), tgt.builder
tgt = env.Alias('None_alias', None)[0]
assert str(tgt) == 'None_alias', tgt
@@ -1833,6 +1834,24 @@ f5: \
assert str(t2.sources[0]) == 'asrc6', map(str, t2.sources)
assert str(t2.sources[1]) == 'asrc7', map(str, t2.sources)
+ tgt = env.Alias('add', 's1')
+ tgt = env.Alias('add', 's2')[0]
+ s = map(str, tgt.sources)
+ assert s == ['s1', 's2'], s
+ tgt = env.Alias(tgt, 's3')[0]
+ s = map(str, tgt.sources)
+ assert s == ['s1', 's2', 's3'], s
+
+ tgt = env.Alias('act', None, "action1")[0]
+ s = str(tgt.builder.action)
+ assert s == "action1", s
+ tgt = env.Alias('act', None, "action2")[0]
+ s = str(tgt.builder.action)
+ assert s == "action1\naction2", s
+ tgt = env.Alias(tgt, None, "action3")[0]
+ s = str(tgt.builder.action)
+ assert s == "action1\naction2\naction3", s
+
def test_AlwaysBuild(self):
"""Test the AlwaysBuild() method"""
env = Environment(FOO='fff', BAR='bbb')
diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py
index 89127a3..c361838 100644
--- a/src/engine/SCons/Node/Alias.py
+++ b/src/engine/SCons/Node/Alias.py
@@ -39,11 +39,15 @@ import SCons.Node
import SCons.Util
class AliasNameSpace(UserDict.UserDict):
- def Alias(self, name):
- if self.has_key(name):
- raise SCons.Errors.UserError
- self[name] = SCons.Node.Alias.Alias(name)
- return self[name]
+ def Alias(self, name, **kw):
+ if isinstance(name, SCons.Node.Alias.Alias):
+ return name
+ try:
+ a = self[name]
+ except KeyError:
+ a = apply(SCons.Node.Alias.Alias, (name,), kw)
+ self[name] = a
+ return a
def lookup(self, name):
try:
@@ -59,18 +63,11 @@ class Alias(SCons.Node.Node):
def __str__(self):
return self.name
- def build(self):
- """A "builder" for aliases."""
- pass
-
+ really_build = SCons.Node.Node.build
current = SCons.Node.Node.children_are_up_to_date
- def sconsign(self):
- """An Alias is not recorded in .sconsign files"""
- pass
-
def is_under(self, dir):
- # Make Alias nodes get built regardless of
+ # Make Alias nodes get built regardless of
# what directory scons was run from. Alias nodes
# are outside the filesystem:
return 1
@@ -82,7 +79,25 @@ class Alias(SCons.Node.Node):
for kid in self.children(None):
contents = contents + kid.get_contents()
return contents
-
+
+ def sconsign(self):
+ """An Alias is not recorded in .sconsign files"""
+ pass
+
+ #
+ #
+ #
+
+ def build(self):
+ """A "builder" for aliases."""
+ pass
+
+ def convert(self):
+ try: del self.builder
+ except AttributeError: pass
+ self.reset_executor()
+ self.build = self.really_build
+
default_ans = AliasNameSpace()
SCons.Node.arg2nodes_lookups.append(default_ans.lookup)
diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py
index afae46b..417cc2b 100644
--- a/src/engine/SCons/Node/AliasTests.py
+++ b/src/engine/SCons/Node/AliasTests.py
@@ -42,15 +42,11 @@ class AliasTestCase(unittest.TestCase):
"""
ans = SCons.Node.Alias.AliasNameSpace()
- a = ans.Alias('a1')
- assert a.name == 'a1', a.name
-
- try:
- ans.Alias('a1')
- except SCons.Errors.UserError:
- pass
- else:
- raise TestFailed, "did not catch expected UserError"
+ a1 = ans.Alias('a1')
+ assert a1.name == 'a1', a1.name
+
+ a2 = ans.Alias('a1')
+ assert a1 is a2, (a1, a2)
def test_lookup(self):
"""Test the lookup() method
diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py
index 657ac9b..e0956e1 100644
--- a/src/engine/SCons/Node/NodeTests.py
+++ b/src/engine/SCons/Node/NodeTests.py
@@ -390,6 +390,19 @@ class NodeTestCase(unittest.TestCase):
n1.builder_set(Builder(is_explicit=None))
assert not n1.has_explicit_builder()
+ def test_get_builder(self):
+ """Test the get_builder() method"""
+ n1 = SCons.Node.Node()
+ b = n1.get_builder()
+ assert b is None, b
+ b = n1.get_builder(777)
+ assert b == 777, b
+ n1.builder_set(888)
+ b = n1.get_builder()
+ assert b == 888, b
+ b = n1.get_builder(999)
+ assert b == 888, b
+
def test_multiple_side_effect_has_builder(self):
"""Test the multiple_side_effect_has_builder() method
"""
diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py
index 36b0aed..fc3d7ec 100644
--- a/src/engine/SCons/Node/__init__.py
+++ b/src/engine/SCons/Node/__init__.py
@@ -329,6 +329,13 @@ class Node:
directories)."""
return self.has_builder() and self.builder.is_explicit
+ def get_builder(self, default_builder=None):
+ """Return the set builder, or a specified default value"""
+ try:
+ return self.builder
+ except AttributeError:
+ return default_builder
+
multiple_side_effect_has_builder = has_builder
def is_derived(self):
diff --git a/test/Alias.py b/test/Alias/Alias.py
index 1facd28..56efafd 100644
--- a/test/Alias.py
+++ b/test/Alias/Alias.py
@@ -166,16 +166,4 @@ test.run(arguments = 'f1.out',
test.up_to_date(arguments = 'f1.out')
-test.write('SConstruct', """
-env=Environment()
-TargetSignatures('content')
-env.Alias('C', 'D')
-env.Alias('B', 'C')
-env.Alias('A', 'B')
-""")
-
-test.run(arguments='A',
- stderr="scons: \\*\\*\\* Source `D' not found, needed by target `C'. Stop.\n",
- status=2)
-
test.pass_test()
diff --git a/test/Alias/action.py b/test/Alias/action.py
new file mode 100644
index 0000000..ea0eac2
--- /dev/null
+++ b/test/Alias/action.py
@@ -0,0 +1,138 @@
+#!/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.
+#
+
+"""
+Test that Aliases with actions work.
+"""
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+import os
+import sys
+import TestSCons
+import TestCmd
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+def cat(target, source, env):
+ target = str(target[0])
+ source = map(str, source)
+ f = open(target, "wb")
+ for src in source:
+ f.write(open(src, "rb").read())
+ f.close()
+
+def foo(target, source, env):
+ target = map(str, target)
+ source = map(str, source)
+ open('foo', 'wb').write("foo(%s, %s)\\n" % (target, source))
+
+def bar(target, source, env):
+ target = map(str, target)
+ source = map(str, source)
+ open('bar', 'wb').write("bar(%s, %s)\\n" % (target, source))
+
+env = Environment(BUILDERS = {'Cat':Builder(action=cat)})
+env.Alias(target = ['build-f1'], source = 'f1.out', action = foo)
+f1 = env.Cat('f1.out', 'f1.in')
+f2 = env.Cat('f2.out', 'f2.in')
+f3 = env.Cat('f3.out', 'f3.in')
+f4 = env.Cat('f4.out', 'f4.in')
+f5 = env.Cat('f5.out', 'f5.in')
+f6 = env.Cat('f6.out', 'f6.in')
+env.Alias('build-all', [f1, f2, f3], foo)
+env.Alias('build-add1', f3, foo)
+env.Alias('build-add1', f2)
+env.Alias('build-add2a', f4)
+env.Alias('build-add2b', f5)
+env.Alias(['build-add2a', 'build-add2b'], action=foo)
+env.Alias('build-add3', f6)
+env.Alias('build-add3', action=foo)
+env.Alias('build-add3', action=bar)
+""")
+
+test.write('f1.in', "f1.in 1\n")
+test.write('f2.in', "f2.in 1\n")
+test.write('f3.in', "f3.in 1\n")
+test.write('f4.in', "f4.in 1\n")
+test.write('f5.in', "f5.in 1\n")
+test.write('f6.in', "f6.in 1\n")
+
+test.run(arguments = 'build-f1')
+
+test.must_match('f1.out', "f1.in 1\n")
+test.must_match('foo', "foo(['build-f1'], ['f1.out'])\n")
+
+test.up_to_date(arguments = 'build-f1')
+
+test.write('f1.in', "f1.in 2\n")
+test.unlink('foo')
+
+test.run(arguments = 'build-f1')
+
+test.must_match('f1.out', "f1.in 2\n")
+test.must_match('foo', "foo(['build-f1'], ['f1.out'])\n")
+
+test.run(arguments = 'build-all')
+
+test.must_match('f1.out', "f1.in 2\n")
+test.must_match('f2.out', "f2.in 1\n")
+test.must_match('f3.out', "f3.in 1\n")
+test.must_match('foo', "foo(['build-all'], ['f1.out', 'f2.out', 'f3.out'])\n")
+
+test.up_to_date(arguments = 'build-all')
+test.up_to_date(arguments = 'build-add1')
+
+test.write('f1.in', "f1.in 3\n")
+test.write('f3.in', "f3.in 2\n")
+test.unlink('foo')
+
+test.run(arguments = 'build-add1')
+
+test.must_match('f1.out', "f1.in 2\n")
+test.must_match('f2.out', "f2.in 1\n")
+test.must_match('f3.out', "f3.in 2\n")
+test.must_match('foo', "foo(['build-add1'], ['f3.out', 'f2.out'])\n")
+
+test.up_to_date(arguments = 'build-add1')
+
+test.run(arguments = 'build-add2a')
+
+test.must_match('f4.out', "f4.in 1\n")
+test.must_not_exist('f5.out')
+test.must_match('foo', "foo(['build-add2a'], ['f4.out'])\n")
+
+test.run(arguments = 'build-add2b')
+
+test.must_match('f5.out', "f5.in 1\n")
+test.must_match('foo', "foo(['build-add2b'], ['f5.out'])\n")
+
+test.run(arguments = 'build-add3')
+
+test.must_match('f6.out', "f6.in 1\n")
+test.must_match('foo', "foo(['build-add3'], ['f6.out'])\n")
+test.must_match('bar', "bar(['build-add3'], ['f6.out'])\n")
+
+test.pass_test()
diff --git a/test/Alias/errors.py b/test/Alias/errors.py
new file mode 100644
index 0000000..f2ca950
--- /dev/null
+++ b/test/Alias/errors.py
@@ -0,0 +1,46 @@
+#!/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__"
+
+import os
+import sys
+import TestSCons
+import TestCmd
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+env=Environment()
+TargetSignatures('content')
+env.Alias('C', 'D')
+env.Alias('B', 'C')
+env.Alias('A', 'B')
+""")
+
+test.run(arguments='A',
+ stderr="scons: *** Source `D' not found, needed by target `C'. Stop.\n",
+ status=2)
+
+test.pass_test()
diff --git a/test/Alias-scanner.py b/test/Alias/scanner.py
index 3ce14dd..3ce14dd 100644
--- a/test/Alias-scanner.py
+++ b/test/Alias/scanner.py