summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2004-03-23 05:23:20 (GMT)
committerSteven Knight <knight@baldmt.com>2004-03-23 05:23:20 (GMT)
commitbd136c4b1fe44da061910372d80b79c86a31d4e3 (patch)
tree9f6d601ec3b50d626b2612a2230c81050d235d5e /src/engine/SCons
parentbfbe139e5a5fd96dfa0319768ad9a0088a5e7e78 (diff)
downloadSCons-bd136c4b1fe44da061910372d80b79c86a31d4e3.zip
SCons-bd136c4b1fe44da061910372d80b79c86a31d4e3.tar.gz
SCons-bd136c4b1fe44da061910372d80b79c86a31d4e3.tar.bz2
Add a --debug= option to print commands before substitution. (Gary Oberbrunner)
Diffstat (limited to 'src/engine/SCons')
-rw-r--r--src/engine/SCons/Action.py159
-rw-r--r--src/engine/SCons/ActionTests.py186
-rw-r--r--src/engine/SCons/Script/__init__.py5
3 files changed, 232 insertions, 118 deletions
diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py
index 6759b02..c074de4 100644
--- a/src/engine/SCons/Action.py
+++ b/src/engine/SCons/Action.py
@@ -1,6 +1,54 @@
-"""engine.SCons.Action
-
-XXX
+"""SCons.Action
+
+This encapsulates information about executing any sort of action that
+can build one or more target Nodes (typically files) from one or more
+source Nodes (also typically files) given a specific Environment.
+
+The base class here is ActionBase. The base class supplies just a few
+OO utility methods and some generic methods for displaying information
+about an Action in response to the various commands that control printing.
+
+The heavy lifting is handled by subclasses for the different types of
+actions we might execute:
+
+ CommandAction
+ CommandGeneratorAction
+ FunctionAction
+ ListAction
+
+The subclasses supply the following public interface methods used by
+other modules:
+
+ __call__()
+ THE public interface, "calling" an Action object executes the
+ command or Python function. This also takes care of printing
+ a pre-substitution command for debugging purposes.
+
+ get_contents()
+ Fetches the "contents" of an Action for signature calculation.
+ This is what the Sig/*.py subsystem uses to decide if a target
+ needs to be rebuilt because its action changed.
+
+Subclasses also supply the following methods for internal use within
+this module:
+
+ __str__()
+ Returns a string representation of the Action *without* command
+ substitution. This is used by the __call__() methods to display
+ the pre-substitution command whenever the --debug=presub option
+ is used.
+
+ strfunction()
+ Returns a substituted string representation of the Action.
+ This is used by the ActionBase.show() command to display the
+ command/function that will be executed to generate the target(s).
+
+ _execute()
+ The internal method that really, truly, actually handles the
+ execution of a command or Python function. This is used so
+ that the __call__() methods can take care of displaying any
+ pre-substitution representations, and *then* execute an action
+ without worrying about the specific Actions involved.
"""
@@ -44,8 +92,9 @@ class _Null:
_null = _Null
-print_actions = 1;
-execute_actions = 1;
+print_actions = 1
+execute_actions = 1
+print_actions_presub = 0
default_ENV = None
@@ -145,9 +194,18 @@ class ActionBase:
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
- def show(self, string):
+ def show(self, s):
if print_actions:
- sys.stdout.write(string + '\n')
+ sys.stdout.write(s + '\n')
+
+ def presub(self, target):
+ if print_actions_presub:
+ if not SCons.Util.is_List(target):
+ target = [target]
+ lines = string.split(str(self), '\n')
+ sys.stdout.write("Building %s with action(s):\n %s\n"%
+ (string.join(map(lambda x: str(x), target), ' and '),
+ string.join(lines, '\n ')))
def get_actions(self):
return [self]
@@ -177,11 +235,14 @@ class CommandAction(ActionBase):
if __debug__: logInstanceCreation(self)
self.cmd_list = cmd
+ def __str__(self):
+ return str(self.cmd_list)
+
def strfunction(self, target, source, env):
cmd_list = env.subst_list(self.cmd_list, 0, target, source)
return map(_string_from_cmd_list, cmd_list)
- def __call__(self, target, source, env):
+ def _execute(self, target, source, env):
"""Execute a command action.
This will handle lists of commands as well as individual commands,
@@ -265,15 +326,9 @@ class CommandAction(ActionBase):
return ret
return 0
- def get_raw_contents(self, target, source, env, dict=None):
- """Return the complete contents of this action's command line.
- """
- cmd = self.cmd_list
- if SCons.Util.is_List(cmd):
- cmd = string.join(map(str, cmd))
- else:
- cmd = str(cmd)
- return env.subst(cmd, SCons.Util.SUBST_RAW, target, source, dict)
+ def __call__(self, target, source, env):
+ self.presub(target)
+ return self._execute(target, source, env)
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action's command line.
@@ -313,12 +368,24 @@ class CommandGeneratorAction(ActionBase):
act = self.__generate(target, source, env, 0)
return act.strfunction(target, rsources, env)
+ def __str__(self):
+ act = self.__generate([], [], {}, 0)
+ return str(act)
+
+ def _execute(self, target, source, env):
+ if not SCons.Util.is_List(source):
+ source = [source]
+ rsources = map(rfile, source)
+ act = self.__generate(target, source, env, 0)
+ return act._execute(target, rsources, env)
+
def __call__(self, target, source, env):
if not SCons.Util.is_List(source):
source = [source]
rsources = map(rfile, source)
act = self.__generate(target, source, env, 0)
- return act(target, rsources, env)
+ act.presub(target)
+ return act._execute(target, source, env)
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action's command line.
@@ -329,10 +396,11 @@ class CommandGeneratorAction(ActionBase):
return self.__generate(target, source, env, 1).get_contents(target, source, env, dict=None)
class LazyCmdGenerator:
- """This is a simple callable class that acts as a command generator.
- It holds on to a key into an Environment dictionary, then waits
- until execution time to see what type it is, then tries to
- create an Action out of it."""
+ """This is not really an Action, although it kind of looks like one.
+ This is really a simple callable class that acts as a command
+ generator. It holds on to a key into an Environment dictionary,
+ then waits until execution time to see what type it is, then tries
+ to create an Action out of it."""
def __init__(self, var):
if __debug__: logInstanceCreation(self)
self.var = SCons.Util.to_String(var)
@@ -344,13 +412,16 @@ class LazyCmdGenerator:
# The variable reference substitutes to nothing.
return ''
- def __call__(self, target, source, env, for_signature):
+ def _execute(self, target, source, env, for_signature):
try:
return env[self.var]
except KeyError:
# The variable reference substitutes to nothing.
return ''
+ def __call__(self, target, source, env, for_signature):
+ return self._execute(target, source, env, for_signature)
+
def __cmp__(self, other):
return cmp(self.__dict__, other.__dict__)
@@ -361,25 +432,31 @@ class FunctionAction(ActionBase):
if __debug__: logInstanceCreation(self)
self.execfunction = execfunction
if strfunction is _null:
- def strfunction(target, source, env, execfunction=execfunction):
+ def strfunction(target, source, env, self=self):
def quote(s):
return '"' + str(s) + '"'
def array(a, q=quote):
return '[' + string.join(map(lambda x, q=q: q(x), a), ", ") + ']'
- try:
- name = execfunction.__name__
- except AttributeError:
- try:
- name = execfunction.__class__.__name__
- except AttributeError:
- name = "unknown_python_function"
+ name = self.function_name()
tstr = len(target) == 1 and quote(target[0]) or array(target)
sstr = len(source) == 1 and quote(source[0]) or array(source)
return "%s(%s, %s)" % (name, tstr, sstr)
self.strfunction = strfunction
self.varlist = varlist
- def __call__(self, target, source, env):
+ def function_name(self):
+ try:
+ return self.execfunction.__name__
+ except AttributeError:
+ try:
+ return self.execfunction.__class__.__name__
+ except AttributeError:
+ return "unknown_python_function"
+
+ def __str__(self):
+ return "%s(env, target, source)" % self.function_name()
+
+ def _execute(self, target, source, env):
r = 0
if not SCons.Util.is_List(target):
target = [target]
@@ -394,6 +471,10 @@ class FunctionAction(ActionBase):
r = self.execfunction(target=target, source=rsources, env=env)
return r
+ def __call__(self, target, source, env):
+ self.presub(target)
+ return self._execute(target, source, env)
+
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this callable action.
@@ -418,6 +499,12 @@ class ListAction(ActionBase):
def get_actions(self):
return self.list
+ def __str__(self):
+ s = []
+ for l in self.list:
+ s.append(str(l))
+ return string.join(s, "\n")
+
def strfunction(self, target, source, env):
s = []
for l in self.list:
@@ -428,13 +515,17 @@ class ListAction(ActionBase):
s.extend(x)
return string.join(s, "\n")
- def __call__(self, target, source, env):
+ def _execute(self, target, source, env):
for l in self.list:
- r = l(target, source, env)
+ r = l._execute(target, source, env)
if r:
return r
return 0
+ def __call__(self, target, source, env):
+ self.presub(target)
+ return self._execute(target, source, env)
+
def get_contents(self, target, source, env, dict=None):
"""Return the signature contents of this action list.
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index b7006f9..75c82ae 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -241,7 +241,7 @@ class ActionTestCase(unittest.TestCase):
class ActionBaseTestCase(unittest.TestCase):
- def test_cmp(self):
+ def test___cmp__(self):
"""Test Action comparison
"""
a1 = SCons.Action.Action("x")
@@ -256,26 +256,58 @@ class ActionBaseTestCase(unittest.TestCase):
"""
save_stdout = sys.stdout
- save = SCons.Action.print_actions
+ save_print_actions = SCons.Action.print_actions
SCons.Action.print_actions = 0
- sio = StringIO.StringIO()
- sys.stdout = sio
- a = SCons.Action.Action("x")
- a.show("xyzzy")
- s = sio.getvalue()
- assert s == "", s
+ try:
+ a = SCons.Action.Action("x")
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ a.show("xyzzy")
+ s = sio.getvalue()
+ assert s == "", s
+
+ SCons.Action.print_actions = 1
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ a.show("foobar")
+ s = sio.getvalue()
+ assert s == "foobar\n", s
+
+ finally:
+ SCons.Action.print_actions = save_print_actions
+ sys.stdout = save_stdout
+
+ def test_presub(self):
+ """Test the presub() method
+ """
+ save_stdout = sys.stdout
+
+ save_print_actions_presub = SCons.Action.print_actions_presub
+ SCons.Action.print_actions_presub = 0
+
+ try:
+ a = SCons.Action.Action("x")
+
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ a.presub("xyzzy")
+ s = sio.getvalue()
+ assert s == "", s
- SCons.Action.print_actions = 1
+ SCons.Action.print_actions_presub = 1
- sio = StringIO.StringIO()
- sys.stdout = sio
- a.show("foobar")
- s = sio.getvalue()
- assert s == "foobar\n", s
+ sio = StringIO.StringIO()
+ sys.stdout = sio
+ a.presub("foobar")
+ s = sio.getvalue()
+ assert s == "Building foobar with action(s):\n x\n", s
- SCons.Action.print_actions = save
- sys.stdout = save_stdout
+ finally:
+ SCons.Action.print_actions_presub = save_print_actions_presub
+ sys.stdout = save_stdout
def test_get_actions(self):
"""Test the get_actions() method
@@ -363,12 +395,26 @@ class ActionBaseTestCase(unittest.TestCase):
class CommandActionTestCase(unittest.TestCase):
- def test_init(self):
+ def test___init__(self):
"""Test creation of a command Action
"""
a = SCons.Action.CommandAction(["xyzzy"])
assert a.cmd_list == [ "xyzzy" ], a.cmd_list
+ def test___str__(self):
+ """Test fetching the pre-substitution string for command Actions
+ """
+ env = Environment()
+ act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE')
+ s = str(act)
+ assert s == 'xyzzy $TARGET $SOURCE', s
+
+ act = SCons.Action.CommandAction(['xyzzy',
+ '$TARGET', '$SOURCE',
+ '$TARGETS', '$SOURCES'])
+ s = str(act)
+ assert s == "['xyzzy', '$TARGET', '$SOURCE', '$TARGETS', '$SOURCES']", s
+
def test_strfunction(self):
"""Test fetching the string representation of command Actions
"""
@@ -526,7 +572,6 @@ class CommandActionTestCase(unittest.TestCase):
r = act([], [], env.Copy(out = outfile))
assert r == expect_nonexecutable, "r == %d" % r
-
def test_pipe_execute(self):
"""Test capturing piped output from an action
"""
@@ -635,67 +680,6 @@ class CommandActionTestCase(unittest.TestCase):
a([], [], e)
assert t.executed == [ '**xyzzy**' ], t.executed
- def test_get_raw_contents(self):
- """Test fetching the contents of a command Action
- """
- def CmdGen(target, source, env, for_signature):
- assert for_signature
- return "%s %s" % \
- (env["foo"], env["bar"])
-
- # The number 1 is there to make sure all args get converted to strings.
- a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar",
- "$)", "|", "$baz", 1])
- c = a.get_raw_contents(target=[], source=[],
- env=Environment(foo = 'FFF', bar = 'BBB',
- baz = CmdGen))
- assert c == "| $( FFF | BBB $) | FFF BBB 1", c
-
- # We've discusssed using the real target and source names in a
- # CommandAction's signature contents. This would have have the
- # advantage of recompiling when a file's name changes (keeping
- # debug info current), but it would currently break repository
- # logic that will change the file name based on whether the
- # files come from a repository or locally. If we ever move to
- # that scheme, then all of the '__t1__' and '__s6__' file names
- # in the asserts below would change to 't1' and 's6' and the
- # like.
- t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6'])
- s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6'])
- env = Environment()
-
- a = SCons.Action.CommandAction(["$TARGET"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "t1", c
-
- a = SCons.Action.CommandAction(["$TARGETS"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "t1 t2 t3 t4 t5 t6", c
-
- a = SCons.Action.CommandAction(["${TARGETS[2]}"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "t3", c
-
- a = SCons.Action.CommandAction(["${TARGETS[3:5]}"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "t4 t5", c
-
- a = SCons.Action.CommandAction(["$SOURCE"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "s1", c
-
- a = SCons.Action.CommandAction(["$SOURCES"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "s1 s2 s3 s4 s5 s6", c
-
- a = SCons.Action.CommandAction(["${SOURCES[2]}"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "s3", c
-
- a = SCons.Action.CommandAction(["${SOURCES[3:5]}"])
- c = a.get_raw_contents(target=t, source=s, env=env)
- assert c == "s4 s5", c
-
def test_get_contents(self):
"""Test fetching the contents of a command Action
"""
@@ -767,7 +751,7 @@ class CommandActionTestCase(unittest.TestCase):
class CommandGeneratorActionTestCase(unittest.TestCase):
- def test_init(self):
+ def test___init__(self):
"""Test creation of a command generator Action
"""
def f(target, source, env):
@@ -775,6 +759,15 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
a = SCons.Action.CommandGeneratorAction(f)
assert a.generator == f
+ def test___str__(self):
+ """Test the pre-substitution strings for command generator Actions
+ """
+ def f(target, source, env, for_signature, self=self):
+ return "FOO"
+ a = SCons.Action.CommandGeneratorAction(f)
+ s = str(a)
+ assert s == 'FOO', s
+
def test_strfunction(self):
"""Test the command generator Action string function
"""
@@ -866,7 +859,7 @@ class CommandGeneratorActionTestCase(unittest.TestCase):
class FunctionActionTestCase(unittest.TestCase):
- def test_init(self):
+ def test___init__(self):
"""Test creation of a function Action
"""
def func1():
@@ -894,6 +887,22 @@ class FunctionActionTestCase(unittest.TestCase):
assert a.execfunction == func4, a.execfunction
assert a.strfunction is None, a.strfunction
+ def test___str__(self):
+ """Test the __str__() method for function Actions
+ """
+ def func1():
+ pass
+ a = SCons.Action.FunctionAction(func1)
+ s = str(a)
+ assert s == "func1(env, target, source)", s
+
+ class class1:
+ def __call__(self):
+ pass
+ a = SCons.Action.FunctionAction(class1())
+ s = str(a)
+ assert s == "class1(env, target, source)", s
+
def test_execute(self):
"""Test executing a function Action
"""
@@ -984,7 +993,7 @@ class FunctionActionTestCase(unittest.TestCase):
class ListActionTestCase(unittest.TestCase):
- def test_init(self):
+ def test___init__(self):
"""Test creation of a list of subsidiary Actions
"""
def func():
@@ -1008,6 +1017,17 @@ class ListActionTestCase(unittest.TestCase):
g = l[1].get_actions()
assert g == [l[1]], g
+ def test___str__(self):
+ """Test the __str__() method ffor a list of subsidiary Actions
+ """
+ def f(target,source,env):
+ pass
+ def g(target,source,env):
+ pass
+ a = SCons.Action.ListAction([f, g, "XXX", f])
+ s = str(a)
+ assert s == "f(env, target, source)\ng(env, target, source)\nXXX\nf(env, target, source)", s
+
def test_strfunction(self):
"""Test the string function for a list of subsidiary Actions
"""
@@ -1069,7 +1089,7 @@ class ListActionTestCase(unittest.TestCase):
assert c == "xyz", c
class LazyActionTestCase(unittest.TestCase):
- def test_init(self):
+ def test___init__(self):
"""Test creation of a lazy-evaluation Action
"""
# Environment variable references should create a special
diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py
index 47fef2b..c59ce69 100644
--- a/src/engine/SCons/Script/__init__.py
+++ b/src/engine/SCons/Script/__init__.py
@@ -409,6 +409,8 @@ def _set_globals(options):
memory_outf = sys.stdout
elif options.debug == "objects":
print_objects = 1
+ elif options.debug == "presub":
+ SCons.Action.print_actions_presub = 1
elif options.debug == "time":
print_time = 1
elif options.debug == "tree":
@@ -492,7 +494,8 @@ class OptParser(OptionParser):
"build all Default() targets.")
def opt_debug(option, opt, value, parser):
- if value in ["count", "dtree", "includes", "memory", "objects", "pdb", "time", "tree"]:
+ if value in ["count", "dtree", "includes", "memory", "objects",
+ "pdb", "presub", "time", "tree"]:
setattr(parser.values, 'debug', value)
else:
raise OptionValueError("Warning: %s is not a valid debug type" % value)