Commits

Anonymous committed b0823c6

Allow passing a dictionary of keyword arguments to Tool specifications. (Gary Oberbrunner)

Comments (0)

Files changed (6)

 .fi
 .RE
 ..
-.TH SCONS 1 "August 2004"
+.TH SCONS 1 "October 2004"
 .SH NAME
 scons \- a software construction tool
 .SH SYNOPSIS
 
 This looks for a tool specification in tools/foo.py (as well as
 using the ordinary default tools for the platform).  foo.py should
-have two functions: generate(env) and exists(env).  generate()
-modifies the passed in environment and exists() should return a true
-value if the tool is available.  Tools in the toolpath are used before
+have two functions: generate(env, **kw) and exists(env).
+The
+.B generate()
+function
+modifies the passed in environment
+to set up variables so that the tool
+can be executed;
+it may use any keyword arguments
+that the user supplies (see below)
+to vary its initialization.
+The
+.B exists()
+function should return a true
+value if the tool is available.
+Tools in the toolpath are used before
 any of the built-in ones.  For example, adding gcc.py to the toolpath
 would override the built-in gcc tool.
 
 env = Environment(tools = [my_tool])
 .EE
 
+The individual elements of the tools list
+may also themselves be two-element lists of the form
+.RI ( toolname ", " kw_dict ).
+SCons searches for the
+.I toolname
+specification file as described above, and
+passes
+.IR kw_dict ,
+which must be a dictionary, as keyword arguments to the tool's
+.B generate
+function.
+The
+.B generate
+function can use the arguments to modify the tool's behavior
+by setting up the environment in different ways
+or otherwise changing its initialization.
+
+.ES
+# in tools/my_tool.py:
+def generate(env, **kw):
+  # Sets MY_TOOL to the value of keyword argument 'arg1' or 1.
+  env['MY_TOOL'] = kw.get('arg1', '1')
+def exists(env):
+  return 1
+
+# in SConstruct:
+env = Environment(tools = ['default', ('my_tool', {'arg1': 'abc'})],
+                  toolpath=['tools'])
+.EE
+
 The tool definition (i.e. my_tool()) can use the PLATFORM variable from
 the environment it receives to customize the tool for different platforms.
 
 
 '\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
 .TP
-.RI Tool( string, toolpath=[] )
+.RI Tool( string [, toolpath ", " **kw ])
 Returns a callable object
 that can be used to initialize
 a construction environment using the
 tools keyword of the Environment() method.
 The object may be called with a construction
 environment as an argument,
-in which case the object will be
+in which case the object will
 add the necessary variables
 to the construction environment
 and the name of the tool will be added to the
 .B $TOOLS
 construction variable.
 
+Additional keyword arguments are passed to the tool's
+.B generate()
+method.
+
 .ES
 env = Environment(tools = [ Tool('msvc') ])
 
 u(env)  # adds 'opengl' to the TOOLS variable
 .EE
 .TP
-.RI env.Tool( string [, toolpath] )
+.RI env.Tool( string [, toolpath ", " **kw ])
 Applies the callable object for the specified tool
 .I string
 to the environment through which the method was called.
 
+Additional keyword arguments are passed to the tool's
+.B generate()
+method.
+
 .ES
 env.Tool('gcc')
 env.Tool('opengl', toolpath = ['build/tools'])
   - Add a --debug=findlibs option to print what's happening when
     the scanner is searching for libraries.
 
+  - Allow Tool specifications to be passed a dictionary of keyword
+    arguments.
+
   From Chris Pawling:
 
   - Have the linkloc tool use $MSVS_VERSION to select the Microsoft

src/engine/SCons/Environment.py

    return copy
 
 def apply_tools(env, tools, toolpath):
-    if tools:
-        # Filter out null tools from the list.
-        tools = filter(None, tools)
-        for tool in tools:
-            if SCons.Util.is_String(tool):
-                env.Tool(tool, toolpath)
-            else:
-                tool(env)
+    if not tools:
+        return
+    # Filter out null tools from the list.
+    for tool in filter(None, tools):
+        if SCons.Util.is_List(tool) or type(tool)==type(()):
+            toolname = tool[0]
+            toolargs = tool[1] # should be a dict of kw args
+            tool = apply(env.Tool, (toolname, toolpath), toolargs)
+        else:
+            env.Tool(tool, toolpath)
 
 # These names are controlled by SCons; users should never set or override
 # them.  This warning can optionally be turned off, but scons will still
                     self._dict[key] = self._dict[key] + val
         self.scanner_map_delete(kw)
 
-    def Copy(self, tools=None, toolpath=[], **kw):
+    def Copy(self, tools=[], toolpath=[], **kw):
         """Return a copy of a construction Environment.  The
         copy is like a Python "deep copy"--that is, independent
         copies are made recursively of each objects--except that
                 del kw[k]
         apply(self.Replace, (), kw)
 
-    def Tool(self, tool, toolpath=[]):
-        tool = self.subst(tool)
-        return SCons.Tool.Tool(tool, map(self.subst, toolpath))(self)
+    def Tool(self, tool, toolpath=[], **kw):
+        if SCons.Util.is_String(tool):
+            tool = self.subst(tool)
+            toolpath = map(self.subst, toolpath)
+            tool = apply(SCons.Tool.Tool, (tool, toolpath), kw)
+        tool(self)
 
     def WhereIs(self, prog, path=None, pathext=None, reject=[]):
         """Find prog in the path.  

src/engine/SCons/EnvironmentTests.py

         t4(env)
         assert env['TOOL4'] == 444, env
 
+        test = TestCmd.TestCmd(workdir = '')
+        test.write('faketool.py', """\
+def generate(env, **kw):
+    for k, v in kw.items():
+        env[k] = v
+
+def exists(env):
+    return 1
+""")
+
+        env = Environment(tools = [('faketool', {'a':1, 'b':2, 'c':3})],
+                          toolpath = [test.workpath('')])
+        assert env['a'] == 1, env['a']
+        assert env['b'] == 2, env['b']
+        assert env['c'] == 3, env['c']
+
     def test_Default_TOOLS(self):
         """Test overriding the default TOOLS variable"""
         def t5(env):

src/engine/SCons/Tool/__init__.py

 import SCons.Defaults
 
 class ToolSpec:
-    def __init__(self, name):
+    def __init__(self, name, **kw):
         self.name = name
+        # remember these so we can merge them into the call
+        self.init_kw = kw
 
     def __call__(self, env, *args, **kw):
+        if self.init_kw is not None:
+            # Merge call kws into init kws;
+            # but don't bash self.init_kw.
+            if kw is not None:
+                call_kw = kw
+                kw = self.init_kw.copy()
+                kw.update(call_kw)
+            else:
+                kw = self.init_kw
         env.Append(TOOLS = [ self.name ])
         apply(self.generate, ( env, ) + args, kw)
 
     def __str__(self):
         return self.name
     
-def Tool(name, toolpath=[]):
+def Tool(name, toolpath=[], **kw):
     "Select a canned Tool specification, optionally searching in toolpath."
 
     try:
         file, path, desc = imp.find_module(name, toolpath)
         try:
             module = imp.load_module(name, file, path, desc)
-            spec = ToolSpec(name)
+            spec = apply(ToolSpec, (name,), kw)
             spec.generate = module.generate
             spec.exists = module.exists
             return spec
             raise SCons.Errors.UserError, "No tool named '%s': %s" % (name, e)
         if file:
             file.close()
-    spec = ToolSpec(name)
+    spec = apply(ToolSpec, (name,), kw)
     spec.generate = sys.modules[full_name].generate
     spec.exists = sys.modules[full_name].exists
     return spec

test/tool_args.py

+#!/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__"
+
+"""
+Test the ability to pass a dictionary of keyword arguments to
+a Tool specification's generate() method.
+"""
+
+import os.path
+import sys
+import time
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.write('SConstruct', """
+# Test passing kw args to Tool constructor
+env1 = Environment(tools=[Tool('FooTool', toolpath=['.'], kw1='kw1val')])
+print "env1['TOOL_FOO'] =", env1.get('TOOL_FOO')
+print "env1['kw1'] =", env1.get('kw1')
+
+# Test apply_tools taking a list of (name, kwargs_dict)
+env2 = Environment(tools=[('FooTool', {'kw2':'kw2val'})], toolpath=['.'])
+print "env2['TOOL_FOO'] =", env2.get('TOOL_FOO')
+print "env2['kw2'] =", env2.get('kw2')
+
+""")
+
+test.write('FooTool.py', r"""\
+def generate(env, **kw):
+    for k in kw.keys():
+        env[k] = kw[k]
+    env['TOOL_FOO'] = 1
+def exists(env):
+    return 1
+""")
+
+test.run(arguments = '.', stdout = """\
+scons: Reading SConscript files ...
+env1['TOOL_FOO'] = 1
+env1['kw1'] = kw1val
+env2['TOOL_FOO'] = 1
+env2['kw2'] = kw2val
+scons: done reading SConscript files.
+scons: Building targets ...
+scons: `.' is up to date.
+scons: done building targets.
+""")
+
+test.pass_test()