From: stevenknight Date: Thu, 7 Oct 2004 17:49:23 +0000 (+0000) Subject: Allow passing a dictionary of keyword arguments to Tool specifications. (Gary Oberbr... X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=a9175fd96ab13d4e69e2f81ab247bc1bb3896c97;p=scons.git Allow passing a dictionary of keyword arguments to Tool specifications. (Gary Oberbrunner) git-svn-id: http://scons.tigris.org/svn/scons/trunk@1116 fdb21ef1-2011-0410-befe-b5e4ea1792b1 --- diff --git a/doc/man/scons.1 b/doc/man/scons.1 index 5d44c323..f21e00b8 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -31,7 +31,7 @@ .fi .RE .. -.TH SCONS 1 "August 2004" +.TH SCONS 1 "October 2004" .SH NAME scons \- a software construction tool .SH SYNOPSIS @@ -991,9 +991,21 @@ env = Environment(tools = ['default', 'foo'], toolpath = ['tools']) 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. @@ -1010,6 +1022,36 @@ def my_tool(env): 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. @@ -4072,20 +4114,24 @@ The default is "build". '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .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') ]) @@ -4096,11 +4142,15 @@ u = Tool('opengl', toolpath = ['tools']) 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']) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index e98f08f0..870d95b6 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -116,6 +116,9 @@ RELEASE 0.97 - XXX - 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 diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index af602a5c..8f76d557 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -116,14 +116,16 @@ def our_deepcopy(x): 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 @@ -620,7 +622,7 @@ class Base: 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 @@ -963,9 +965,12 @@ class Base: 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. diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index aa38965a..b2f35363 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -840,6 +840,22 @@ class EnvironmentTestCase(unittest.TestCase): 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): diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py index afad44c1..2adaacb4 100644 --- a/src/engine/SCons/Tool/__init__.py +++ b/src/engine/SCons/Tool/__init__.py @@ -45,24 +45,35 @@ import SCons.Errors 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 @@ -83,7 +94,7 @@ def Tool(name, toolpath=[]): 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 diff --git a/test/tool_args.py b/test/tool_args.py new file mode 100644 index 00000000..f7682561 --- /dev/null +++ b/test/tool_args.py @@ -0,0 +1,73 @@ +#!/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()