summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CHANGES.txt10
-rw-r--r--src/engine/SCons/Action.py85
-rw-r--r--src/engine/SCons/ActionTests.py106
-rw-r--r--src/engine/SCons/Defaults.py56
-rw-r--r--src/engine/SCons/Script/SConscript.py8
5 files changed, 261 insertions, 4 deletions
diff --git a/src/CHANGES.txt b/src/CHANGES.txt
index f3e9a3d..0d28a0a 100644
--- a/src/CHANGES.txt
+++ b/src/CHANGES.txt
@@ -83,6 +83,16 @@ RELEASE 0.96 - XXX
Environments are the same object, not if their underlying dictionaries
are equivalent.
+ - Add a --debug=explain option that reports the reason(s) why SCons
+ thinks it must rebuild something.
+
+ - Add support for functions that return platform-independent Actions
+ to Chmod(), Copy(), Delete(), Mkdir(), Move() and Touch() files
+ and/or directories. Like any other Actions, the returned Action
+ object may be executed directly using the Execute() global function
+ or env.Execute() environment method, or may be used as a Builder
+ action or in an env.Command() action list.
+
From Gary Oberbrunner:
- Add a --debug=presub option to print actions prior to substitution.
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 7397873..6b8b184 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -58,6 +58,14 @@ this module:
pre-substitution representations, and *then* execute an action
without worrying about the specific Actions involved.
+There is a related independent ActionCaller class that looks like a
+regular Action, and which serves as a wrapper for arbitrary functions
+that we want to let the user specify the arguments to now, but actually
+execute later (when an out-of-date check determines that it's needed to
+be executed, for example). Objects of this class are returned by an
+ActionFactory class that provides a __call__() method as a convenient
+way for wrapping up the functions.
+
"""
#
@@ -512,11 +520,21 @@ class FunctionAction(ActionBase):
"""
try:
# "self.execfunction" is a function.
- code = self.execfunction.func_code.co_code
+ contents = str(self.execfunction.func_code.co_code)
except AttributeError:
# "self.execfunction" is a callable object.
- code = self.execfunction.__call__.im_func.func_code.co_code
- return str(code) + env.subst(string.join(map(lambda v: '${'+v+'}',
+ try:
+ contents = str(self.execfunction.__call__.im_func.func_code.co_code)
+ except AttributeError:
+ try:
+ # See if execfunction will do the heavy lifting for us.
+ gc = self.execfunction.get_contents
+ except AttributeError:
+ # This is weird, just do the best we can.
+ contents = str(self.execfunction)
+ else:
+ contents = gc(target, source, env, dict)
+ return contents + env.subst(string.join(map(lambda v: '${'+v+'}',
self.varlist)))
class ListAction(ActionBase):
@@ -565,3 +583,64 @@ class ListAction(ActionBase):
x.get_contents(t, s, e, d),
self.list),
"")
+
+class ActionCaller:
+ """A class for delaying calling an Action function with specific
+ (positional and keyword) arguments until the Action is actually
+ executed.
+
+ This class looks to the rest of the world like a normal Action object,
+ but what it's really doing is hanging on to the arguments until we
+ have a target, source and env to use for the expansion.
+ """
+ def __init__(self, parent, args, kw):
+ self.parent = parent
+ self.args = args
+ self.kw = kw
+ def get_contents(self, target, source, env, dict=None):
+ actfunc = self.parent.actfunc
+ try:
+ # "self.actfunc" is a function.
+ contents = str(actfunc.func_code.co_code)
+ except AttributeError:
+ # "self.actfunc" is a callable object.
+ try:
+ contents = str(actfunc.__call__.im_func.func_code.co_code)
+ except AttributeError:
+ # No __call__() method, so it might be a builtin
+ # or something like that. Do the best we can.
+ contents = str(actfunc)
+ return contents
+ def subst_args(self, target, source, env):
+ return map(lambda x, e=env, t=target, s=source:
+ e.subst(x, 0, t, s),
+ self.args)
+ def subst_kw(self, target, source, env):
+ kw = {}
+ for key in self.kw.keys():
+ kw[key] = env.subst(self.kw[key], 0, target, source)
+ return kw
+ def __call__(self, target, source, env):
+ args = self.subst_args(target, source, env)
+ kw = self.subst_kw(target, source, env)
+ return apply(self.parent.actfunc, args, kw)
+ def strfunction(self, target, source, env):
+ args = self.subst_args(target, source, env)
+ kw = self.subst_kw(target, source, env)
+ return apply(self.parent.strfunc, args, kw)
+
+class ActionFactory:
+ """A factory class that will wrap up an arbitrary function
+ as an SCons-executable Action object.
+
+ The real heavy lifting here is done by the ActionCaller class.
+ We just collect the (positional and keyword) arguments that we're
+ called with and give them to the ActionCaller object we create,
+ so it can hang onto them until it needs them.
+ """
+ def __init__(self, actfunc, strfunc):
+ self.actfunc = actfunc
+ self.strfunc = strfunc
+ def __call__(self, *args, **kw):
+ ac = ActionCaller(self, args, kw)
+ return Action(ac, strfunction=ac.strfunction)
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index a273607..589d1d3 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -1103,6 +1103,13 @@ class FunctionActionTestCase(unittest.TestCase):
c = a.get_contents(target=[], source=[], env=Environment(XYZ = 'foo'))
assert c == "\177\036\000\177\037\000d\000\000Sfoo", repr(c)
+ class Foo:
+ def get_contents(self, target, source, env, dict=None):
+ return 'xyzzy'
+ a = SCons.Action.FunctionAction(Foo())
+ c = a.get_contents(target=[], source=[], env=Environment())
+ assert c == 'xyzzy', repr(c)
+
class ListActionTestCase(unittest.TestCase):
def test___init__(self):
@@ -1265,6 +1272,101 @@ class LazyActionTestCase(unittest.TestCase):
c = a.get_contents(target=[], source=[], env=env, dict={})
assert c == "This is a test", c
+class ActionCallerTestCase(unittest.TestCase):
+ def test___init__(self):
+ """Test creation of an ActionCaller"""
+ ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO' : 4, 'BAR' : 5})
+ assert ac.parent == 1, ac.parent
+ assert ac.args == [2, 3], ac.args
+ assert ac.kw == {'FOO' : 4, 'BAR' : 5}, ac.kw
+
+ def test_get_contents(self):
+ """Test fetching the contents of an ActionCaller"""
+ def actfunc():
+ pass
+ def strfunc():
+ pass
+
+ af = SCons.Action.ActionFactory(actfunc, strfunc)
+ ac = SCons.Action.ActionCaller(af, [], {})
+ c = ac.get_contents([], [], Environment())
+ assert c == "\177\005\005\177\006\005d\000\000S", repr(c)
+
+ class ActFunc:
+ def __call__(self):
+ pass
+
+ af = SCons.Action.ActionFactory(ActFunc(), strfunc)
+ ac = SCons.Action.ActionCaller(af, [], {})
+ c = ac.get_contents([], [], Environment())
+ assert c == "\177\020\005\177\021\005d\000\000S", repr(c)
+
+ af = SCons.Action.ActionFactory(str, strfunc)
+ ac = SCons.Action.ActionCaller(af, [], {})
+ c = ac.get_contents([], [], Environment())
+ assert c == "<built-in function str>" or \
+ c == "<type 'str'>", repr(c)
+
+ def test___call__(self):
+ """Test calling an ActionCaller"""
+ actfunc_args = []
+ def actfunc(a1, a2, a3, args=actfunc_args):
+ args.extend([a1, a2, a3])
+ def strfunc(a1, a2, a3):
+ pass
+
+ af = SCons.Action.ActionFactory(actfunc, strfunc)
+ ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3], {})
+ ac([], [], Environment(FOO = 2))
+ assert actfunc_args == [1, '2', 3], actfunc_args
+
+ del actfunc_args[:]
+ ac = SCons.Action.ActionCaller(af, [], {'a3' : 6, 'a2' : '$BAR', 'a1' : 4})
+ ac([], [], Environment(BAR = 5))
+ assert actfunc_args == [4, '5', 6], actfunc_args
+
+ def test_strfunction(self):
+ """Test calling the ActionCaller strfunction() method"""
+ strfunc_args = []
+ def actfunc(a1, a2, a3):
+ pass
+ def strfunc(a1, a2, a3, args=strfunc_args):
+ args.extend([a1, a2, a3])
+
+ af = SCons.Action.ActionFactory(actfunc, strfunc)
+ ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3], {})
+ ac.strfunction([], [], Environment(FOO = 2))
+ assert strfunc_args == [1, '2', 3], strfunc_args
+
+ del strfunc_args[:]
+ ac = SCons.Action.ActionCaller(af, [], {'a3' : 6, 'a2' : '$BAR', 'a1' : 4})
+ ac.strfunction([], [], Environment(BAR = 5))
+ assert strfunc_args == [4, '5', 6], strfunc_args
+
+class ActionFactoryTestCase(unittest.TestCase):
+ def test___init__(self):
+ """Test creation of an ActionFactory"""
+ def actfunc():
+ pass
+ def strfunc():
+ pass
+ ac = SCons.Action.ActionFactory(actfunc, strfunc)
+ assert ac.actfunc is actfunc, ac.actfunc
+ assert ac.strfunc is strfunc, ac.strfunc
+
+ def test___call__(self):
+ """Test calling whatever's returned from an ActionFactory"""
+ actfunc_args = []
+ strfunc_args = []
+ def actfunc(a1, a2, a3, args=actfunc_args):
+ args.extend([a1, a2, a3])
+ def strfunc(a1, a2, a3, args=strfunc_args):
+ args.extend([a1, a2, a3])
+ af = SCons.Action.ActionFactory(actfunc, strfunc)
+ af(3, 6, 9)([], [], Environment())
+ assert actfunc_args == [3, 6, 9], actfunc_args
+ assert strfunc_args == [3, 6, 9], strfunc_args
+
if __name__ == "__main__":
suite = unittest.TestSuite()
@@ -1274,7 +1376,9 @@ if __name__ == "__main__":
CommandGeneratorActionTestCase,
FunctionActionTestCase,
ListActionTestCase,
- LazyActionTestCase ]
+ LazyActionTestCase,
+ ActionCallerTestCase,
+ ActionFactoryTestCase ]
for tclass in tclasses:
names = unittest.getTestCaseNames(tclass, 'test_')
suite.addTests(map(tclass, names))
diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py
index 89ac0d3..98c9195 100644
--- a/src/engine/SCons/Defaults.py
+++ b/src/engine/SCons/Defaults.py
@@ -41,6 +41,7 @@ import os.path
import shutil
import stat
import string
+import time
import types
import SCons.Action
@@ -144,6 +145,61 @@ def PDF():
prefix = '$PDFPREFIX',
suffix = '$PDFSUFFIX')
+# Common tasks that we allow users to perform in platform-independent
+# ways by creating ActionFactory instances.
+ActionFactory = SCons.Action.ActionFactory
+
+Chmod = ActionFactory(os.chmod,
+ lambda dest, mode: 'Chmod("%s", 0%o)' % (dest, mode))
+
+def Copy(dest, src):
+ def _copy_func(target, source, env, dest=dest, src=src):
+ dest = str(env.arg2nodes(dest, env.fs.Entry)[0])
+ src = str(env.arg2nodes(src, env.fs.Entry)[0])
+ shutil.copytree(src, dest, 1)
+ def _copy_str(target, source, env, dest=dest, src=src):
+ dest = str(env.arg2nodes(dest, env.fs.Entry)[0])
+ src = str(env.arg2nodes(src, env.fs.Entry)[0])
+ return 'Copy("%s", "%s")' % (dest, src)
+ return SCons.Action.Action(_copy_func, strfunction=_copy_str)
+
+def copy_func(dest, src):
+ if os.path.isfile(src):
+ return shutil.copy(src, dest)
+ else:
+ return shutil.copytree(src, dest, 1)
+
+Copy = ActionFactory(copy_func,
+ lambda dest, src: 'Copy("%s", "%s")' % (dest, src))
+
+def delete_func(entry):
+ if os.path.isfile(entry):
+ return os.unlink(entry)
+ else:
+ return shutil.rmtree(entry, 1)
+
+Delete = ActionFactory(delete_func,
+ lambda entry: 'Delete("%s")' % entry)
+
+Mkdir = ActionFactory(os.makedirs,
+ lambda dir: 'Mkdir("%s")' % dir)
+
+Move = ActionFactory(lambda dest, src: os.rename(src, dest),
+ lambda dest, src: 'Move("%s", "%s")' % (dest, src))
+
+def touch_func(file):
+ mtime = int(time.time())
+ if os.path.exists(file):
+ atime = os.path.getatime(file)
+ else:
+ open(file, 'w')
+ atime = mtime
+ return os.utime(file, (atime, mtime))
+
+Touch = ActionFactory(touch_func,
+ lambda file: 'Touch("%s")' % file)
+
+# Internal utility functions
def copyFunc(dest, source, env):
"""Install a source file into a destination by copying it (and its
permission/mode bits)."""
diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py
index 417e1c0..6a414b7 100644
--- a/src/engine/SCons/Script/SConscript.py
+++ b/src/engine/SCons/Script/SConscript.py
@@ -674,6 +674,14 @@ def BuildDefaultGlobals():
'Tool' : SCons.Tool.Tool,
'WhereIs' : SCons.Util.WhereIs,
+ # Action factories.
+ 'Chmod' : SCons.Defaults.Chmod,
+ 'Copy' : SCons.Defaults.Copy,
+ 'Delete' : SCons.Defaults.Delete,
+ 'Mkdir' : SCons.Defaults.Mkdir,
+ 'Move' : SCons.Defaults.Move,
+ 'Touch' : SCons.Defaults.Touch,
+
# Other variables we provide.
'ARGUMENTS' : Arguments,
'BUILD_TARGETS' : BuildTargets,