'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.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,
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')
env.Alias('install', ['/usr/local/bin', '/usr/local/lib'])
env.Alias('install', ['/usr/local/man'])
+
+env.Alias('update', ['file1', 'file2'], "update_database $SOURCES")
.EE
'\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
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
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):
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 = []
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
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')
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:
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
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)
"""
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
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
"""
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):
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()
--- /dev/null
+#!/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()
--- /dev/null
+#!/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()